Open adamchalmers opened 1 year ago
@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.
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. 🔜™️
I think gearWithoutTeeth
totally exists simultaneously with toothToRemove2D
!
Agree about the angular distance param.
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
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.
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.
Without further ado, the user clicks the start sketch
button and selects one of the default planes
App goes into sketch mode (perpendicular to the sketch plane ortho camera)
User selects the circle tooltip, then the axis to set the circle center, then where they want the edge of the circle to be
Does the same thing for the inner circle and exits the sketch
Sketch is selected so that it can be extruded
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.
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', %)
Projects geometry from another solid onto the sketch plane
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.
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
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
Another arc is added
One of the points is selected
And another with shift for multi-select, with multi-cursor
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
Loop so far is mirrored along the yaxis, which comes from selecting the yaxis and that's what the 90
is there for.
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.
Sketch is exited
The previous sketch is selected so that it can extrude-cut
Extrude cut is actually done
Cut operation is then selected
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
Fin
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
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
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
The user wants the rootRadius
to be part of the function params, so they drag that var into the function definition
Some with the outerRadius (though they've stopped renaming at this point)
And the extrude height
Lastly they create a gear twice as big
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.
I forgot to do selecting an axis or similar for the radialPattern
sorry @jgomez720.
I was able to follow along with no problem. I like it.
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:
It'll be something like this (remember, type annotations will be optional, they're included here for clarity)
Just walking through this example with Josh was really helpful for bridging the gap between my software and his hardware background.
Open questions:
circularPattern
is still hazytoothToRemove2D
on the cylinder