From fbcbb341e24cfc779e5148466cdaf54865f0926a Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Tue, 1 Jul 2025 12:42:12 -0500 Subject: [PATCH] KCL: Add planeOf function to stdlib (#7643) Gets the plane a face lies on, if any. Closes #7642 --- docs/kcl-std/functions/std-sketch-planeOf.md | 48 ++ docs/kcl-std/index.md | 1 + docs/kcl-std/modules/std-sketch.md | 1 + rust/justfile | 3 + rust/kcl-derive-docs/src/example_tests.rs | 1 + rust/kcl-lib/src/execution/cad_op.rs | 5 +- rust/kcl-lib/src/execution/exec_ast.rs | 8 +- rust/kcl-lib/src/execution/geometry.rs | 9 +- rust/kcl-lib/src/execution/kcl_value.rs | 36 +- rust/kcl-lib/src/execution/types.rs | 14 +- rust/kcl-lib/src/simulation_tests.rs | 21 + rust/kcl-lib/src/std/args.rs | 6 +- rust/kcl-lib/src/std/mod.rs | 4 + rust/kcl-lib/src/std/patterns.rs | 2 +- rust/kcl-lib/src/std/planes.rs | 116 ++- rust/kcl-lib/src/std/segment.rs | 8 +- rust/kcl-lib/src/std/shapes.rs | 9 +- rust/kcl-lib/src/std/sketch.rs | 4 +- rust/kcl-lib/std/sketch.kcl | 21 + ...al_test_example_fn_std-sketch-planeOf0.png | Bin 0 -> 19940 bytes .../tests/plane_of/artifact_commands.snap | 381 ++++++++++ .../plane_of/artifact_graph_flowchart.snap | 6 + .../plane_of/artifact_graph_flowchart.snap.md | 105 +++ rust/kcl-lib/tests/plane_of/ast.snap | 705 ++++++++++++++++++ rust/kcl-lib/tests/plane_of/input.kcl | 13 + rust/kcl-lib/tests/plane_of/ops.snap | 254 +++++++ .../tests/plane_of/program_memory.snap | 192 +++++ .../kcl-lib/tests/plane_of/rendered_model.png | Bin 0 -> 54480 bytes rust/kcl-lib/tests/plane_of/unparsed.snap | 16 + 29 files changed, 1945 insertions(+), 44 deletions(-) create mode 100644 docs/kcl-std/functions/std-sketch-planeOf.md create mode 100644 rust/kcl-lib/tests/outputs/serial_test_example_fn_std-sketch-planeOf0.png create mode 100644 rust/kcl-lib/tests/plane_of/artifact_commands.snap create mode 100644 rust/kcl-lib/tests/plane_of/artifact_graph_flowchart.snap create mode 100644 rust/kcl-lib/tests/plane_of/artifact_graph_flowchart.snap.md create mode 100644 rust/kcl-lib/tests/plane_of/ast.snap create mode 100644 rust/kcl-lib/tests/plane_of/input.kcl create mode 100644 rust/kcl-lib/tests/plane_of/ops.snap create mode 100644 rust/kcl-lib/tests/plane_of/program_memory.snap create mode 100644 rust/kcl-lib/tests/plane_of/rendered_model.png create mode 100644 rust/kcl-lib/tests/plane_of/unparsed.snap diff --git a/docs/kcl-std/functions/std-sketch-planeOf.md b/docs/kcl-std/functions/std-sketch-planeOf.md new file mode 100644 index 000000000..111a03db3 --- /dev/null +++ b/docs/kcl-std/functions/std-sketch-planeOf.md @@ -0,0 +1,48 @@ +--- +title: "planeOf" +subtitle: "Function in std::sketch" +excerpt: "Find the plane a face lies on. Returns an error if the face doesn't lie on any plane (for example, the curved face of a cylinder)" +layout: manual +--- + +Find the plane a face lies on. Returns an error if the face doesn't lie on any plane (for example, the curved face of a cylinder) + +```kcl +planeOf( + @solid: Solid, + face: TaggedFace, +): Plane +``` + + + +### Arguments + +| Name | Type | Description | Required | +|----------|------|-------------|----------| +| `solid` | [`Solid`](/docs/kcl-std/types/std-types-Solid) | The solid whose face is being queried. | Yes | +| `face` | [`TaggedFace`](/docs/kcl-std/types/std-types-TaggedFace) | Find the plane which this face lies on. | Yes | + +### Returns + +[`Plane`](/docs/kcl-std/types/std-types-Plane) - An abstract plane. + + +### Examples + +```kcl +triangle = startSketchOn(XY) + |> polygon(radius = 3, numSides = 3, center = [0, 0]) + |> extrude(length = 2) + +// Find the plane of the triangle's top face. +topPlane = planeOf(triangle, face = END) + +// Create a new plane, 10 units above the triangle's top face. +startSketchOn(offsetPlane(topPlane, offset = 10)) + +``` + +![Rendered example of planeOf 0](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAAAALQCAYAAADPfd1WAABNq0lEQVR4Ae3AA6AkWZbG8f937o3IzKdyS2Oubdu2bdu2bdu2bWmMnpZKr54yMyLu+Xa3anqmhztr1a8+6EEP4qqrrrrqqquuuuqqq6666qqrrrrqqquu+j+JylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuur/vBd7sRd7bZ6Pf/iHf/htrrrqqquuuuqqq676v4zKVVddddVVV1111VX/p73O67zOe3/4h3/4d128ePF2HuDEiRM3f/3Xf/37/NZv/dZ3c9VVV1111VVXXXXV/1XoQQ96EFddddVVV1111VVX/d90zTXXPPibvumbnv60pz3tD7/lW77lbXmAD/qgD/rJhz70oa/6IR/yIQ+57777buWqq6666qqrrrrqqv+LqFx11VVXXXXVVVdd9X/eiRMnbn7Hd3zHr+EBHvrQh74qV1111VVXXXXVVVf9X0flqquuuuqqq6666qr/077+67/+fT78wz/8u17u5V7unXguP/IjP/LZ9913361cddVVV1111VVXXfV/FXrQgx7EVVddddVVV1111VX/d11zzTUPfrEXe7HX5vn4rd/6re/mqquuuuqqq6666qr/y9CDHvQgrrrqqquuuuqqq6666qqrrrrqqquuuuqq/5Mox48f56qrrrrqqquuuuqq/x8+/MM//Lse/OAHvzSgs2fP3spVV1111VVXXXXVVf/XUbnqqquuuuqqq6666v+Fb/qmb3r6Nddc82Ce6UM+5EMect99993KVVddddVVV1111VX/lxFcddVVV1111VVXXfX/wjXXXPNgHuBzP/dzf5urrrrqqquuuuqqq/6vI7jqqquuuuqqq6666v+Ff/iHf/htnum+++679YM/+IMfzFVXXXXVVVddddVV/9ehBz3oQVx11VVXXXXVVVdd9f/D67zO67z3mTNnHvSjP/qjn8NVV1111VVXXXXVVf8foAc96EFcddVVV1111VVXXXXVVVddddVVV1111VX/J1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/Kv4R/WxcCe3yxu8AAAAASUVORK5CYII=) + + diff --git a/docs/kcl-std/index.md b/docs/kcl-std/index.md index 9b7cb37c8..4a31d1d64 100644 --- a/docs/kcl-std/index.md +++ b/docs/kcl-std/index.md @@ -67,6 +67,7 @@ layout: manual * [`patternCircular2d`](/docs/kcl-std/functions/std-sketch-patternCircular2d) * [`patternLinear2d`](/docs/kcl-std/functions/std-sketch-patternLinear2d) * [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d) + * [`planeOf`](/docs/kcl-std/functions/std-sketch-planeOf) * [`polygon`](/docs/kcl-std/functions/std-sketch-polygon) * [`profileStart`](/docs/kcl-std/functions/std-sketch-profileStart) * [`profileStartX`](/docs/kcl-std/functions/std-sketch-profileStartX) diff --git a/docs/kcl-std/modules/std-sketch.md b/docs/kcl-std/modules/std-sketch.md index b64f54467..a96536547 100644 --- a/docs/kcl-std/modules/std-sketch.md +++ b/docs/kcl-std/modules/std-sketch.md @@ -32,6 +32,7 @@ This module contains functions for creating and manipulating sketches, and makin * [`patternCircular2d`](/docs/kcl-std/functions/std-sketch-patternCircular2d) * [`patternLinear2d`](/docs/kcl-std/functions/std-sketch-patternLinear2d) * [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d) +* [`planeOf`](/docs/kcl-std/functions/std-sketch-planeOf) * [`polygon`](/docs/kcl-std/functions/std-sketch-polygon) * [`profileStart`](/docs/kcl-std/functions/std-sketch-profileStart) * [`profileStartX`](/docs/kcl-std/functions/std-sketch-profileStartX) diff --git a/rust/justfile b/rust/justfile index f098f05de..b85c94480 100644 --- a/rust/justfile +++ b/rust/justfile @@ -8,6 +8,9 @@ lint: # Ensure we can build without extra feature flags. cargo clippy -p kcl-lib --all-targets -- -D warnings +lint-fix: + cargo clippy --workspace --all-targets --all-features --fix + # Run the stdlib docs generation redo-kcl-stdlib-docs-no-imgs: EXPECTORATE=overwrite {{cnr}} {{kcl_lib_flags}} docs::gen_std_tests::test_generate_stdlib diff --git a/rust/kcl-derive-docs/src/example_tests.rs b/rust/kcl-derive-docs/src/example_tests.rs index 392fc3b91..27ba9fcf9 100644 --- a/rust/kcl-derive-docs/src/example_tests.rs +++ b/rust/kcl-derive-docs/src/example_tests.rs @@ -97,6 +97,7 @@ pub const TEST_NAMES: &[&str] = &[ "std-offsetPlane-2", "std-offsetPlane-3", "std-offsetPlane-4", + "std-sketch-planeOf-0", "std-sketch-circle-0", "std-sketch-circle-1", "std-sketch-patternTransform2d-0", diff --git a/rust/kcl-lib/src/execution/cad_op.rs b/rust/kcl-lib/src/execution/cad_op.rs index 6c277258b..585a50d04 100644 --- a/rust/kcl-lib/src/execution/cad_op.rs +++ b/rust/kcl-lib/src/execution/cad_op.rs @@ -226,10 +226,7 @@ impl From<&KclValue> for OpKclValue { match value { KclValue::Uuid { value, .. } => Self::Uuid { value: *value }, KclValue::Bool { value, .. } => Self::Bool { value: *value }, - KclValue::Number { value, ty, .. } => Self::Number { - value: *value, - ty: ty.clone(), - }, + KclValue::Number { value, ty, .. } => Self::Number { value: *value, ty: *ty }, KclValue::String { value, .. } => Self::String { value: value.clone() }, KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => { let value = value.iter().map(Self::from).collect(); diff --git a/rust/kcl-lib/src/execution/exec_ast.rs b/rust/kcl-lib/src/execution/exec_ast.rs index 34639033d..9d2240e30 100644 --- a/rust/kcl-lib/src/execution/exec_ast.rs +++ b/rust/kcl-lib/src/execution/exec_ast.rs @@ -1297,7 +1297,7 @@ impl Node { Ok(KclValue::Number { value: -value, meta, - ty: ty.clone(), + ty: *ty, }) } KclValue::Plane { value } => { @@ -1329,7 +1329,7 @@ impl Node { .map(|v| match v { KclValue::Number { value, ty, meta } => Ok(KclValue::Number { value: *value * -1.0, - ty: ty.clone(), + ty: *ty, meta: meta.clone(), }), _ => Err(err()), @@ -1350,7 +1350,7 @@ impl Node { .map(|v| match v { KclValue::Number { value, ty, meta } => Ok(KclValue::Number { value: *value * -1.0, - ty: ty.clone(), + ty: *ty, meta: meta.clone(), }), _ => Err(err()), @@ -1544,7 +1544,7 @@ impl Node { .into_iter() .map(|num| KclValue::Number { value: num as f64, - ty: start_ty.clone(), + ty: start_ty, meta: meta.clone(), }) .collect(), diff --git a/rust/kcl-lib/src/execution/geometry.rs b/rust/kcl-lib/src/execution/geometry.rs index 2a091be4a..4745c6e94 100644 --- a/rust/kcl-lib/src/execution/geometry.rs +++ b/rust/kcl-lib/src/execution/geometry.rs @@ -939,6 +939,7 @@ impl From for Point3D { Self { x: p.x, y: p.y, z: p.z } } } + impl From for kittycad_modeling_cmds::shared::Point3d { fn from(p: Point3d) -> Self { Self { @@ -1004,12 +1005,12 @@ pub struct BasePath { impl BasePath { pub fn get_to(&self) -> [TyF64; 2] { let ty: NumericType = self.units.into(); - [TyF64::new(self.to[0], ty.clone()), TyF64::new(self.to[1], ty)] + [TyF64::new(self.to[0], ty), TyF64::new(self.to[1], ty)] } pub fn get_from(&self) -> [TyF64; 2] { let ty: NumericType = self.units.into(); - [TyF64::new(self.from[0], ty.clone()), TyF64::new(self.from[1], ty)] + [TyF64::new(self.from[0], ty), TyF64::new(self.from[1], ty)] } } @@ -1225,14 +1226,14 @@ impl Path { pub fn get_from(&self) -> [TyF64; 2] { let p = &self.get_base().from; let ty: NumericType = self.get_base().units.into(); - [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)] + [TyF64::new(p[0], ty), TyF64::new(p[1], ty)] } /// Where does this path segment end? pub fn get_to(&self) -> [TyF64; 2] { let p = &self.get_base().to; let ty: NumericType = self.get_base().units.into(); - [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)] + [TyF64::new(p[0], ty), TyF64::new(p[1], ty)] } /// The path segment start point and its type. diff --git a/rust/kcl-lib/src/execution/kcl_value.rs b/rust/kcl-lib/src/execution/kcl_value.rs index 71c2dba66..9f9a07451 100644 --- a/rust/kcl-lib/src/execution/kcl_value.rs +++ b/rust/kcl-lib/src/execution/kcl_value.rs @@ -415,15 +415,41 @@ impl KclValue { /// Put the point into a KCL value. pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec) -> Self { + let [x, y] = p; Self::Tuple { value: vec![ Self::Number { - value: p[0], + value: x, meta: meta.clone(), - ty: ty.clone(), + ty, }, Self::Number { - value: p[1], + value: y, + meta: meta.clone(), + ty, + }, + ], + meta, + } + } + + /// Put the point into a KCL value. + pub fn from_point3d(p: [f64; 3], ty: NumericType, meta: Vec) -> Self { + let [x, y, z] = p; + Self::Tuple { + value: vec![ + Self::Number { + value: x, + meta: meta.clone(), + ty, + }, + Self::Number { + value: y, + meta: meta.clone(), + ty, + }, + Self::Number { + value: z, meta: meta.clone(), ty, }, @@ -448,7 +474,7 @@ impl KclValue { pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> { match self { - KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, ty.clone())), + KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, *ty)), _ => None, } } @@ -562,7 +588,7 @@ impl KclValue { pub fn as_ty_f64(&self) -> Option { match self { - KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, ty.clone())), + KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)), _ => None, } } diff --git a/rust/kcl-lib/src/execution/types.rs b/rust/kcl-lib/src/execution/types.rs index 5546a6f88..6ea937ab5 100644 --- a/rust/kcl-lib/src/execution/types.rs +++ b/rust/kcl-lib/src/execution/types.rs @@ -460,7 +460,7 @@ impl fmt::Display for PrimitiveType { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)] #[ts(export)] #[serde(tag = "type")] pub enum NumericType { @@ -575,7 +575,7 @@ impl NumericType { match (&ty, &i.ty) { (Any, Default { .. }) if i.n == 0.0 => {} (Any, t) => { - ty = t.clone(); + ty = *t; } (_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown), @@ -598,7 +598,7 @@ impl NumericType { } if ty == Any && !input.is_empty() { - ty = input[0].ty.clone(); + ty = input[0].ty; } (result, ty) @@ -722,7 +722,7 @@ impl NumericType { if ty.subtype(self) { return Ok(KclValue::Number { value: *value, - ty: ty.clone(), + ty: *ty, meta: meta.clone(), }); } @@ -736,7 +736,7 @@ impl NumericType { (Any, _) => Ok(KclValue::Number { value: *value, - ty: self.clone(), + ty: *self, meta: meta.clone(), }), @@ -744,7 +744,7 @@ impl NumericType { // means accept any number rather than force the current default. (_, Default { .. }) => Ok(KclValue::Number { value: *value, - ty: ty.clone(), + ty: *ty, meta: meta.clone(), }), @@ -1491,7 +1491,7 @@ impl KclValue { pub fn principal_type(&self) -> Option { match self { KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)), - KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))), + KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))), KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)), KclValue::Object { value, .. } => { let properties = value diff --git a/rust/kcl-lib/src/simulation_tests.rs b/rust/kcl-lib/src/simulation_tests.rs index 776e364ae..40a507ba0 100644 --- a/rust/kcl-lib/src/simulation_tests.rs +++ b/rust/kcl-lib/src/simulation_tests.rs @@ -3632,3 +3632,24 @@ mod non_english_identifiers { super::execute(TEST_NAME, true).await } } +mod plane_of { + const TEST_NAME: &str = "plane_of"; + + /// Test parsing KCL. + #[test] + fn parse() { + super::parse(TEST_NAME) + } + + /// Test that parsing and unparsing KCL produces the original KCL input. + #[tokio::test(flavor = "multi_thread")] + async fn unparse() { + super::unparse(TEST_NAME).await + } + + /// Test that KCL is executed correctly. + #[tokio::test(flavor = "multi_thread")] + async fn kcl_test_execute() { + super::execute(TEST_NAME, true).await + } +} diff --git a/rust/kcl-lib/src/std/args.rs b/rust/kcl-lib/src/std/args.rs index 4aaecbee3..087a6220c 100644 --- a/rust/kcl-lib/src/std/args.rs +++ b/rust/kcl-lib/src/std/args.rs @@ -340,12 +340,12 @@ impl Args { let x = KclValue::Number { value: p[0], meta: vec![meta], - ty: ty.clone(), + ty, }; let y = KclValue::Number { value: p[1], meta: vec![meta], - ty: ty.clone(), + ty, }; let ty = RuntimeType::Primitive(PrimitiveType::Number(ty)); @@ -1038,7 +1038,7 @@ impl<'a> FromKclValue<'a> for u64 { impl<'a> FromKclValue<'a> for TyF64 { fn from_kcl_val(arg: &'a KclValue) -> Option { match arg { - KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, ty.clone())), + KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)), _ => None, } } diff --git a/rust/kcl-lib/src/std/mod.rs b/rust/kcl-lib/src/std/mod.rs index 4c124fcbf..152cd4be1 100644 --- a/rust/kcl-lib/src/std/mod.rs +++ b/rust/kcl-lib/src/std/mod.rs @@ -256,6 +256,10 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp |e, a| Box::pin(crate::std::shapes::circle(e, a)), StdFnProps::default("std::sketch::circle"), ), + ("sketch", "planeOf") => ( + |e, a| Box::pin(crate::std::planes::plane_of(e, a)), + StdFnProps::default("std::sketch::planeOf"), + ), ("sketch", "extrude") => ( |e, a| Box::pin(crate::std::extrude::extrude(e, a)), StdFnProps::default("std::sketch::extrude").include_in_feature_tree(), diff --git a/rust/kcl-lib/src/std/patterns.rs b/rust/kcl-lib/src/std/patterns.rs index 454385f83..b1807553b 100644 --- a/rust/kcl-lib/src/std/patterns.rs +++ b/rust/kcl-lib/src/std/patterns.rs @@ -408,7 +408,7 @@ impl GeometryTrait for Sketch { exec_state: &mut ExecState, ) -> Result<[TyF64; 3], KclError> { let [x, y] = array_to_point2d(val, source_ranges, exec_state)?; - let ty = x.ty.clone(); + let ty = x.ty; Ok([x, y, TyF64::new(0.0, ty)]) } diff --git a/rust/kcl-lib/src/std/planes.rs b/rust/kcl-lib/src/std/planes.rs index 59389196e..25fe4cce3 100644 --- a/rust/kcl-lib/src/std/planes.rs +++ b/rust/kcl-lib/src/std/planes.rs @@ -1,15 +1,123 @@ //! Standard library plane helpers. use kcmc::{ModelingCmd, each_cmd as mcmd, length_unit::LengthUnit, shared::Color}; -use kittycad_modeling_cmds as kcmc; +use kittycad_modeling_cmds::{self as kcmc, ok_response::OkModelingCmdResponse, websocket::OkWebSocketResponseData}; -use super::{args::TyF64, sketch::PlaneData}; +use super::{ + args::TyF64, + sketch::{FaceTag, PlaneData}, +}; use crate::{ - errors::KclError, - execution::{ExecState, KclValue, ModelingCmdMeta, Plane, PlaneType, types::RuntimeType}, + UnitLen, + errors::{KclError, KclErrorDetails}, + execution::{ExecState, KclValue, Metadata, ModelingCmdMeta, Plane, PlaneType, types::RuntimeType}, std::Args, }; +/// Find the plane of a given face. +pub async fn plane_of(exec_state: &mut ExecState, args: Args) -> Result { + let solid = args.get_unlabeled_kw_arg("solid", &RuntimeType::solid(), exec_state)?; + let face = args.get_kw_arg("face", &RuntimeType::tagged_face(), exec_state)?; + + inner_plane_of(solid, face, exec_state, &args) + .await + .map(Box::new) + .map(|value| KclValue::Plane { value }) +} + +async fn inner_plane_of( + solid: crate::execution::Solid, + face: FaceTag, + exec_state: &mut ExecState, + args: &Args, +) -> Result { + // Support mock execution + // Return an arbitrary (incorrect) plane and a non-fatal error. + if args.ctx.no_engine_commands().await { + let plane_id = exec_state.id_generator().next_uuid(); + exec_state.err(crate::CompilationError { + source_range: args.source_range, + message: "The engine isn't available, so returning an arbitrary incorrect plane".to_owned(), + suggestion: None, + severity: crate::errors::Severity::Error, + tag: crate::errors::Tag::None, + }); + return Ok(Plane { + artifact_id: plane_id.into(), + id: plane_id, + // Engine doesn't know about the ID we created, so set this to Uninit. + value: PlaneType::Uninit, + info: crate::execution::PlaneInfo { + origin: Default::default(), + x_axis: Default::default(), + y_axis: Default::default(), + }, + meta: vec![Metadata { + source_range: args.source_range, + }], + }); + } + + // Query the engine to learn what plane, if any, this face is on. + let face_id = face.get_face_id(&solid, exec_state, args, true).await?; + let meta = args.into(); + let cmd = ModelingCmd::FaceIsPlanar(mcmd::FaceIsPlanar { object_id: face_id }); + let plane_resp = exec_state.send_modeling_cmd(meta, cmd).await?; + let OkWebSocketResponseData::Modeling { + modeling_response: OkModelingCmdResponse::FaceIsPlanar(planar), + } = plane_resp + else { + return Err(KclError::new_semantic(KclErrorDetails::new( + format!( + "Engine returned invalid response, it should have returned FaceIsPlanar but it returned {plane_resp:#?}" + ), + vec![args.source_range], + ))); + }; + // Destructure engine's response to check if the face was on a plane. + let not_planar: Result<_, KclError> = Err(KclError::new_semantic(KclErrorDetails::new( + "The face you provided doesn't lie on any plane. It might be curved.".to_owned(), + vec![args.source_range], + ))); + let Some(x_axis) = planar.x_axis else { return not_planar }; + let Some(y_axis) = planar.y_axis else { return not_planar }; + let Some(origin) = planar.origin else { return not_planar }; + + // Engine always returns measurements in mm. + let engine_units = UnitLen::Mm; + let x_axis = crate::execution::Point3d { + x: x_axis.x, + y: x_axis.y, + z: x_axis.z, + units: engine_units, + }; + let y_axis = crate::execution::Point3d { + x: y_axis.x, + y: y_axis.y, + z: y_axis.z, + units: engine_units, + }; + let origin = crate::execution::Point3d { + x: origin.x.0, + y: origin.y.0, + z: origin.z.0, + units: engine_units, + }; + + // Engine doesn't send back an ID, so let's just make a new plane ID. + let plane_id = exec_state.id_generator().next_uuid(); + Ok(Plane { + artifact_id: plane_id.into(), + id: plane_id, + // Engine doesn't know about the ID we created, so set this to Uninit. + value: PlaneType::Uninit, + info: crate::execution::PlaneInfo { origin, x_axis, y_axis }, + meta: vec![Metadata { + source_range: args.source_range, + }], + }) +} + /// Offset a plane by a distance along its normal. pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result { let std_plane = args.get_unlabeled_kw_arg("plane", &RuntimeType::plane(), exec_state)?; diff --git a/rust/kcl-lib/src/std/segment.rs b/rust/kcl-lib/src/std/segment.rs index 42d40bf42..02b873c79 100644 --- a/rust/kcl-lib/src/std/segment.rs +++ b/rust/kcl-lib/src/std/segment.rs @@ -18,7 +18,7 @@ pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result Result<[TyF64; 2], KclError> { @@ -31,7 +31,7 @@ fn inner_segment_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args })?; let (p, ty) = path.end_point_components(); // Docs generation isn't smart enough to handle ([f64; 2], NumericType). - let point = [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)]; + let point = [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]; Ok(point) } @@ -81,7 +81,7 @@ pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result Result<[TyF64; 2], KclError> { @@ -94,7 +94,7 @@ fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar })?; let (p, ty) = path.start_point_components(); // Docs generation isn't smart enough to handle ([f64; 2], NumericType). - let point = [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)]; + let point = [TyF64::new(p[0], ty), TyF64::new(p[1], ty)]; Ok(point) } diff --git a/rust/kcl-lib/src/std/shapes.rs b/rust/kcl-lib/src/std/shapes.rs index 74685e392..5518f228b 100644 --- a/rust/kcl-lib/src/std/shapes.rs +++ b/rust/kcl-lib/src/std/shapes.rs @@ -71,7 +71,7 @@ async fn inner_circle( let radius = get_radius(radius, diameter, args.source_range)?; let from = [center_u[0] + radius.to_length_units(units), center_u[1]]; - let from_t = [TyF64::new(from[0], ty.clone()), TyF64::new(from[1], ty)]; + let from_t = [TyF64::new(from[0], ty), TyF64::new(from[1], ty)]; let sketch = crate::std::sketch::inner_start_profile(sketch_surface, from_t, None, exec_state, args.clone()).await?; @@ -156,7 +156,7 @@ async fn inner_circle_three_point( exec_state: &mut ExecState, args: Args, ) -> Result { - let ty = p1[0].ty.clone(); + let ty = p1[0].ty; let units = ty.expect_length(); let p1 = point_to_len_unit(p1, units); @@ -172,10 +172,7 @@ async fn inner_circle_three_point( SketchOrSurface::Sketch(group) => group.on, }; - let from = [ - TyF64::new(center[0] + radius, ty.clone()), - TyF64::new(center[1], ty.clone()), - ]; + let from = [TyF64::new(center[0] + radius, ty), TyF64::new(center[1], ty)]; let sketch = crate::std::sketch::inner_start_profile(sketch_surface, from.clone(), None, exec_state, args.clone()).await?; diff --git a/rust/kcl-lib/src/std/sketch.rs b/rust/kcl-lib/src/std/sketch.rs index cd09df518..8b67c7096 100644 --- a/rust/kcl-lib/src/std/sketch.rs +++ b/rust/kcl-lib/src/std/sketch.rs @@ -599,7 +599,7 @@ async fn inner_angled_line_of_x_length( } let to = get_y_component(Angle::from_degrees(angle_degrees), length.n); - let to = [TyF64::new(to[0], length.ty.clone()), TyF64::new(to[1], length.ty)]; + let to = [TyF64::new(to[0], length.ty), TyF64::new(to[1], length.ty)]; let new_sketch = straight_line(StraightLineParams::relative(to, sketch, tag), exec_state, args).await?; @@ -666,7 +666,7 @@ async fn inner_angled_line_of_y_length( } let to = get_x_component(Angle::from_degrees(angle_degrees), length.n); - let to = [TyF64::new(to[0], length.ty.clone()), TyF64::new(to[1], length.ty)]; + let to = [TyF64::new(to[0], length.ty), TyF64::new(to[1], length.ty)]; let new_sketch = straight_line(StraightLineParams::relative(to, sketch, tag), exec_state, args).await?; diff --git a/rust/kcl-lib/std/sketch.kcl b/rust/kcl-lib/std/sketch.kcl index 9c1e59b67..864b0246e 100644 --- a/rust/kcl-lib/std/sketch.kcl +++ b/rust/kcl-lib/std/sketch.kcl @@ -1941,3 +1941,24 @@ export fn subtract2d( /// The shape(s) which should be cut out of the sketch. tool: [Sketch; 1+], ): Sketch {} + +/// Find the plane a face lies on. +/// Returns an error if the face doesn't lie on any plane (for example, the curved face of a cylinder) +///```kcl +/// triangle = startSketchOn(XY) +/// |> polygon(radius = 3, numSides = 3, center = [0, 0]) +/// |> extrude(length = 2) +/// +/// // Find the plane of the triangle's top face. +/// topPlane = planeOf(triangle, face = END) +/// +/// // Create a new plane, 10 units above the triangle's top face. +/// startSketchOn(offsetPlane(topPlane, offset = 10)) +/// ``` +@(impl = std_rust) +export fn planeOf( + /// The solid whose face is being queried. + @solid: Solid, + /// Find the plane which this face lies on. + face: TaggedFace, +): Plane {} diff --git a/rust/kcl-lib/tests/outputs/serial_test_example_fn_std-sketch-planeOf0.png b/rust/kcl-lib/tests/outputs/serial_test_example_fn_std-sketch-planeOf0.png new file mode 100644 index 0000000000000000000000000000000000000000..df07af62749bad3358ed88ae23f17f80daead70a GIT binary patch literal 19940 zcmeHP+e?#S9G*p4f-I2`y4W%U!;3C5>7v{)6|+bxG>JSx7t%omO=p@e5*aJQi^vFR z%><2x8&Xsr+9E|ec!JOop|fb_0VkT`{B-;J)GmI%+#e9~z}UvvyYG8{`@PTiJkR@V z`ZGxhfdP90WHMP`;)%F(GTAoi>6U>V+ogy8vf=YG*=bW^TugGY-}Jk!#{Cy+x@JX< z>)wZ+#{5&4%sJm){k>hTGSr;37!F15b1fCUzEaYlJ9>4{q8g8p|9EaPw@z-n6V&=o zDXHm&gB$M_w7Vbp9@IK1Mnw1?R5&Q6B>Oy)bac`5EX~)Eq}RmMvn@VOZ}pVDmZ$kX zPH**tJel#%jdNO?r(nB-iIIxp8)NN{RwTWFHO6|wWCnO};0JiH1_5{gJit6C0R&g1 z)c^_R)d0Kast2-#sZD?fUoKG4Y`MT1VD1ON1Ko?yFsZK!31~!JT_M{M{Ex%Z4N2TF;r{aI;}g0gqQ?d z70nWfjc@y=+WSh^-ls`#FP<$@jCSgSk7zx1t!H^rCnF(z07pr&5<}FjLbf{O_GF;9K*W==Y>bWrW+}Fin zjiZSqv?RV%GEkxDBr9Lkq!;u?$;+imHQUFQ+)WOlX=aRMu*A>Oi21#wdfckHf1tZs z`HK!vuAhoxWz^qx*!Uv5e}om=&VpfLWuDNGS0Y5&(|%_eCSp}TV-@+1vc+LG=3x+> zh4_B$-HOoy^vgFM&Lc23WA%}D>=CQFP@iZdV2y8Q<}d&Nlr literal 0 HcmV?d00001 diff --git a/rust/kcl-lib/tests/plane_of/artifact_commands.snap b/rust/kcl-lib/tests/plane_of/artifact_commands.snap new file mode 100644 index 000000000..8a38a1a13 --- /dev/null +++ b/rust/kcl-lib/tests/plane_of/artifact_commands.snap @@ -0,0 +1,381 @@ +--- +source: kcl-lib/src/simulation_tests.rs +description: Artifact commands plane_of.kcl +--- +{ + "rust/kcl-lib/tests/plane_of/input.kcl": [ + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "make_plane", + "origin": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "x_axis": { + "x": 1.0, + "y": 0.0, + "z": 0.0 + }, + "y_axis": { + "x": 0.0, + "y": 1.0, + "z": 0.0 + }, + "size": 60.0, + "clobber": false, + "hide": true + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "enable_sketch_mode", + "entity_id": "[uuid]", + "ortho": false, + "animated": false, + "adjust_camera": false, + "planar_normal": { + "x": 0.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "start_path" + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "move_path_pen", + "path": "[uuid]", + "to": { + "x": 2743.2, + "y": 0.0, + "z": 0.0 + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "sketch_mode_disable" + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "extend_path", + "path": "[uuid]", + "segment": { + "type": "line", + "end": { + "x": -1371.5999999999995, + "y": 2375.680887661472, + "z": 0.0 + }, + "relative": false + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "extend_path", + "path": "[uuid]", + "segment": { + "type": "line", + "end": { + "x": -1371.6000000000013, + "y": -2375.680887661472, + "z": 0.0 + }, + "relative": false + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "extend_path", + "path": "[uuid]", + "segment": { + "type": "line", + "end": { + "x": 2743.2, + "y": 0.0, + "z": 0.0 + }, + "relative": false + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "close_path", + "path_id": "[uuid]" + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "enable_sketch_mode", + "entity_id": "[uuid]", + "ortho": false, + "animated": false, + "adjust_camera": false, + "planar_normal": { + "x": 0.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "extrude", + "target": "[uuid]", + "distance": 1828.8, + "faces": null, + "opposite": "None" + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "sketch_mode_disable" + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "object_bring_to_front", + "object_id": "[uuid]" + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "solid3d_get_extrusion_face_info", + "object_id": "[uuid]", + "edge_id": "[uuid]" + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "solid3d_get_adjacency_info", + "object_id": "[uuid]", + "edge_id": "[uuid]" + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "face_is_planar", + "object_id": "[uuid]" + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "make_plane", + "origin": { + "x": 0.0, + "y": 0.0, + "z": 3657.6 + }, + "x_axis": { + "x": 1.0, + "y": 0.0, + "z": 0.0 + }, + "y_axis": { + "x": 0.0, + "y": 1.0, + "z": 0.0 + }, + "size": 100.0, + "clobber": false, + "hide": false + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "plane_set_color", + "plane_id": "[uuid]", + "color": { + "r": 0.6, + "g": 0.6, + "b": 0.6, + "a": 0.3 + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "object_visible", + "object_id": "[uuid]", + "hidden": true + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "enable_sketch_mode", + "entity_id": "[uuid]", + "ortho": false, + "animated": false, + "adjust_camera": false, + "planar_normal": { + "x": 0.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "start_path" + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "move_path_pen", + "path": "[uuid]", + "to": { + "x": 1828.8, + "y": 0.0, + "z": 0.0 + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "sketch_mode_disable" + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "extend_path", + "path": "[uuid]", + "segment": { + "type": "line", + "end": { + "x": 0.00000000000011198170331403397, + "y": 1828.8, + "z": 0.0 + }, + "relative": false + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "extend_path", + "path": "[uuid]", + "segment": { + "type": "line", + "end": { + "x": -1828.8, + "y": 0.00000000000022396340662806795, + "z": 0.0 + }, + "relative": false + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "extend_path", + "path": "[uuid]", + "segment": { + "type": "line", + "end": { + "x": -0.0000000000003359451099421019, + "y": -1828.8, + "z": 0.0 + }, + "relative": false + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "extend_path", + "path": "[uuid]", + "segment": { + "type": "line", + "end": { + "x": 1828.8, + "y": 0.0, + "z": 0.0 + }, + "relative": false + } + } + }, + { + "cmdId": "[uuid]", + "range": [], + "command": { + "type": "close_path", + "path_id": "[uuid]" + } + } + ], + "std::appearance": [], + "std::array": [], + "std::math": [], + "std::prelude": [], + "std::sketch": [], + "std::solid": [], + "std::sweep": [], + "std::transform": [], + "std::turns": [], + "std::types": [], + "std::units": [] +} diff --git a/rust/kcl-lib/tests/plane_of/artifact_graph_flowchart.snap b/rust/kcl-lib/tests/plane_of/artifact_graph_flowchart.snap new file mode 100644 index 000000000..4d1e0089e --- /dev/null +++ b/rust/kcl-lib/tests/plane_of/artifact_graph_flowchart.snap @@ -0,0 +1,6 @@ +--- +source: kcl-lib/src/simulation_tests.rs +description: Artifact graph flowchart plane_of.kcl +extension: md +snapshot_kind: binary +--- diff --git a/rust/kcl-lib/tests/plane_of/artifact_graph_flowchart.snap.md b/rust/kcl-lib/tests/plane_of/artifact_graph_flowchart.snap.md new file mode 100644 index 000000000..17cc9b7e7 --- /dev/null +++ b/rust/kcl-lib/tests/plane_of/artifact_graph_flowchart.snap.md @@ -0,0 +1,105 @@ +```mermaid +flowchart LR + subgraph path2 [Path] + 2["Path
[64, 114, 0]"] + %% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }] + 3["Segment
[64, 114, 0]"] + %% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }] + 4["Segment
[64, 114, 0]"] + %% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }] + 5["Segment
[64, 114, 0]"] + %% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }] + 6["Segment
[64, 114, 0]"] + %% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }] + 7[Solid2d] + end + subgraph path21 [Path] + 21["Path
[311, 361, 0]"] + %% [ProgramBodyItem { index: 2 }, ExpressionStatementExpr, PipeBodyItem { index: 1 }] + 22["Segment
[311, 361, 0]"] + %% [ProgramBodyItem { index: 2 }, ExpressionStatementExpr, PipeBodyItem { index: 1 }] + 23["Segment
[311, 361, 0]"] + %% [ProgramBodyItem { index: 2 }, ExpressionStatementExpr, PipeBodyItem { index: 1 }] + 24["Segment
[311, 361, 0]"] + %% [ProgramBodyItem { index: 2 }, ExpressionStatementExpr, PipeBodyItem { index: 1 }] + 25["Segment
[311, 361, 0]"] + %% [ProgramBodyItem { index: 2 }, ExpressionStatementExpr, PipeBodyItem { index: 1 }] + 26["Segment
[311, 361, 0]"] + %% [ProgramBodyItem { index: 2 }, ExpressionStatementExpr, PipeBodyItem { index: 1 }] + 27[Solid2d] + end + 1["Plane
[41, 58, 0]"] + %% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }] + 8["Sweep Extrusion
[120, 139, 0]"] + %% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 2 }] + 9[Wall] + %% face_code_ref=Missing NodePath + 10[Wall] + %% face_code_ref=Missing NodePath + 11[Wall] + %% face_code_ref=Missing NodePath + 12["Cap Start"] + %% face_code_ref=Missing NodePath + 13["Cap End"] + %% face_code_ref=Missing NodePath + 14["SweepEdge Opposite"] + 15["SweepEdge Adjacent"] + 16["SweepEdge Opposite"] + 17["SweepEdge Adjacent"] + 18["SweepEdge Opposite"] + 19["SweepEdge Adjacent"] + 20["Plane
[277, 304, 0]"] + %% [ProgramBodyItem { index: 2 }, ExpressionStatementExpr, PipeBodyItem { index: 0 }, CallKwUnlabeledArg] + 28["StartSketchOnPlane
[263, 305, 0]"] + %% [ProgramBodyItem { index: 2 }, ExpressionStatementExpr, PipeBodyItem { index: 0 }] + 1 --- 2 + 2 --- 3 + 2 --- 4 + 2 --- 5 + 2 --- 6 + 2 --- 7 + 2 ---- 8 + 3 --- 9 + 3 x--> 12 + 3 --- 14 + 3 --- 15 + 4 --- 10 + 4 x--> 12 + 4 --- 16 + 4 --- 17 + 5 --- 11 + 5 x--> 12 + 5 --- 18 + 5 --- 19 + 8 --- 9 + 8 --- 10 + 8 --- 11 + 8 --- 12 + 8 --- 13 + 8 --- 14 + 8 --- 15 + 8 --- 16 + 8 --- 17 + 8 --- 18 + 8 --- 19 + 9 --- 14 + 9 --- 15 + 19 <--x 9 + 15 <--x 10 + 10 --- 16 + 10 --- 17 + 17 <--x 11 + 11 --- 18 + 11 --- 19 + 14 <--x 13 + 16 <--x 13 + 18 <--x 13 + 20 --- 21 + 20 <--x 28 + 21 --- 22 + 21 --- 23 + 21 --- 24 + 21 --- 25 + 21 --- 26 + 21 --- 27 +``` diff --git a/rust/kcl-lib/tests/plane_of/ast.snap b/rust/kcl-lib/tests/plane_of/ast.snap new file mode 100644 index 000000000..7db45f037 --- /dev/null +++ b/rust/kcl-lib/tests/plane_of/ast.snap @@ -0,0 +1,705 @@ +--- +source: kcl-lib/src/simulation_tests.rs +description: Result of parsing plane_of.kcl +--- +{ + "Ok": { + "body": [ + { + "commentStart": 0, + "declaration": { + "commentStart": 0, + "end": 0, + "id": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "tri", + "start": 0, + "type": "Identifier" + }, + "init": { + "body": [ + { + "arguments": [], + "callee": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "startSketchOn", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name" + }, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "start": 0, + "type": "CallExpressionKw", + "type": "CallExpressionKw", + "unlabeled": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "XY", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name", + "type": "Name" + } + }, + { + "arguments": [ + { + "type": "LabeledArg", + "label": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "radius", + "start": 0, + "type": "Identifier" + }, + "arg": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "raw": "3", + "start": 0, + "type": "Literal", + "type": "Literal", + "value": { + "value": 3.0, + "suffix": "None" + } + } + }, + { + "type": "LabeledArg", + "label": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "numSides", + "start": 0, + "type": "Identifier" + }, + "arg": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "raw": "3", + "start": 0, + "type": "Literal", + "type": "Literal", + "value": { + "value": 3.0, + "suffix": "None" + } + } + }, + { + "type": "LabeledArg", + "label": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "center", + "start": 0, + "type": "Identifier" + }, + "arg": { + "commentStart": 0, + "elements": [ + { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "raw": "0", + "start": 0, + "type": "Literal", + "type": "Literal", + "value": { + "value": 0.0, + "suffix": "None" + } + }, + { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "raw": "0", + "start": 0, + "type": "Literal", + "type": "Literal", + "value": { + "value": 0.0, + "suffix": "None" + } + } + ], + "end": 0, + "moduleId": 0, + "start": 0, + "type": "ArrayExpression", + "type": "ArrayExpression" + } + } + ], + "callee": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "polygon", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name" + }, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "start": 0, + "type": "CallExpressionKw", + "type": "CallExpressionKw", + "unlabeled": null + }, + { + "arguments": [ + { + "type": "LabeledArg", + "label": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "length", + "start": 0, + "type": "Identifier" + }, + "arg": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "raw": "2", + "start": 0, + "type": "Literal", + "type": "Literal", + "value": { + "value": 2.0, + "suffix": "None" + } + } + } + ], + "callee": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "extrude", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name" + }, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "start": 0, + "type": "CallExpressionKw", + "type": "CallExpressionKw", + "unlabeled": null + } + ], + "commentStart": 0, + "end": 0, + "moduleId": 0, + "nonCodeMeta": { + "nonCodeNodes": { + "2": [ + { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "start": 0, + "type": "NonCodeNode", + "value": { + "type": "newLineBlockComment", + "value": "Get the plane which `tri` ends on.", + "style": "line" + } + } + ] + }, + "startNodes": [] + }, + "start": 0, + "type": "PipeExpression", + "type": "PipeExpression" + }, + "moduleId": 0, + "start": 0, + "type": "VariableDeclarator" + }, + "end": 0, + "kind": "const", + "moduleId": 0, + "start": 0, + "type": "VariableDeclaration", + "type": "VariableDeclaration" + }, + { + "commentStart": 0, + "declaration": { + "commentStart": 0, + "end": 0, + "id": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "p0", + "start": 0, + "type": "Identifier" + }, + "init": { + "arguments": [ + { + "type": "LabeledArg", + "label": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "face", + "start": 0, + "type": "Identifier" + }, + "arg": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "END", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name", + "type": "Name" + } + } + ], + "callee": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "planeOf", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name" + }, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "start": 0, + "type": "CallExpressionKw", + "type": "CallExpressionKw", + "unlabeled": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "tri", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name", + "type": "Name" + } + }, + "moduleId": 0, + "start": 0, + "type": "VariableDeclarator" + }, + "end": 0, + "kind": "const", + "moduleId": 0, + "start": 0, + "type": "VariableDeclaration", + "type": "VariableDeclaration" + }, + { + "commentStart": 0, + "end": 0, + "expression": { + "body": [ + { + "arguments": [], + "callee": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "startSketchOn", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name" + }, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "start": 0, + "type": "CallExpressionKw", + "type": "CallExpressionKw", + "unlabeled": { + "arguments": [ + { + "type": "LabeledArg", + "label": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "offset", + "start": 0, + "type": "Identifier" + }, + "arg": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "raw": "2", + "start": 0, + "type": "Literal", + "type": "Literal", + "value": { + "value": 2.0, + "suffix": "None" + } + } + } + ], + "callee": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "offsetPlane", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name" + }, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "start": 0, + "type": "CallExpressionKw", + "type": "CallExpressionKw", + "unlabeled": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "p0", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name", + "type": "Name" + } + } + }, + { + "arguments": [ + { + "type": "LabeledArg", + "label": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "radius", + "start": 0, + "type": "Identifier" + }, + "arg": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "raw": "2", + "start": 0, + "type": "Literal", + "type": "Literal", + "value": { + "value": 2.0, + "suffix": "None" + } + } + }, + { + "type": "LabeledArg", + "label": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "numSides", + "start": 0, + "type": "Identifier" + }, + "arg": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "raw": "4", + "start": 0, + "type": "Literal", + "type": "Literal", + "value": { + "value": 4.0, + "suffix": "None" + } + } + }, + { + "type": "LabeledArg", + "label": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "center", + "start": 0, + "type": "Identifier" + }, + "arg": { + "commentStart": 0, + "elements": [ + { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "raw": "0", + "start": 0, + "type": "Literal", + "type": "Literal", + "value": { + "value": 0.0, + "suffix": "None" + } + }, + { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "raw": "0", + "start": 0, + "type": "Literal", + "type": "Literal", + "value": { + "value": 0.0, + "suffix": "None" + } + } + ], + "end": 0, + "moduleId": 0, + "start": 0, + "type": "ArrayExpression", + "type": "ArrayExpression" + } + } + ], + "callee": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "polygon", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name" + }, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "start": 0, + "type": "CallExpressionKw", + "type": "CallExpressionKw", + "unlabeled": null + } + ], + "commentStart": 0, + "end": 0, + "moduleId": 0, + "start": 0, + "type": "PipeExpression", + "type": "PipeExpression" + }, + "moduleId": 0, + "preComments": [ + "", + "", + "// Offset that plane by 2, then draw a square on it." + ], + "start": 0, + "type": "ExpressionStatement", + "type": "ExpressionStatement" + } + ], + "commentStart": 0, + "end": 0, + "innerAttrs": [ + { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "settings", + "start": 0, + "type": "Identifier" + }, + "properties": [ + { + "commentStart": 0, + "end": 0, + "key": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "defaultLengthUnit", + "start": 0, + "type": "Identifier" + }, + "moduleId": 0, + "start": 0, + "type": "ObjectProperty", + "value": { + "abs_path": false, + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "name": "yd", + "start": 0, + "type": "Identifier" + }, + "path": [], + "start": 0, + "type": "Name", + "type": "Name" + } + } + ], + "start": 0, + "type": "Annotation" + } + ], + "moduleId": 0, + "nonCodeMeta": { + "nonCodeNodes": { + "2": [ + { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "start": 0, + "type": "NonCodeNode", + "value": { + "type": "newLine" + } + } + ] + }, + "startNodes": [ + { + "commentStart": 0, + "end": 0, + "moduleId": 0, + "start": 0, + "type": "NonCodeNode", + "value": { + "type": "newLine" + } + } + ] + }, + "start": 0 + } +} diff --git a/rust/kcl-lib/tests/plane_of/input.kcl b/rust/kcl-lib/tests/plane_of/input.kcl new file mode 100644 index 000000000..bca0bfa3a --- /dev/null +++ b/rust/kcl-lib/tests/plane_of/input.kcl @@ -0,0 +1,13 @@ +@settings(defaultLengthUnit = yd) + +tri = startSketchOn(XY) + |> polygon(radius = 3, numSides = 3, center = [0, 0]) + |> extrude(length = 2) + +// Get the plane which `tri` ends on. +p0 = planeOf(tri, face = END) + +// Offset that plane by 2, then draw a square on it. +startSketchOn(offsetPlane(p0, offset = 2)) + |> polygon(radius = 2, numSides = 4, center = [0, 0]) + diff --git a/rust/kcl-lib/tests/plane_of/ops.snap b/rust/kcl-lib/tests/plane_of/ops.snap new file mode 100644 index 000000000..adfad681b --- /dev/null +++ b/rust/kcl-lib/tests/plane_of/ops.snap @@ -0,0 +1,254 @@ +--- +source: kcl-lib/src/simulation_tests.rs +description: Operations executed plane_of.kcl +--- +{ + "rust/kcl-lib/tests/plane_of/input.kcl": [ + { + "type": "StdLibCall", + "name": "startSketchOn", + "unlabeledArg": { + "value": { + "type": "Plane", + "artifact_id": "[uuid]" + }, + "sourceRange": [] + }, + "labeledArgs": {}, + "nodePath": { + "steps": [ + { + "type": "ProgramBodyItem", + "index": 0 + }, + { + "type": "VariableDeclarationDeclaration" + }, + { + "type": "VariableDeclarationInit" + }, + { + "type": "PipeBodyItem", + "index": 0 + } + ] + }, + "sourceRange": [] + }, + { + "type": "StdLibCall", + "name": "extrude", + "unlabeledArg": { + "value": { + "type": "Sketch", + "value": { + "artifactId": "[uuid]" + } + }, + "sourceRange": [] + }, + "labeledArgs": { + "length": { + "value": { + "type": "Number", + "value": 2.0, + "ty": { + "type": "Default", + "len": { + "type": "Yards" + }, + "angle": { + "type": "Degrees" + } + } + }, + "sourceRange": [] + } + }, + "nodePath": { + "steps": [ + { + "type": "ProgramBodyItem", + "index": 0 + }, + { + "type": "VariableDeclarationDeclaration" + }, + { + "type": "VariableDeclarationInit" + }, + { + "type": "PipeBodyItem", + "index": 2 + } + ] + }, + "sourceRange": [] + }, + { + "type": "StdLibCall", + "name": "offsetPlane", + "unlabeledArg": { + "value": { + "type": "Plane", + "artifact_id": "[uuid]" + }, + "sourceRange": [] + }, + "labeledArgs": { + "offset": { + "value": { + "type": "Number", + "value": 2.0, + "ty": { + "type": "Default", + "len": { + "type": "Yards" + }, + "angle": { + "type": "Degrees" + } + } + }, + "sourceRange": [] + } + }, + "nodePath": { + "steps": [ + { + "type": "ProgramBodyItem", + "index": 2 + }, + { + "type": "ExpressionStatementExpr" + }, + { + "type": "PipeBodyItem", + "index": 0 + }, + { + "type": "CallKwUnlabeledArg" + } + ] + }, + "sourceRange": [] + }, + { + "type": "StdLibCall", + "name": "startSketchOn", + "unlabeledArg": { + "value": { + "type": "Plane", + "artifact_id": "[uuid]" + }, + "sourceRange": [] + }, + "labeledArgs": {}, + "nodePath": { + "steps": [ + { + "type": "ProgramBodyItem", + "index": 2 + }, + { + "type": "ExpressionStatementExpr" + }, + { + "type": "PipeBodyItem", + "index": 0 + } + ] + }, + "sourceRange": [] + } + ], + "std::appearance": [], + "std::array": [], + "std::math": [ + { + "type": "VariableDeclaration", + "name": "PI", + "value": { + "type": "Number", + "value": 3.141592653589793, + "ty": { + "type": "Unknown" + } + }, + "visibility": "export", + "nodePath": { + "steps": [] + }, + "sourceRange": [] + }, + { + "type": "VariableDeclaration", + "name": "E", + "value": { + "type": "Number", + "value": 2.718281828459045, + "ty": { + "type": "Known", + "type": "Count" + } + }, + "visibility": "export", + "nodePath": { + "steps": [] + }, + "sourceRange": [] + }, + { + "type": "VariableDeclaration", + "name": "TAU", + "value": { + "type": "Number", + "value": 6.283185307179586, + "ty": { + "type": "Known", + "type": "Count" + } + }, + "visibility": "export", + "nodePath": { + "steps": [] + }, + "sourceRange": [] + } + ], + "std::prelude": [ + { + "type": "VariableDeclaration", + "name": "START", + "value": { + "type": "String", + "value": "start" + }, + "visibility": "export", + "nodePath": { + "steps": [] + }, + "sourceRange": [] + }, + { + "type": "VariableDeclaration", + "name": "END", + "value": { + "type": "String", + "value": "end" + }, + "visibility": "export", + "nodePath": { + "steps": [] + }, + "sourceRange": [] + } + ], + "std::sketch": [], + "std::solid": [], + "std::sweep": [], + "std::transform": [], + "std::turns": [], + "std::types": [], + "std::units": [] +} diff --git a/rust/kcl-lib/tests/plane_of/program_memory.snap b/rust/kcl-lib/tests/plane_of/program_memory.snap new file mode 100644 index 000000000..ddd84bc7c --- /dev/null +++ b/rust/kcl-lib/tests/plane_of/program_memory.snap @@ -0,0 +1,192 @@ +--- +source: kcl-lib/src/simulation_tests.rs +description: Variables in memory after executing plane_of.kcl +--- +{ + "p0": { + "type": "Plane", + "value": { + "artifactId": "[uuid]", + "id": "[uuid]", + "origin": { + "x": 0.0, + "y": 0.0, + "z": 1828.8, + "units": { + "type": "Mm" + } + }, + "value": "Uninit", + "xAxis": { + "x": 1.0, + "y": 0.0, + "z": 0.0, + "units": { + "type": "Mm" + } + }, + "yAxis": { + "x": 0.0, + "y": 1.0, + "z": 0.0, + "units": { + "type": "Mm" + } + } + } + }, + "tri": { + "type": "Solid", + "value": { + "type": "Solid", + "id": "[uuid]", + "artifactId": "[uuid]", + "value": [ + { + "faceId": "[uuid]", + "id": "[uuid]", + "sourceRange": [], + "tag": null, + "type": "extrudePlane" + }, + { + "faceId": "[uuid]", + "id": "[uuid]", + "sourceRange": [], + "tag": null, + "type": "extrudePlane" + }, + { + "faceId": "[uuid]", + "id": "[uuid]", + "sourceRange": [], + "tag": null, + "type": "extrudePlane" + } + ], + "sketch": { + "type": "Sketch", + "id": "[uuid]", + "paths": [ + { + "__geoMeta": { + "id": "[uuid]", + "sourceRange": [] + }, + "from": [ + 3.0, + 0.0 + ], + "tag": null, + "to": [ + -1.4999999999999993, + 2.598076211353316 + ], + "type": "ToPoint", + "units": { + "type": "Yards" + } + }, + { + "__geoMeta": { + "id": "[uuid]", + "sourceRange": [] + }, + "from": [ + -1.4999999999999993, + 2.598076211353316 + ], + "tag": null, + "to": [ + -1.5000000000000013, + -2.5980762113533156 + ], + "type": "ToPoint", + "units": { + "type": "Yards" + } + }, + { + "__geoMeta": { + "id": "[uuid]", + "sourceRange": [] + }, + "from": [ + -1.5000000000000013, + -2.5980762113533156 + ], + "tag": null, + "to": [ + 3.0, + 0.0 + ], + "type": "ToPoint", + "units": { + "type": "Yards" + } + } + ], + "on": { + "artifactId": "[uuid]", + "id": "[uuid]", + "origin": { + "x": 0.0, + "y": 0.0, + "z": 0.0, + "units": { + "type": "Mm" + } + }, + "type": "plane", + "value": "XY", + "xAxis": { + "x": 1.0, + "y": 0.0, + "z": 0.0, + "units": { + "type": "Unknown" + } + }, + "yAxis": { + "x": 0.0, + "y": 1.0, + "z": 0.0, + "units": { + "type": "Unknown" + } + } + }, + "start": { + "from": [ + 3.0, + 0.0 + ], + "to": [ + 3.0, + 0.0 + ], + "units": { + "type": "Yards" + }, + "tag": null, + "__geoMeta": { + "id": "[uuid]", + "sourceRange": [] + } + }, + "artifactId": "[uuid]", + "originalId": "[uuid]", + "units": { + "type": "Yards" + } + }, + "height": 2.0, + "startCapId": "[uuid]", + "endCapId": "[uuid]", + "units": { + "type": "Yards" + }, + "sectional": false + } + } +} diff --git a/rust/kcl-lib/tests/plane_of/rendered_model.png b/rust/kcl-lib/tests/plane_of/rendered_model.png new file mode 100644 index 0000000000000000000000000000000000000000..441e64b83669814edc99a141d17eacbf07a2c549 GIT binary patch literal 54480 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 polygon(radius = 3, numSides = 3, center = [0, 0]) + |> extrude(length = 2) + +// Get the plane which `tri` ends on. +p0 = planeOf(tri, face = END) + +// Offset that plane by 2, then draw a square on it. +startSketchOn(offsetPlane(p0, offset = 2)) + |> polygon(radius = 2, numSides = 4, center = [0, 0])