-
Notifications
You must be signed in to change notification settings - Fork 0
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
Tags for entities #3
Comments
Yeah, I really like this idea! One way to make it more ergonomic would be this convention: any constant implicitly has a default tag, which is the name of that constant. So if you left off the BTW in all the API code I call the tag the "ID", so we should standardize so we don't go back and forth between tag and ID. |
Based on the discussion in #2 I think we're going to avoid attributes for now. But you can easily specify a part's tag via a keyword (or required positional) argument. I still like the idea that named constants would have default tags from their binding label. |
The intention I had with the example the tag was for the line was for the The default tag could still work but would be for the (sketch in this case) entity as a whole instead of something within. I'll close this one. |
Let's leave this open and discuss tags more generally. Thanks for explaining, and also for your blog post sketching out these ideas a few years ago. I think TypeCurrent KCL docs don't have an arbitrary String type, rather, a Text type which is specifically for putting text into your designs. I suggest we have a new type which is Tag, which works kinda like Ruby symbols. Symbols are:
Seems like a good fit for tags. When the AST runs semantic analysis, it can track every tag declaration and make sure every tag reference refers to a valid tag. This means we'll need a consistent way to recognize when tags are being set. What can be tagged?We want lines to be tagged, as in your sketch example. What about particular faces? E.g. if you want to sketch on top of a cylinder, do you tag the top face of the cylinder, and then reference that tag in your sketch? Josh explained to me yesterday that generally CAD either sketches on a plane or on a face of a solid. Maybe our sketches will start by taking a "what to sketch on" parameter, which is something like
i.e. you can sketch on any plane, or on some tagged face of a Solid3D. Syntax proposalWIP idea, so please criticize it. To tag an entity, you pass a Functions which can refer to tags explicitly have a parameter of type
Note that here I've declared an anonymous enum for the Here's how you'd actually invoke the function:
|
This might be nice for generated code, but I imagine it would be hard to remember as a learning designer/programmer that you have to include this kind of inline enum in the function declaration if you want to be able to refer to tagged values in your custom functions (sidenote: I do think we will likely see end-users refer to things like that as "custom" even though it's all kinda custom, just arbitrary code). Might be a silly question, but does that inline enum need to be there? |
Oh yeah, your point is compelling and noble, I prefer your design!
…On Tue, 18 July 2023, 11:14 am Frank Noirot, ***@***.***> wrote:
length: Literal(Distance) | LengthOf(Tag)
This might be nice for generated code, but I imagine it would be hard to
remember as a learning designer/programmer that you have to include this
kind of inline enum in the function declaration if you want to be able to
refer to tagged values in your custom functions (sidenote: I do think we
will likely see end-users refer to things like that as "custom" even though
it's all kinda custom, just arbitrary code).
Might be a silly question, but does that inline enum need to be there?
LengthOf(seg01) as it's being passed into angledLine() should resolve to
a valid Literal(Distance) anyway if the compiler resolves that inner
function call first, right?
—
Reply to this email directly, view it on GitHub
<#3 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABJIFYJMIC36MZH7JCJFD5LXQ2Y6LANCNFSM6AAAAAA2JYQC6I>
.
You are receiving this because you modified the open/close state.Message
ID: ***@***.***>
|
Regarding the question of what can be tagged, I think any geometry can be:
To that end, I think it's worth thinking about the signature for functions like
So you couldn't pass in the Tag for a Point, because getting the length of a Point is nonsensical as far as I know. But I don't know if that However I'm not sure how to enumerate which kinds of entity tags can be passed into a function; like are there functions that make sense for Points, Lines, Planes, and any other geometric entities I'm missing? |
The current language design has a few built-in traits e.g.
etc. I don't know if "height" and "length" are meaningful concepts here because they kinda presuppose a view or frame of reference. How do users say that the 2x4 rectangle has length 2 depth 4 vs. depth 2, length 4? |
I like the I don't think there would be a I'm beginning to think that we won't need a way to represent a function type signature that accepts more than one type of Tag. |
Been giving this a think today. I don't love the idea of having to pass in an argument for tags for two reasons:
I've written up my thoughts on tagging these implicitly-created pieces of geometry here: SummaryWe will need an affordance to tag pieces of geometry (Points, Lines, and Planes/Faces) that were implicitly created by an operation for reference to use later in KCL programs. Problem StatementIn all CAD programs, it is possible to create a new sketch on a face created from an extrusion command. Here is a very basic example recorded in Onshape: Extrusions implicitly create a number of new Faces and Lines within the model, as do other operations such as a Boolean Subtraction, Intersection, or Exclusion. Currently we have no way in KCL to represent these implicitly-created pieces of geometry. Proposed SolutionSince there could be any number of new faces or lines created in such operations, I propose that we create a deterministic rule set for how they are indexed, something like:
...and so on until it is impossible to create a non-deterministic arrangement of implicit geometry. I believe my example above has some problems because if the object is rotated or translated, so I'd like people smarter than me to help find a deterministic way to index. We index implicitly-created Faces and Lines using a rubric like the one above into separate lists. And we allow all geometry-creating functions to tag any of its sub-elements using a Code examplewidth = 10.cm
length = 5.cm
height = 12.cm
// Also showing a JS feature of matched named parameters not needing "width = width"
box = startSketch(0, 0)
|> rectangle(center = [0, 0], width, height = length)
|> extrude(height)
sketchingFace = box.tagFace(2)
roundedEdge = box.tagEdge(3)
knob = let (
center = [length / 2, height / 2]
radius = 3.mm
) in startSketch(sketchingFace)
|> circle(center, radius)
fullPart = union(box, knob) ConclusionWe should give every returned value from a geometry-creating function access to If we have more ergonomic or robust ways of indexing into the faces and lines I would love that, but as a product of codegen I don't see a huge usability issue with having it just be an index integer. Our compiler could give index out-of-bound error messages and so on if things change further up the dependency graph. |
This is kind of why I think the base primitive should always be a segment, and not anything more abstract like a rectangle or a box because of this problem. By keeping segments, as the base, it means we can always come back to tagging them when a user wants to build on a face. And btw, I don't think this then limits our UI/UX, as there's no reason why clicking a rectangle tool and dragging it on a sketch plane can't produce, startLoop()
|> xLine(5, tag:'seg01', %)
|> yLine(4, tag:'seg01', %)
|> xLine(-segLen(%..seg01), %)
|> yLine(-segLen(%..seg02), %) |
Yeah I hear you @Irev-Dev. I guess since this is a domain-specific language accompanied by a GUI I was hoping we could create UX that aids in understanding the indices for the edges and faces, possibly like so:
I'm a little confused, how does your example work when a user wants to reference a bit of geometry that was created implicitly as part of another operation, like creating a second sketch on a face created as a result of an extrude? |
What do you think @franknoirot? |
Oh and because there is still more working in regards to code-gen workflow with the old fake engine, I put up a PR with an old commit so that we can still play with the old thing with a vercel build. |
This makes sense @Irev-Dev! I think I could see a couple niceties for the codegen of the rectangle tool if it's generating segment-based code too. I get this now, that if we have the originating segments to tag before an extrude we can have a reference to the face created by that segment during the extrude (a child in the dependency graph, I think). How would this work in instances where the face being extruded from is not so cleanly mapped from the segment it was created by, or which was created by an operation that never involved segments directly? For example, take a boolean subtraction of two cubes partially overlapping in space. Now the user wants to sketch on one of the faces created by the subtraction. How do they reference it? By segments alone it seems convoluted. At least with a heuristic, winding order sort of indexing you can easily say "face n of the resulting shape". |
What I really don't like about indexes is that it's now moving the source of truth away from being declarative in language to being something that's being stored in the engine, the user will only be able to tell which index is which with help from the engine displaying the index in the view that you had. Sure it has a heuristic/winding order, but how likely are users to learn and understand it, and what happens when you instancing much more complex shapes, even like a bolt, figuring out which faces have which index without help from the engine sounds really difficult. If instead only using tags then you could go a long way without feedback from the view because it's declarative. And I have not found a circumstance where faces will not have tags if you insist on segments being the base abstraction. here's an excerpt from CadHub I'm sure you can see how that applies to your cube subtraction example so long as cubes aren't just instanced out of thin air and are instead created by extruding a 4-segment loop then those faces can still be tagged, and will be transferred as part of the subtraction. |
In the case where a part has instances created in a loop then the faces can be a combination of the face's tag from the original shape, and then it's index from the loop |
No you're right, this is more learnable and intuitive than a winding order. I think the idea that you have to go all the way back to tag the line segment that created the face on the cutting object in order to reference a face like my example will be a surprising revelation for users, but you've convinced me that all created geometries can be represented this way. |
How does this function parameter implementation of tagging work when you want to create a function by abstracting, but still want entities to be "taggable"? You have to expose the
|
I'd name the tags tag1, ..., tag4, and describe in the docstring which edge they belong to. I can't imagine it'll make a lot of sense if it's just textual but with the visualisation it'll work. |
I was supposed to come back to this. One issue I'm still not sure about with tags is the case where you're importing geometry from a third party library, and they haven't tagged all of their segments, and let's say you want to extrude on one of the faces without a tag, in that case:
You're last comment @franknoirot, it's worth remembering that the box = (width: Distance, length: Distance, height: Distance) => startLoop()
|> xLine(width, tag: 'seg01', %) // <--- I don't know what to call these tag parameters in my new function
|> yLine(length, tag: 'seg02', %)
|> xLine(-segLen(%..seg01), tag: 'seg03', %)
|> close(%)
|> extrude(height, %)
const boxInstance = box(/* . . . */)
const boxInstanceFilleted = FilletByTag(boxInstance..seg01, 3, boxInstance)
const ExactSameButDoneInPipe = box(/* . . . */)
|> FilletByTag(%..seg01, 3, %) That's maybe a bad example because maybe Does that answer the question, I might have missed the point 😬 |
No @Irev-Dev I think that does answer my question. That's pretty cool, although as you mentioned above it does rely on the function author to provide tags for each of the entities in a function, and for the function user to understand both a) that the tags exist and b) what their names are. My point with the winding order is that it guarantees that every entity is "tagged" according to a well-defined ruleset, no matter how or when they are referenced. While I agree that it is harder to learn this ordering and keep its rules in your head, the UI is there to help you with that and, more importantly, it does not place the burden of creating well-named tags for every entity in a part that could be referenced by a future user on error-prone manual function authors. Like @adamchalmers said above:
That sounds like winding order to me, just a function-author-specific one. I don't see how with this named tag approach we aren't asking function users to learn the internal tag naming conventions of every external function they call, and asking them to carefully design the tag naming conventions of their internally-authored functions to be both well-defined and flexible. |
There's a need to tag entities in the language to refer to them later, currently, that's done with strings so
might become
There are a number of problems with this
segLen('sEg01', %)
to fail.segLen(%$seg01, %)
.$
is arbitrary but also a bit weird, maybe..
might be a better convention?%..seg01
, maybe also weird that seg01 starts its life as what looks like a string and then changes to something that behaves more like a key.line([2.8, 0], %)
->line({ to: [2.8, 0], tag: 'seg01' }, %)
you can see that I'm trying to keep the line function to always taking two params, and adding in more optional params by changing the first param to a key-value pair, which in itself is kind of ugly.With all of that said maybe attributes could work as a way of adding tags that's still compatible with pipeExpressions or not
And maybe there's a possibility of this having this part of the typing system i.e.
It would know that the tag seg01 doesn't exist ahead of execution/calls to the engine?
This goes against something I said in #2, that attributes can be used for metadata not associated with modelling, but I think I can live with that.
The text was updated successfully, but these errors were encountered: