Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KCL gear example #6

Open
adamchalmers opened this issue Jul 17, 2023 · 8 comments
Open

KCL gear example #6

adamchalmers opened this issue Jul 17, 2023 · 8 comments

Comments

@adamchalmers
Copy link
Collaborator

adamchalmers commented Jul 17, 2023

We're gonna need examples of how to practically use KCL. I think a gear is a good first step. It's a nontrivial 3D solid, and it's also very common in real CAD. Here's what I've got so far.

@jgomez720 says the way real engineers draw gears is:

  1. Create a cylinder with a cylinder missing in its center
  2. Draw a gap (which, when removed, forms a tooth) on the top of the cylinder
  3. Extrude it down, subtracting it from the cylinder

It'll be something like this (remember, type annotations will be optional, they're included here for clarity)

// Create cylinder with a cylinder missing in center

cylinder = (height: Distance, radius: Distance) =>
	circle(radius) 
	|> extrude(height)

gearWithoutTeeth = (height: Distance) =>
	let
		radius = Distance::cm(30)
	in subtract(
		cylinder(height, radius/10), // outer cylinder
		cylinder(height, radius)     // inner cylinder to subtract
	)

// Draw the negative space which defines the gear tooth.

toothToRemove2D = ... // TODO: define the tooth via lines/path segments

// Extrude the removal tooth into 3D

toothToRemove = (height: Distance) =>
	extrude(toothToRemove2D, height)

// Subtract that tooth from the main gear part via circular pattern

teethToRemove = (teeth: Number, height: Distance) =>
	circularPattern(toothToRemove, teeth, gearWithoutTeeth(height))

// Voila: a parameterized gear function.

gear = (teeth: Number, height: Distance) =>
	subtract(gearWithoutTeeth(height), teethToRemove)

// Which lets you make various individual gears.
gear1 = gear(40, Distance::cm(3))
gear2 = gear(33, Distance::cm(20))

Just walking through this example with Josh was really helpful for bridging the gap between my software and his hardware background.

Open questions:

  • The signature for circularPattern is still hazy
  • How do we draw the toothToRemove2D on the cylinder
@adamchalmers
Copy link
Collaborator Author

adamchalmers commented Jul 17, 2023

@Irev-Dev would love your feedback especially on open question 2.

I guess we'd tag the top face of the cylinder, and then build a tooth-shaped path, and then put the path on the tagged face.

@Irev-Dev
Copy link

For circularPattern I think what you have with teeth being an int makes sense, because usually, you'll want to repeat the pattern a certain amount of times, and not be thinking about it like "a tooth every 12°", especially since defining the angle might mean the teeth don't match up at the end of the full revolution. This goes for other use-cases too. I think there should be an optional angular distance param that defaults to 360° so that users can specify they want this hole to repeat 5 times but only for 45°.

I think we'll draw the toothToRemove2D by selecting the top face, and then using our sketching API as you alluded to. How we're able to select the face with something worked into you're example code does seem a little tricky. I think tagging yes, but doesn't sit very well with gearWithoutTeeth abstraction, because that gear hasn't been created at the time that toothToRemove2D is being worked on.

Considering that and a few other thoughts on the API this is making me think of, I might try my hand at some psuedo code too. 🔜™️

@adamchalmers
Copy link
Collaborator Author

adamchalmers commented Jul 18, 2023

I think gearWithoutTeeth totally exists simultaneously with toothToRemove2D!

Agree about the angular distance param.

@jgomez720
Copy link

Another thing to think about in the circularPattern is which axis you are revolving around. In traditional CAD software, once you created the "donut" (cylinder missing within another cylinder), the software would now give you the center of the cylinder as an axis to click on, but we would need some way for the user to define that. If it's one of the origin axis (X, Y, Z), I'd assume it's easier, but when it's off-origin, we need a way for the user to define

@lf94
Copy link

lf94 commented Jul 18, 2023

I wrote up the same thing in JS which uses node-libfive bindings (an actual working example) to add to the conversation:

const { circle, cm, nothing } = require("../index");

const cylinder = ({ height, radius }) => circle(radius).extrude(height);

const donut = ({ height, outerRadius, innerRadius }) =>
  cylinder({ height, radius: outerRadius })
    .difference(cylinder({ height, radius: innerRadius }));

const tooth2d = () => nothing(); // Define the tooth in whatever way

const tooth3d = ({ height }) => tooth2d.extrude(height);

const teeth = ({ amount, height, radius }) =>
  tooth3d({ height }).distribute.radial({ amount, radius });

const gear = ({ amount, height, radius }) =>
  donut({ height, outerRadius: radius, innerRadius: radius/10 })
    .difference(teeth({ amount, height, radius }));

const gear1 = gear({ amount: 40, height: 3*cm, radius: 30*cm });
const gear2 = gear({ amount: 33, height: 20*cm, radius: 30*cm });

In FREP engines you can also do things like infinite extrude, so the height on the tooth3d is unnecessary.

Quite honestly all I can think about improving this is having native measurement types. Objects, spread, rest, and destructuring really make manipulating variables easy-peasy. The declarative API keeps context where it matters, reducing the need to retype a lot and read naturally.

@Irev-Dev
Copy link

Irev-Dev commented Jul 19, 2023

So here's my attempt at a gear example, It's a bit vague and handy wavey. What it does, and what I'm emphasising is code-gen from UI interactions. One tricky thing about this lang's constraints/goal which is pretty unique is that it needs to treat code-gen as a first-class-citizen. And when we think about API/function calls etc we can't just think about the final state, but also how the code progressed to get to that point. I'm not wedded to any of the syntax below, though I do think the pipe operator is a god send for not having to generate more variable names since they are hard in code-gen.

  • The examples use buttons (that I add to the UI as I need them lol), but these would all have hot-keys.
  • The green triangles with numbers represent where the user has clicked and the order for a given frame
  1. Without further ado, the user clicks the start sketch button and selects one of the default planes
image
  1. App goes into sketch mode (perpendicular to the sketch plane ortho camera)
image
  1. User selects the circle tooltip, then the axis to set the circle center, then where they want the edge of the circle to be
image
  1. Does the same thing for the inner circle and exits the sketch
image
  1. Sketch is selected so that it can be extruded
image
  1. Bit of an explanation I added startSketch separate from addloop because I still like the idea of inner loops implicitly being subtracted from sketches (when extruded) unless the user specifies otherwise, I think this is a sane default that makes UI interactions -> code-gen much easier, but this is just example code, I'm not set on it absolutely.
image
  1. Starts a sketch on the top of another extrusion, why it started a new variable/pipe-expression is a little arbitrary, an alternate API might be
const part01 = startSketch()
  |> addLoop(%)
  |> circle(center: [0,0], radius: 50, %)
  |> addLoop(%)
  |> circle(center: [0,0], radius: 10, %)
  |> extrude(20, tag: extrude01, %)
  |> startSketch(%)
  |> transformToExtrudeTop(ref: 'extrude01', %)
image
  1. Projects geometry from another solid onto the sketch plane
image
  1. Adds more reference geometry for the rootRadius, sets the center of the circle by clicking an existing circle then sets where they want the edge to be.
image
  1. Adds an arc that follows the rootRadius circle and starts aligned with the yaxis, both the axis and the reference geometry would have highlighted when the user hovered and then clicked. I've hand wave over this bit with /* ... on seg02 aligned with positive y axis*/ 😑
    The end point of the arc is set with the 3rd click
image
  1. Involute curve tooltip is selected and it goes up to the other reference circle, hand waving the other involute inputs, can probably just be sane defaults that the user can change if they like
image
  1. Another arc is added
image
  1. One of the points is selected
image
  1. And another with shift for multi-select, with multi-cursor
image
  1. Fillets are added to both.
    The filletBetweenPrevTwo seems a little problematic. The second instance would have to know to skip previous fillets, but it's not worth worrying about for the purposes of this example
image
  1. Loop so far is mirrored along the yaxis, which comes from selecting the yaxis and that's what the 90 is there for.
image
  1. Loop is closed, I realised later that I've actually cut through the top of the circle, which would cause issues later with the extrude-cut, but I'm being lazy and it doesn't deminish from the example much.
image
  1. Sketch is exited
image
  1. The previous sketch is selected so that it can extrude-cut
image
  1. Extrude cut is actually done
image
  1. Cut operation is then selected
image
  1. So that the radial pattern operation can be done, we could probably get away with sane defaults that the user can then edit, but maybe we through up a modal when the button is clicked and prompt them for more details
image
  1. Fin
image
  1. But wait there's more, the user wanted to refactor the code a little to get rid of some of the default var names, first of the rootRadius can be move into a var that describes what it is. The user drags that radius up above the pipe expression
image
  1. Get's a default name, we know it's a radius because that's where it was being used, but the user can update that as they are prompted when they mouse up
image
  1. One second thought the user now wants to abstract this and see if they can make gears of different shapes and sizes, they select it all and git the function button
image
  1. The user wants the rootRadius to be part of the function params, so they drag that var into the function definition
image
  1. Some with the outerRadius (though they've stopped renaming at this point)
image
  1. And the extrude height
image
  1. Lastly they create a gear twice as big
image

After which they did their job as a good developer and gave better names to a bunch of things before putting up a pull request to their team.

@Irev-Dev
Copy link

I forgot to do selecting an axis or similar for the radialPattern sorry @jgomez720.

@jgomez720
Copy link

I was able to follow along with no problem. I like it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants