KittyCAD / kcl-experiments

KittyCAD Language
9 stars 0 forks source link

Constraint solver syntax #21

Open Irev-Dev opened 2 weeks ago

Irev-Dev commented 2 weeks ago

There's lots proposals for a constraint syntax, not at all centralised so I'm adding to the chaos with another proposal

There's my original non-solver constraints write up https://github.com/KittyCAD/modeling-app/issues/111 Josh's https://github.com/KittyCAD/notes/blob/main/docs/personal/Gomez-Josh/kcl/kcl-refactor.md Jon's https://github.com/KittyCAD/kcl-experiments/pull/13 Nick's https://gist.github.com/nrc/efdec535bd961a0f7b470480676fa9eb

And here's my new write up for a solver syntax, I hope what I can contribute thinking through the code-mods and how individual bits of UI ties back to the code.

I have a handful of principles that should go into the design of the solver syntax. (I agree with lots that's been said already so you'll notice I'm repeating ideas from others below).

  1. We make a clean break from the chaining profile syntax
  2. A sketch is nothing but data
  3. A sketch should be in a special block sketch mySketch {...}
  4. Normal expressions are not allowed in said block, no variable declarations, no if statements, no loops. One exception would be basic math like 5+2 or myVar / 2 to be used as values.
  5. There is no difference between a "Constraint" and a "Dimension"
  6. Each constraint has a UI representation (when in sketch mode)
  7. 2D Solver should be implemented in KCL (as it must be compatible with partial/mock executions) 3D constraints are completely separate, and not related, I think we should use the solidwork's terminology "mates" to minimise confusion.
  8. Old syntax should stay, (but maybe not the UI for it).

Expanded on a little more

  1. The whole chaining syntax was added very much with the implicit constraints in mind, trying to merge the two together is fraught, if we're going with a different paradigm it needs syntax designed for it.
  2. Going from implict constraints to a solver syntax can be conceptualised as the constraints being solved in the code, now being solved at run time (by the solver). Then it's useful to conceptualise the syntax as just declaring data ready to be fed into the solver, relates strongly to 3 and 4.

3 & 4. Allowing dynamic language features smells like a huge foot gun that we should avoid in the first pass, and an easy way to do this is by giving it's own special block sketch mySketch {...} or similar to limit what can be used inside. Bit of an aside, because of the lack of chaining, we don't even have the concept of a profile tightly defined, so we should get "multi-profiles" for free

  1. Both Constraints and Dimensions are simply feeding info to solve, they should be grouped together in the syntax
  2. How the code get represented in the UI beyond the segments themselves has be to thought of ahead of time, and simple mental models are also important, So each constraint being something declared in the code and interactable in the UI is a great tie together, This will make more sense in the example code below.
  3. Sketch mode relies on the fact that we can run the 2d only part of the script locally, especially for respect constraints while animating edits (user dragging a handle) and the same forces are at place here. Possible performance issue here if the solver is slow, (which I doubt for anything but massive sketches) when using it for animating edits, if that's the case we can separate animating the currently segments the user is editing and letting the solver catch up with the rest of the sketch greyed out or similar.
  4. Considering it will be as simple as not deleting the old std lib functions they might as well stay, also relates to 4 since these are explicitly defining each segment, they are going to be much less of a foot gun when it comes to if statements, loops etc, so they'll still have a place for library authors or higher level abstractions.

To make the above concrete with how it will fit into the UI here a simple example:

In the below I didn't step through the most clicking to add each one of these segments because it's straight forward First only the point1 { x: 0, y: 0.5 } is added to the block, and then on the second click another point is added point2 { x: 10, y: 1, } which allows a segment to be added between the two with line1 straight { start: point1, end: point2 } and so and so forth.

Other things to point out

Image

The user selects the top and bottom segments and applies the horizontal constraint and the UI:

The code mode for this is straight forward, it's basically adding horizontal { segs: [<User's selections>] }, will likely need to get the solver to inform a second mod as to what information is now redundant, in order to remove the y information from this example.

Image

To make sure it's not an after thought, let's consider what happens when a constraint is removed (maybe through the feature tree, or interacting with the symbol in the sketch), either way the user removes the horizontal constraint from the top segment. The obvious thing is that line3 is removed from the horizontal constraint, but from the previous step we removed y information from point2 that we need now. In the same manner that is done with the current constraints is we can pull segment information from the last execution to backfill this information to add y: 0.5, this basically the information needed so that we can remove the constraint without the segment moving. There's no need to try and restore the value before the constraint was applied in the first place.

Image

Let's also touch on editing the lines by dragging handles, we'll look at dragging two separate points, one partially constrained, the other not. Below point2 is trying to be dragged down and to the right, but because it's constrained, the point only moves horizontally. The code mod here is really simple. From the mouse drag, we get updated XY coords, but when applying them to point2 { x: 12.3 } because there's no y component to change, only the x will update

Image

Where as the unconstrained point4 gets both x and y updated in the code mode.

Image

We'll continue on with the constraints, add the horizontal constraint back in

Image

And now two vertical constraints. Notice point4 no longer needs any XY info.

Image

User selects line4 and point3, adds a perpendicular distance constraint, this removes XY info from point2. This perpendicular distance is really a dimension, but as pointed out earlier, I don't think we should differentiate. This new constraint needs a UI element, and in this case it's obvious, it's the dimension overlay. giving a easy to way edit this after the fact as well.

The meta property is for us to dump a base64 string or similar for where to put the dimension overlay, because we can try and put it in an intelligent place and not let users move it around, but I think that would be a hard sell, and with code being the source of truth, we need to store it somewhere in the script, open to other ideas though.

Image

Another perpendicular distance constraint

Image

And finally we're just going to lock off point1 as fixed, (User selects this point and adds a fixed point constraint), again new constraint needs a UI element, the one I added is very goofy, not a suggestion for what this icon should actually be.

Image

Final comment here is that this sketch has been fully constrained, and the mental model is really simple when looking at the code, once all of the info is removed from the original points then it's constrained, there will be a few more nuances when it comes to non-straight segments, but I think we can extend this mental model.

I did not mention anything about tags here, which are not needed for use within sketches anymore, but are still needed for selecting faces and edges and etc once the sketch is extruded or other.

Relationship expressions like ||line2|| = 0.5 * ||line3|| from Jon's proposal I think is a cool concept, but not actually something ME really expect so not sure we should aim for this initially, when a equalLength {segs: [line2, line3]} would suffice (and the 0.5 * I doubt will get used much)

adamchalmers commented 2 weeks ago

And now two vertical constraints. Notice point4 no longer needs any XY info.

The XY info have been removed from point2 instead, right? Or am I misunderstanding. My impression is that one of those two points doesn't need any XY information anymore.

The meta property is for us to dump a base64 string or similar for where to put the dimension overlay, because we can try and put it in an intelligent place and not let users move it around, but I think that would be a hard sell, and with code being the source of truth, we need to store it somewhere in the script, open to other ideas though.

Why shouldn't the constraint's visible location in the 2D sketch view just be determined/locked onto one place? E.g. if you're constraining a distance, label that distance. If you're constraining that two lines are equal, put a little label on both lines, etc. If we do have to make it user-configurable, I'd prefer something meaningful to the user (e.g. "visuals: { anchorOn: point2 }" or "visuals: { centeredBetween: [line2, line4]} etc)". If it's base64-encoded binary data, there's no real way to resolve git conflicts and the program becomes a lot less readable.

Some other questions:

nrc commented 2 weeks ago

Minor note: https://github.com/KittyCAD/kcl-experiments/pull/20 is an updated version of the gist at the start of the issue

nrc commented 2 weeks ago
  1. Normal expressions are not allowed in said block, no variable declarations, no if statements, no loops. One exception would be basic math like 5+2 or myVar / 2 to be used as values.

Is this something you think is desirable long-term, or is it purely to make things easier to implement?

This seems like the most dramatic change proposed so far in that it is really a different paradigm for KCL. As you say in point 2, this is just data, not programming. I'm not sure how this idea of sketches as data/constraints works with variables or functions or other programming concepts we make heavy use of at the moment.

What do you envisage happening outside the sketch? E.g., what does extrude look like?

I guess bigger question, if KCL is just a data format (or mostly a data format with some programming around the edges) what is the key value proposition or distinguishing feature of KMA?

nrc commented 2 weeks ago
  1. 2D Solver should be implemented in KCL

This seems fraught to me (not that I have a better solution, sketch mode is just awkward). How would it work? It seems like KCL would need knowledge of geometry which is currently only known in the engine. E.g., if there is a constrain that a line must be parallel to the tangent of a curve at a point, do we duplicate all the logic about every curve in the engine and in KCL? Does the engine have API to get this info? (But then, we are introducing possible latency issues by forcing round trips). And do we duplicate the constraint solver in KCL and the engine too? Or only send resolved constraints to the engine, but in that case the API of the engine does not seem like it would be fit for purpose as a CAD component for uses without KCL.

adamchalmers commented 2 weeks ago
  1. Normal expressions are not allowed in said block, no variable declarations, no if statements, no loops. One exception would be basic math like 5+2 or myVar / 2 to be used as values.

Is this something you think is desirable long-term, or is it purely to make things easier to implement?

This seems like the most dramatic change proposed so far in that it is really a different paradigm for KCL. As you say in point 2, this is just data, not programming. I'm not sure how this idea of sketches as data/constraints works with variables or functions or other programming concepts we make heavy use of at the moment.

What do you envisage happening outside the sketch? E.g., what does extrude look like?

I guess bigger question, if KCL is just a data format (or mostly a data format with some programming around the edges) what is the key value proposition or distinguishing feature of KMA?

Yeah, big concerns about this -- things like my procedural gear would not be possible without dynamic definitions of sketch lines.

Distinguishing features would still be the cloud streaming, the ability to break code into smaller functions with meaningful diffs and CRDT collaboration, copying and pasting, etc. But we'd lose a lot of the power.

jtran commented 2 weeks ago

Some things I think are good:

  1. The idea that a sketch is data is nice because it doesn't matter what generates that data or how it gets generated.
  2. When the user constrains something (e.g. horizontal), its initial point is deleted from the code
  3. Nice UI integration

Some things I don't understand or I think could use work:

  1. Other than the point-and-click UI, the above leaves it unspecified as to how to generate the sketch. I.e. my first thought is that I will want code to generate the above JSON-ish data, with if-else, loops, functions, etc.
  2. "Normal expressions are not allowed in said block" so how do you build abstractions and compose them? This is a deal-breaker to me. The above seems less like a language and more like a data format. Is this what you intended? If so, I think using straight up JSON is preferable.
  3. "dump a base64 string or similar" This is a big detriment to humans reading the code. Why not make it transparent what the data is?