Skip to content

Constraint solver syntax #21

Open
Open
@Irev-Dev

Description

@Irev-Dev

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 KittyCAD/modeling-app#111
Josh's https://github.com/KittyCAD/notes/blob/main/docs/personal/Gomez-Josh/kcl/kcl-refactor.md
Jon's #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

  • The faint purple text is not intended to be part of the UI, instead hopefully will help with following along
  • You can see what I mean by the block is just data, I almost made it just JSON, but we can take some liberties to make it more ergonomic. I'm not tied to many of the specifics of this, beside that the special block itself is important, also having three explicit sections for points, segments and constraints is useful.

Image

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

  • adds the horizontal constraints horizontal { segs: [line1, line3] }, it might be best to list these each explicitly, horizontal line1, horizontal line3 but either works.
  • Each of the constraints has a UI element added for it, it's the blue dashes next to the segments, we can make these clickable to remove or explainer tool-tips etc.
  • Information determined to be redundant by the solver is remove from the "points" section, In this case Points 2 and 4 no longer need y information, as that follows from the constraints.

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions