SketchOnFace UI (#1664)
* always enter edit mode * initial blocking of extra code-mirror updates * dry out code * rejig selections * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * clean up * stream clean up * update export * sketch mode can be entered and exited for extrude faces But has bugs * startSketchOn working in some cases, editsketch animation working but not orientation of instersection plane etc * Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" This reverts commit406fca4c55
. * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * remove comment * add sketch on face e2e test * tweenCamToNegYAxis should respect reduced motion * initial sketch on face working with test * remove temporary toolbar button and xState flow * un-used vars * snapshot test tweak * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * type tidy up * Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" This reverts commitc39b8ebf95
. * Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" This reverts commitfecf6f490a
. * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * rename * sketch on sketch on sketch * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * typo * startSketchOn Endcaps end works, start is weird still * clear selections for entity_ids that are not recognised * fix sketch on end cap of second order extrustion * tiny clean up * fix sketch on close segment/face * clean up 'lastCodeMirrorSelectionUpdatedFromScene' * add code mode test for sketchOnExtrudedFace * make end cap selection more robust * update js artifacts for extrudes * update kcl docs * clean up --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -76,6 +76,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -244,6 +246,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -76,6 +76,8 @@ startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -244,6 +246,8 @@ startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -77,6 +77,8 @@ startSketchOn('YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -245,6 +247,8 @@ startSketchOn('YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -82,6 +82,8 @@ const part001 = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -250,6 +252,8 @@ const part001 = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -76,6 +76,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -244,6 +246,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -75,6 +75,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -243,6 +245,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -86,6 +86,8 @@ startSketchOn('-YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -254,6 +256,8 @@ startSketchOn('-YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -79,6 +79,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -247,6 +249,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -71,6 +71,8 @@ const rectangle = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -135,6 +137,8 @@ const rectangle = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -302,6 +306,8 @@ const rectangle = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -72,6 +72,8 @@ startSketchOn('YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -240,6 +242,8 @@ startSketchOn('YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -67,6 +67,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -68,6 +68,8 @@ const square = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -236,6 +238,8 @@ const square = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -403,6 +407,8 @@ const square = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -66,6 +66,8 @@ startSketchOn("YZ")
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -66,6 +66,8 @@ startSketchOn("YZ")
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -66,6 +66,8 @@ startSketchOn('-XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -234,6 +236,8 @@ startSketchOn('-XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -72,6 +72,8 @@ const part = rectShape([0, 0], 20, 20)
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -240,6 +242,8 @@ const part = rectShape([0, 0], 20, 20)
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -80,6 +80,8 @@ const part = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -77,6 +77,8 @@ const part = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -67,6 +67,8 @@ startSketchOn("YZ")
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -67,6 +67,8 @@ startSketchOn("YZ")
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -67,6 +67,8 @@ startSketchOn("YZ")
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -57,6 +57,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -128,6 +130,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -65,6 +65,8 @@ startSketchAt([0, 0])
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -254,6 +254,8 @@ string
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -75,6 +75,8 @@ startSketchOn('-YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -243,6 +245,8 @@ startSketchOn('-YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -65,6 +65,8 @@ startSketchOn('-YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -233,6 +235,8 @@ startSketchOn('-YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -66,6 +66,8 @@ startSketchOn('YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -234,6 +236,8 @@ startSketchOn('YZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -66,6 +66,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -234,6 +236,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -66,6 +66,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -234,6 +236,8 @@ startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -67,6 +67,8 @@ startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
@ -235,6 +237,8 @@ startSketchOn('XZ')
|
|||||||
},
|
},
|
||||||
} |
|
} |
|
||||||
{
|
{
|
||||||
|
// the face id the sketch is on
|
||||||
|
faceId: uuid,
|
||||||
// The id of the face.
|
// The id of the face.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
// The original sketch group id of the object we are sketching on.
|
// The original sketch group id of the object we are sketching on.
|
||||||
|
@ -20,6 +20,8 @@ const commonPoints = {
|
|||||||
startAt: '[9.06, -12.22]',
|
startAt: '[9.06, -12.22]',
|
||||||
num1: 9.14,
|
num1: 9.14,
|
||||||
num2: 18.2,
|
num2: 18.2,
|
||||||
|
// num1: 9.64,
|
||||||
|
// num2: 19.19,
|
||||||
}
|
}
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }) => {
|
test.beforeEach(async ({ context, page }) => {
|
||||||
@ -76,6 +78,7 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`const part001 = startSketchOn('-XZ')`
|
`const part001 = startSketchOn('-XZ')`
|
||||||
)
|
)
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
@ -86,7 +89,6 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
@ -625,7 +627,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
const emptySpaceClick = () =>
|
const emptySpaceClick = () =>
|
||||||
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
|
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
|
||||||
const topHorzSegmentClick = () =>
|
const topHorzSegmentClick = () =>
|
||||||
page.mouse.click(709, 289).then(() => page.waitForTimeout(100))
|
page.mouse.click(709, 290).then(() => page.waitForTimeout(100))
|
||||||
const bottomHorzSegmentClick = () =>
|
const bottomHorzSegmentClick = () =>
|
||||||
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
|
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
|
||||||
|
|
||||||
@ -640,13 +642,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await page.waitForTimeout(700) // wait for animation
|
await page.waitForTimeout(700) // wait for animation
|
||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
@ -727,13 +728,18 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await emptySpaceClick()
|
await emptySpaceClick()
|
||||||
|
|
||||||
// select segment in editor than another segment in scene and check there are two cursors
|
// select segment in editor than another segment in scene and check there are two cursors
|
||||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
// TODO change this back to shift click in the scene, not cmd click in the editor
|
||||||
await page.waitForTimeout(300)
|
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
|
||||||
await bottomHorzSegmentClick()
|
await bottomHorzSegmentClick()
|
||||||
await page.keyboard.up('Shift')
|
|
||||||
|
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
||||||
|
|
||||||
|
await page.keyboard.down(process.platform === 'linux' ? 'Control' : 'Meta')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||||
|
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.up(process.platform === 'linux' ? 'Control' : 'Meta')
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
await emptySpaceClick()
|
await emptySpaceClick()
|
||||||
@ -918,13 +924,13 @@ test('Can add multiple sketches', async ({ page }) => {
|
|||||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
@ -1372,10 +1378,129 @@ test('Snap to close works (at any scale)', async ({ page }) => {
|
|||||||
) => `const part001 = startSketchOn('XZ')
|
) => `const part001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %)
|
|> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %)
|
||||||
|> line([${roundOff(scale * 175.36)}, 0], %)
|
|> line([${roundOff(scale * 175.36)}, 0], %)
|
||||||
|> line([0, -${roundOff(scale * 175.37) + fudge}], %)
|
|> line([0, -${roundOff(scale * 175.36) + fudge}], %)
|
||||||
|> close(%)`
|
|> close(%)`
|
||||||
|
|
||||||
await doSnapAtDifferentScales([0, 100, 100], codeTemplate(0.01, 0.01))
|
await doSnapAtDifferentScales([0, 100, 100], codeTemplate(0.01, 0.01))
|
||||||
|
|
||||||
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
|
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Sketch on face', async ({ page, context }) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await context.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([3.29, 7.86], %)
|
||||||
|
|> line([2.48, 2.44], %)
|
||||||
|
|> line([2.66, 1.17], %)
|
||||||
|
|> line([3.75, 0.46], %)
|
||||||
|
|> line([4.99, -0.46], %)
|
||||||
|
|> line([3.3, -2.12], %)
|
||||||
|
|> line([2.16, -3.33], %)
|
||||||
|
|> line([0.85, -3.08], %)
|
||||||
|
|> line([-0.18, -3.36], %)
|
||||||
|
|> line([-3.86, -2.73], %)
|
||||||
|
|> line([-17.67, 0.85], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
|
||||||
|
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.mouse.click(793, 133)
|
||||||
|
|
||||||
|
const firstClickPosition = [612, 238]
|
||||||
|
const secondClickPosition = [661, 242]
|
||||||
|
const thirdClickPosition = [609, 267]
|
||||||
|
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
|
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||||
|
|> startProfileAt([1.03, 1.03], %)
|
||||||
|
|> line([4.18, -0.35], %)
|
||||||
|
|> line([-4.44, -2.13], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
|
await u.updateCamPosition([1049, 239, 686])
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
const pointToDragFirst = [691, 237]
|
||||||
|
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
|
||||||
|
await page.mouse.down()
|
||||||
|
await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
|
||||||
|
steps: 5,
|
||||||
|
})
|
||||||
|
await page.mouse.up()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||||
|
|> startProfileAt([1.03, 1.03], %)
|
||||||
|
|> line([2.81, -0.33], %)
|
||||||
|
|> line([-4.44, -2.13], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
|
// exit sketch
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
|
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
|
||||||
|
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
|
||||||
|
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||||
|
|
||||||
|
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||||
|
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||||
|
|> startProfileAt([1.03, 1.03], %)
|
||||||
|
|> line([2.81, -0.33], %)
|
||||||
|
|> line([-4.44, -2.13], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)`)
|
||||||
|
})
|
||||||
|
@ -612,7 +612,7 @@ test('Client side scene scale should match engine scale mm', async ({
|
|||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([230.03, -310.33], %)`)
|
|> startProfileAt([230.03, -310.32], %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -622,7 +622,7 @@ test('Client side scene scale should match engine scale mm', async ({
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([230.03, -310.33], %)
|
|> startProfileAt([230.03, -310.32], %)
|
||||||
|> line([232.2, 0], %)`)
|
|> line([232.2, 0], %)`)
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||||
@ -632,7 +632,7 @@ test('Client side scene scale should match engine scale mm', async ({
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([230.03, -310.33], %)
|
|> startProfileAt([230.03, -310.32], %)
|
||||||
|> line([232.2, 0], %)
|
|> line([232.2, 0], %)
|
||||||
|> tangentialArcTo([694.43, -78.12], %)`)
|
|> tangentialArcTo([694.43, -78.12], %)`)
|
||||||
|
|
||||||
@ -658,3 +658,48 @@ test('Client side scene scale should match engine scale mm', async ({
|
|||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Sketch on face with none z-up', async ({ page, context }) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await context.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([1.4, 2.47], %)
|
||||||
|
|> line({ to: [9.31, 10.55], tag: 'seg01' }, %)
|
||||||
|
|> line([11.91, -10.42], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)
|
||||||
|
const part002 = startSketchOn(part001, 'seg01')
|
||||||
|
|> startProfileAt([-2.89, 1.82], %)
|
||||||
|
|> line([4.68, 3.05], %)
|
||||||
|
|> line({ to: [0, -7.79], tag: 'seg02' }, %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
).not.toBeDisabled()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
// click at 641, 135
|
||||||
|
await page.mouse.click(641, 135)
|
||||||
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
|
await expect(page).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
})
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 52 KiB |
Binary file not shown.
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 54 KiB |
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
@ -425,6 +425,7 @@ export class CameraControls {
|
|||||||
if (this.camera instanceof OrthographicCamera) return
|
if (this.camera instanceof OrthographicCamera) return
|
||||||
const { x: px, y: py, z: pz } = this.camera.position
|
const { x: px, y: py, z: pz } = this.camera.position
|
||||||
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
||||||
|
const oldCamUp = this.camera.up.clone()
|
||||||
const aspect = window.innerWidth / window.innerHeight
|
const aspect = window.innerWidth / window.innerHeight
|
||||||
this.lastPerspectiveFov = this.camera.fov
|
this.lastPerspectiveFov = this.camera.fov
|
||||||
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||||
@ -436,7 +437,8 @@ export class CameraControls {
|
|||||||
z_near,
|
z_near,
|
||||||
z_far
|
z_far
|
||||||
)
|
)
|
||||||
this.camera.up.set(0, 0, 1)
|
|
||||||
|
this.camera.up.copy(oldCamUp)
|
||||||
this.camera.layers.enable(SKETCH_LAYER)
|
this.camera.layers.enable(SKETCH_LAYER)
|
||||||
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||||
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||||
@ -458,13 +460,14 @@ export class CameraControls {
|
|||||||
}
|
}
|
||||||
private createPerspectiveCamera = () => {
|
private createPerspectiveCamera = () => {
|
||||||
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||||
|
const previousCamUp = this.camera.up.clone()
|
||||||
this.camera = new PerspectiveCamera(
|
this.camera = new PerspectiveCamera(
|
||||||
this.lastPerspectiveFov,
|
this.lastPerspectiveFov,
|
||||||
window.innerWidth / window.innerHeight,
|
window.innerWidth / window.innerHeight,
|
||||||
z_near,
|
z_near,
|
||||||
z_far
|
z_far
|
||||||
)
|
)
|
||||||
this.camera.up.set(0, 0, 1)
|
this.camera.up.copy(previousCamUp)
|
||||||
this.camera.layers.enable(SKETCH_LAYER)
|
this.camera.layers.enable(SKETCH_LAYER)
|
||||||
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||||
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||||
@ -618,7 +621,7 @@ export class CameraControls {
|
|||||||
didChange = true
|
didChange = true
|
||||||
}
|
}
|
||||||
|
|
||||||
this.safeLookAtTarget()
|
this.safeLookAtTarget(this.camera.up)
|
||||||
|
|
||||||
// Update the camera's matrices
|
// Update the camera's matrices
|
||||||
this.camera.updateMatrixWorld()
|
this.camera.updateMatrixWorld()
|
||||||
@ -683,6 +686,7 @@ export class CameraControls {
|
|||||||
targetAngle = -Math.PI / 2,
|
targetAngle = -Math.PI / 2,
|
||||||
duration = 500
|
duration = 500
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
// should tween the camera so that it has an xPosition of 0, and forcing it's yPosition to be negative
|
// should tween the camera so that it has an xPosition of 0, and forcing it's yPosition to be negative
|
||||||
// zPosition should stay the same
|
// zPosition should stay the same
|
||||||
const xyRadius = Math.sqrt(
|
const xyRadius = Math.sqrt(
|
||||||
@ -693,22 +697,7 @@ export class CameraControls {
|
|||||||
this.camera.position.y - this.target.y,
|
this.camera.position.y - this.target.y,
|
||||||
this.camera.position.x - this.target.x
|
this.camera.position.x - this.target.x
|
||||||
)
|
)
|
||||||
this._isCamMovingCallback(true, true)
|
const camAtTime = (obj: { angle: number }) => {
|
||||||
return new Promise((resolve) => {
|
|
||||||
new TWEEN.Tween({ angle: xyAngle })
|
|
||||||
.to({ angle: targetAngle }, duration)
|
|
||||||
.onUpdate((obj) => {
|
|
||||||
const x = xyRadius * Math.cos(obj.angle)
|
|
||||||
const y = xyRadius * Math.sin(obj.angle)
|
|
||||||
this.camera.position.set(
|
|
||||||
this.target.x + x,
|
|
||||||
this.target.y + y,
|
|
||||||
this.camera.position.z
|
|
||||||
)
|
|
||||||
this.update()
|
|
||||||
this.onCameraChange()
|
|
||||||
})
|
|
||||||
.onComplete((obj) => {
|
|
||||||
const x = xyRadius * Math.cos(obj.angle)
|
const x = xyRadius * Math.cos(obj.angle)
|
||||||
const y = xyRadius * Math.sin(obj.angle)
|
const y = xyRadius * Math.sin(obj.angle)
|
||||||
this.camera.position.set(
|
this.camera.position.set(
|
||||||
@ -718,13 +707,27 @@ export class CameraControls {
|
|||||||
)
|
)
|
||||||
this.update()
|
this.update()
|
||||||
this.onCameraChange()
|
this.onCameraChange()
|
||||||
|
}
|
||||||
|
const onComplete = (obj: { angle: number }) => {
|
||||||
|
camAtTime(obj)
|
||||||
this._isCamMovingCallback(false, true)
|
this._isCamMovingCallback(false, true)
|
||||||
|
|
||||||
// resolve after a couple of frames
|
// resolve after a couple of frames
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => resolve())
|
requestAnimationFrame(() => resolve())
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
this._isCamMovingCallback(true, true)
|
||||||
|
|
||||||
|
if (isReducedMotion()) {
|
||||||
|
onComplete({ angle: targetAngle })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
new TWEEN.Tween({ angle: xyAngle })
|
||||||
|
.to({ angle: targetAngle }, duration)
|
||||||
|
.onUpdate(camAtTime)
|
||||||
|
.onComplete(onComplete)
|
||||||
.start()
|
.start()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -778,6 +781,8 @@ export class CameraControls {
|
|||||||
targetQuaternion,
|
targetQuaternion,
|
||||||
animationProgress
|
animationProgress
|
||||||
)
|
)
|
||||||
|
const up = new Vector3(0, 0, 1).applyQuaternion(currentQ)
|
||||||
|
this.camera.up.copy(up)
|
||||||
const currentTarget = tempVec.lerpVectors(
|
const currentTarget = tempVec.lerpVectors(
|
||||||
initialTarget,
|
initialTarget,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
@ -802,7 +807,7 @@ export class CameraControls {
|
|||||||
|
|
||||||
const onComplete = async () => {
|
const onComplete = async () => {
|
||||||
if (isReducedMotion() && toOrthographic) {
|
if (isReducedMotion() && toOrthographic) {
|
||||||
cameraAtTime(0.99)
|
cameraAtTime(0.9999)
|
||||||
this.useOrthographicCamera()
|
this.useOrthographicCamera()
|
||||||
} else if (toOrthographic) {
|
} else if (toOrthographic) {
|
||||||
await this.animateToOrthographic()
|
await this.animateToOrthographic()
|
||||||
@ -863,37 +868,40 @@ export class CameraControls {
|
|||||||
|
|
||||||
animateFovChange() // Start the animation
|
animateFovChange() // Start the animation
|
||||||
})
|
})
|
||||||
animateToPerspective = () =>
|
animateToPerspective = (targetCamUp = new Vector3(0, 0, 1)) =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
if (this.syncDirection === 'engineToClient')
|
if (this.syncDirection === 'engineToClient') {
|
||||||
console.warn(
|
console.warn(
|
||||||
'animate To Perspective not design to work with engineToClient syncDirection.'
|
'animate To Perspective not design to work with engineToClient syncDirection.'
|
||||||
)
|
)
|
||||||
|
}
|
||||||
this.isFovAnimationInProgress = true
|
this.isFovAnimationInProgress = true
|
||||||
// Immediately set the camera to perspective with a very low FOV
|
|
||||||
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
||||||
this.lastPerspectiveFov = 4
|
this.lastPerspectiveFov = 4
|
||||||
let currentFov = 4
|
let currentFov = 4
|
||||||
this.camera.updateProjectionMatrix()
|
const initialCameraUp = this.camera.up.clone()
|
||||||
const fovAnimationStep = (targetFov - currentFov) / FRAMES_TO_ANIMATE_IN
|
|
||||||
this.usePerspectiveCamera()
|
this.usePerspectiveCamera()
|
||||||
|
const tempVec = new Vector3()
|
||||||
|
|
||||||
const animateFovChange = () => {
|
const cameraAtTime = (t: number) => {
|
||||||
if (this.camera instanceof OrthographicCamera) return
|
currentFov =
|
||||||
if (this.camera.fov < targetFov) {
|
this.lastPerspectiveFov + (targetFov - this.lastPerspectiveFov) * t
|
||||||
// Increase the FOV
|
const currentUp = tempVec.lerpVectors(initialCameraUp, targetCamUp, t)
|
||||||
currentFov = Math.min(currentFov + fovAnimationStep, targetFov)
|
this.camera.up.copy(currentUp)
|
||||||
// this.camera.fov = currentFov
|
|
||||||
this.camera.updateProjectionMatrix()
|
|
||||||
this.dollyZoom(currentFov)
|
this.dollyZoom(currentFov)
|
||||||
requestAnimationFrame(animateFovChange) // Continue the animation
|
}
|
||||||
} else {
|
|
||||||
// Set the flag to false as the FOV animation is complete
|
const onComplete = () => {
|
||||||
this.isFovAnimationInProgress = false
|
this.isFovAnimationInProgress = false
|
||||||
resolve(true)
|
resolve(true)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
animateFovChange() // Start the animation
|
new TWEEN.Tween({ t: 0 })
|
||||||
|
.to({ t: 1 }, isReducedMotion() ? 50 : FRAMES_TO_ANIMATE_IN * 16) // Assuming 60fps, hence 16ms per frame
|
||||||
|
.easing(TWEEN.Easing.Quadratic.InOut)
|
||||||
|
.onUpdate(({ t }) => cameraAtTime(t))
|
||||||
|
.onComplete(onComplete)
|
||||||
|
.start()
|
||||||
})
|
})
|
||||||
|
|
||||||
reactCameraPropertiesCallback: (a: ReactCameraProperties) => void = () => {}
|
reactCameraPropertiesCallback: (a: ReactCameraProperties) => void = () => {}
|
||||||
|
@ -40,3 +40,12 @@ export function isQuaternionVertical(q: Quaternion) {
|
|||||||
// no x or y components means it's vertical
|
// no x or y components means it's vertical
|
||||||
return compareVec2Epsilon2([v.x, v.y], [0, 0])
|
return compareVec2Epsilon2([v.x, v.y], [0, 0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function quaternionFromUpNForward(up: Vector3, forward: Vector3) {
|
||||||
|
const dummyCam = new PerspectiveCamera()
|
||||||
|
dummyCam.up.copy(up)
|
||||||
|
dummyCam.position.copy(forward)
|
||||||
|
dummyCam.lookAt(0, 0, 0)
|
||||||
|
dummyCam.updateMatrix()
|
||||||
|
return dummyCam.quaternion.clone()
|
||||||
|
}
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
Intersection,
|
Intersection,
|
||||||
LineCurve3,
|
LineCurve3,
|
||||||
Matrix4,
|
|
||||||
Mesh,
|
Mesh,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
Object3D,
|
Object3D,
|
||||||
@ -37,7 +36,7 @@ import {
|
|||||||
Y_AXIS,
|
Y_AXIS,
|
||||||
YZ_PLANE,
|
YZ_PLANE,
|
||||||
} from './sceneInfra'
|
} from './sceneInfra'
|
||||||
import { isQuaternionVertical } from './helpers'
|
import { isQuaternionVertical, quaternionFromUpNForward } from './helpers'
|
||||||
import {
|
import {
|
||||||
CallExpression,
|
CallExpression,
|
||||||
getTangentialArcToInfo,
|
getTangentialArcToInfo,
|
||||||
@ -55,7 +54,7 @@ import {
|
|||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { kclManager } from 'lang/KclSingleton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { executeAst } from 'useStore'
|
import { executeAst, useStore } from 'useStore'
|
||||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||||
import {
|
import {
|
||||||
createArcGeometry,
|
createArcGeometry,
|
||||||
@ -70,16 +69,22 @@ import {
|
|||||||
changeSketchArguments,
|
changeSketchArguments,
|
||||||
updateStartProfileAtArgs,
|
updateStartProfileAtArgs,
|
||||||
} from 'lang/std/sketch'
|
} from 'lang/std/sketch'
|
||||||
import { isReducedMotion, throttle } from 'lib/utils'
|
import { throttle } from 'lib/utils'
|
||||||
import {
|
import {
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
createCallExpressionStdLib,
|
createCallExpressionStdLib,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
createPipeSubstitution,
|
createPipeSubstitution,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { getEventForSegmentSelection } from 'lib/selections'
|
import {
|
||||||
|
getEventForSegmentSelection,
|
||||||
|
sendSelectEventToEngine,
|
||||||
|
} from 'lib/selections'
|
||||||
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
||||||
import { createGridHelper, orthoScale, perspScale } from './helpers'
|
import { createGridHelper, orthoScale, perspScale } from './helpers'
|
||||||
|
import { Models } from '@kittycad/lib'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { SketchDetails } from 'machines/modelingMachine'
|
||||||
|
|
||||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||||
|
|
||||||
@ -164,7 +169,7 @@ class SceneEntities {
|
|||||||
console.warn('createIntersectionPlane called when it already exists')
|
console.warn('createIntersectionPlane called when it already exists')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const hundredM = 1000000
|
const hundredM = 100_0000
|
||||||
const planeGeometry = new PlaneGeometry(hundredM, hundredM)
|
const planeGeometry = new PlaneGeometry(hundredM, hundredM)
|
||||||
const planeMaterial = new MeshBasicMaterial({
|
const planeMaterial = new MeshBasicMaterial({
|
||||||
color: 0xff0000,
|
color: 0xff0000,
|
||||||
@ -178,7 +183,12 @@ class SceneEntities {
|
|||||||
this.intersectionPlane.layers.set(INTERSECTION_PLANE_LAYER)
|
this.intersectionPlane.layers.set(INTERSECTION_PLANE_LAYER)
|
||||||
this.scene.add(this.intersectionPlane)
|
this.scene.add(this.intersectionPlane)
|
||||||
}
|
}
|
||||||
createSketchAxis(sketchPathToNode: PathToNode) {
|
createSketchAxis(
|
||||||
|
sketchPathToNode: PathToNode,
|
||||||
|
forward: [number, number, number],
|
||||||
|
up: [number, number, number],
|
||||||
|
sketchPosition?: [number, number, number]
|
||||||
|
) {
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
const baseXColor = 0x000055
|
const baseXColor = 0x000055
|
||||||
const baseYColor = 0x550000
|
const baseYColor = 0x550000
|
||||||
@ -238,14 +248,12 @@ class SceneEntities {
|
|||||||
child.layers.set(SKETCH_LAYER)
|
child.layers.set(SKETCH_LAYER)
|
||||||
})
|
})
|
||||||
|
|
||||||
const quat = quaternionFromSketchGroup(
|
const quat = quaternionFromUpNForward(
|
||||||
sketchGroupFromPathToNode({
|
new Vector3(...up),
|
||||||
pathToNode: sketchPathToNode,
|
new Vector3(...forward)
|
||||||
ast: kclManager.ast,
|
|
||||||
programMemory: kclManager.programMemory,
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
this.axisGroup.setRotationFromQuaternion(quat)
|
this.axisGroup.setRotationFromQuaternion(quat)
|
||||||
|
sketchPosition && this.axisGroup.position.set(...sketchPosition)
|
||||||
this.scene.add(this.axisGroup)
|
this.scene.add(this.axisGroup)
|
||||||
}
|
}
|
||||||
removeIntersectionPlane() {
|
removeIntersectionPlane() {
|
||||||
@ -258,10 +266,16 @@ class SceneEntities {
|
|||||||
ast,
|
ast,
|
||||||
// is draft line assumes the last segment is a draft line, and mods it as the user moves the mouse
|
// is draft line assumes the last segment is a draft line, and mods it as the user moves the mouse
|
||||||
draftSegment,
|
draftSegment,
|
||||||
|
forward,
|
||||||
|
up,
|
||||||
|
position,
|
||||||
}: {
|
}: {
|
||||||
sketchPathToNode: PathToNode
|
sketchPathToNode: PathToNode
|
||||||
ast?: Program
|
ast?: Program
|
||||||
draftSegment?: DraftSegment
|
draftSegment?: DraftSegment
|
||||||
|
forward: [number, number, number]
|
||||||
|
up: [number, number, number]
|
||||||
|
position?: [number, number, number]
|
||||||
}) {
|
}) {
|
||||||
sceneInfra.resetMouseListeners()
|
sceneInfra.resetMouseListeners()
|
||||||
this.createIntersectionPlane()
|
this.createIntersectionPlane()
|
||||||
@ -286,6 +300,7 @@ class SceneEntities {
|
|||||||
if (!Array.isArray(sketchGroup?.value)) return
|
if (!Array.isArray(sketchGroup?.value)) return
|
||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
|
position && group.position.set(...position)
|
||||||
group.userData = {
|
group.userData = {
|
||||||
type: SKETCH_GROUP_SEGMENTS,
|
type: SKETCH_GROUP_SEGMENTS,
|
||||||
pathToNode: sketchPathToNode,
|
pathToNode: sketchPathToNode,
|
||||||
@ -377,13 +392,18 @@ class SceneEntities {
|
|||||||
this.activeSegments[JSON.stringify(segPathToNode)] = seg
|
this.activeSegments[JSON.stringify(segPathToNode)] = seg
|
||||||
})
|
})
|
||||||
|
|
||||||
this.currentSketchQuaternion = quaternionFromSketchGroup(sketchGroup)
|
this.currentSketchQuaternion = quaternionFromUpNForward(
|
||||||
|
new Vector3(...up),
|
||||||
|
new Vector3(...forward)
|
||||||
|
)
|
||||||
group.setRotationFromQuaternion(this.currentSketchQuaternion)
|
group.setRotationFromQuaternion(this.currentSketchQuaternion)
|
||||||
this.intersectionPlane &&
|
this.intersectionPlane &&
|
||||||
this.intersectionPlane.setRotationFromQuaternion(
|
this.intersectionPlane.setRotationFromQuaternion(
|
||||||
this.currentSketchQuaternion
|
this.currentSketchQuaternion
|
||||||
)
|
)
|
||||||
|
this.intersectionPlane &&
|
||||||
|
position &&
|
||||||
|
this.intersectionPlane.position.set(...position)
|
||||||
this.scene.add(group)
|
this.scene.add(group)
|
||||||
if (!draftSegment) {
|
if (!draftSegment) {
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
@ -453,7 +473,13 @@ class SceneEntities {
|
|||||||
|
|
||||||
kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
||||||
await this.tearDownSketch({ removeAxis: false })
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
this.setupSketch({ sketchPathToNode, draftSegment })
|
this.setupSketch({
|
||||||
|
sketchPathToNode,
|
||||||
|
draftSegment,
|
||||||
|
forward,
|
||||||
|
up,
|
||||||
|
position,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
onMove: (args) => {
|
onMove: (args) => {
|
||||||
this.onDragSegment({
|
this.onDragSegment({
|
||||||
@ -476,21 +502,37 @@ class SceneEntities {
|
|||||||
}
|
}
|
||||||
updateAstAndRejigSketch = async (
|
updateAstAndRejigSketch = async (
|
||||||
sketchPathToNode: PathToNode,
|
sketchPathToNode: PathToNode,
|
||||||
modifiedAst: Program
|
modifiedAst: Program,
|
||||||
|
forward: [number, number, number],
|
||||||
|
up: [number, number, number],
|
||||||
|
origin: [number, number, number]
|
||||||
) => {
|
) => {
|
||||||
await kclManager.updateAst(modifiedAst, false)
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
await this.tearDownSketch({ removeAxis: false })
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
this.setupSketch({ sketchPathToNode })
|
this.setupSketch({ sketchPathToNode, forward, up, position: origin })
|
||||||
}
|
}
|
||||||
setUpDraftArc = async (sketchPathToNode: PathToNode) => {
|
setUpDraftArc = async (
|
||||||
|
sketchPathToNode: PathToNode,
|
||||||
|
forward: [number, number, number],
|
||||||
|
up: [number, number, number]
|
||||||
|
) => {
|
||||||
await this.tearDownSketch({ removeAxis: false })
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
this.setupSketch({ sketchPathToNode, draftSegment: 'tangentialArcTo' })
|
this.setupSketch({
|
||||||
|
sketchPathToNode,
|
||||||
|
draftSegment: 'tangentialArcTo',
|
||||||
|
forward,
|
||||||
|
up,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
setUpDraftLine = async (sketchPathToNode: PathToNode) => {
|
setUpDraftLine = async (
|
||||||
|
sketchPathToNode: PathToNode,
|
||||||
|
forward: [number, number, number],
|
||||||
|
up: [number, number, number]
|
||||||
|
) => {
|
||||||
await this.tearDownSketch({ removeAxis: false })
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
this.setupSketch({ sketchPathToNode, draftSegment: 'line' })
|
this.setupSketch({ sketchPathToNode, draftSegment: 'line', forward, up })
|
||||||
}
|
}
|
||||||
onDraftLineMouseMove = () => {}
|
onDraftLineMouseMove = () => {}
|
||||||
prepareTruncatedMemoryAndAst = (
|
prepareTruncatedMemoryAndAst = (
|
||||||
@ -785,10 +827,10 @@ class SceneEntities {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async animateAfterSketch() {
|
async animateAfterSketch() {
|
||||||
if (isReducedMotion()) {
|
// if (isReducedMotion()) {
|
||||||
sceneInfra.camControls.usePerspectiveCamera()
|
// sceneInfra.camControls.usePerspectiveCamera()
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
await sceneInfra.camControls.animateToPerspective()
|
await sceneInfra.camControls.animateToPerspective()
|
||||||
}
|
}
|
||||||
removeSketchGrid() {
|
removeSketchGrid() {
|
||||||
@ -853,26 +895,81 @@ class SceneEntities {
|
|||||||
const type: DefaultPlane = selected.userData.type
|
const type: DefaultPlane = selected.userData.type
|
||||||
selected.material.color = defaultPlaneColor(type)
|
selected.material.color = defaultPlaneColor(type)
|
||||||
},
|
},
|
||||||
onClick: (args) => {
|
onClick: async (args) => {
|
||||||
|
const checkExtrudeFaceClick = async (): Promise<boolean> => {
|
||||||
|
const { streamDimensions } = useStore.getState()
|
||||||
|
const { entity_id } = await sendSelectEventToEngine(
|
||||||
|
args?.mouseEvent,
|
||||||
|
document.getElementById('video-stream') as HTMLVideoElement,
|
||||||
|
streamDimensions
|
||||||
|
)
|
||||||
|
if (!entity_id) return false
|
||||||
|
const artifact = engineCommandManager.artifactMap[entity_id]
|
||||||
|
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info')
|
||||||
|
return false
|
||||||
|
const faceInfo: Models['FaceIsPlanar_type'] = (
|
||||||
|
await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'face_is_planar',
|
||||||
|
object_id: entity_id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)?.data?.data
|
||||||
|
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
||||||
|
return false
|
||||||
|
const { z_axis, origin, y_axis } = faceInfo
|
||||||
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
|
kclManager.ast,
|
||||||
|
artifact.range
|
||||||
|
)
|
||||||
|
|
||||||
|
sceneInfra.modelingSend({
|
||||||
|
type: 'Select default plane',
|
||||||
|
data: {
|
||||||
|
type: 'extrudeFace',
|
||||||
|
zAxis: [z_axis.x, z_axis.y, z_axis.z],
|
||||||
|
yAxis: [y_axis.x, y_axis.y, y_axis.z],
|
||||||
|
position: [origin.x, origin.y, origin.z].map(
|
||||||
|
(num) => num / sceneInfra._baseUnitMultiplier
|
||||||
|
) as [number, number, number],
|
||||||
|
extrudeSegmentPathToNode: pathToNode,
|
||||||
|
cap:
|
||||||
|
artifact?.additionalData?.type === 'cap'
|
||||||
|
? artifact.additionalData.info
|
||||||
|
: 'none',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await checkExtrudeFaceClick()) return
|
||||||
|
|
||||||
if (!args || !args.intersects?.[0]) return
|
if (!args || !args.intersects?.[0]) return
|
||||||
if (args.mouseEvent.which !== 1) return
|
if (args.mouseEvent.which !== 1) return
|
||||||
const { intersects } = args
|
const { intersects } = args
|
||||||
const type = intersects?.[0].object.name || ''
|
const type = intersects?.[0].object.name || ''
|
||||||
const posNorm = Number(intersects?.[0]?.normal?.z) > 0
|
const posNorm = Number(intersects?.[0]?.normal?.z) > 0
|
||||||
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
|
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
|
||||||
let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
|
let zAxis: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
|
||||||
|
let yAxis: [number, number, number] = [0, 1, 0]
|
||||||
if (type === YZ_PLANE) {
|
if (type === YZ_PLANE) {
|
||||||
planeString = posNorm ? 'YZ' : '-YZ'
|
planeString = posNorm ? 'YZ' : '-YZ'
|
||||||
normal = posNorm ? [1, 0, 0] : [-1, 0, 0]
|
zAxis = posNorm ? [1, 0, 0] : [-1, 0, 0]
|
||||||
|
yAxis = [0, 0, 1]
|
||||||
} else if (type === XZ_PLANE) {
|
} else if (type === XZ_PLANE) {
|
||||||
planeString = posNorm ? 'XZ' : '-XZ'
|
planeString = posNorm ? 'XZ' : '-XZ'
|
||||||
normal = posNorm ? [0, 1, 0] : [0, -1, 0]
|
zAxis = posNorm ? [0, 1, 0] : [0, -1, 0]
|
||||||
|
yAxis = [0, 0, 1]
|
||||||
}
|
}
|
||||||
sceneInfra.modelingSend({
|
sceneInfra.modelingSend({
|
||||||
type: 'Select default plane',
|
type: 'Select default plane',
|
||||||
data: {
|
data: {
|
||||||
|
type: 'defaultPlane',
|
||||||
plane: planeString,
|
plane: planeString,
|
||||||
normal,
|
zAxis,
|
||||||
|
yAxis,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -1002,33 +1099,10 @@ export function sketchGroupFromPathToNode({
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
).node
|
).node
|
||||||
|
// console.trace('where from?')
|
||||||
return programMemory.root[varDec?.id?.name || ''] as SketchGroup
|
return programMemory.root[varDec?.id?.name || ''] as SketchGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
export function quaternionFromSketchGroup(
|
|
||||||
sketchGroup: SketchGroup
|
|
||||||
): Quaternion {
|
|
||||||
// TODO figure what is happening in the executor that it's some times returning
|
|
||||||
// [x,y,z] and sometimes {x,y,z}
|
|
||||||
if (!sketchGroup?.zAxis) {
|
|
||||||
// sometimes sketchGroup is undefined,
|
|
||||||
// I don't quiet understand the circumstances yet
|
|
||||||
// and it's very intermittent so leaving this here for now
|
|
||||||
console.log('no zAxis', sketchGroup)
|
|
||||||
console.trace('no zAxis')
|
|
||||||
}
|
|
||||||
const zAxisVec = massageFormats(sketchGroup?.zAxis)
|
|
||||||
const yAxisVec = massageFormats(sketchGroup?.yAxis)
|
|
||||||
const xAxisVec = new Vector3().crossVectors(yAxisVec, zAxisVec).normalize()
|
|
||||||
|
|
||||||
let yAxisVecNormalized = yAxisVec.clone().normalize()
|
|
||||||
let zAxisVecNormalized = zAxisVec.clone().normalize()
|
|
||||||
|
|
||||||
let rotationMatrix = new Matrix4()
|
|
||||||
rotationMatrix.makeBasis(xAxisVec, yAxisVecNormalized, zAxisVecNormalized)
|
|
||||||
return new Quaternion().setFromRotationMatrix(rotationMatrix)
|
|
||||||
}
|
|
||||||
|
|
||||||
function colorSegment(object: any, color: number) {
|
function colorSegment(object: any, color: number) {
|
||||||
const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START])
|
const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START])
|
||||||
if (segmentHead) {
|
if (segmentHead) {
|
||||||
@ -1063,10 +1137,68 @@ export function getSketchQuaternion(
|
|||||||
programMemory: kclManager.programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
})
|
})
|
||||||
const zAxis = sketchGroup?.zAxis || sketchNormalBackUp
|
const zAxis = sketchGroup?.zAxis || sketchNormalBackUp
|
||||||
|
return getQuaternionFromZAxis(massageFormats(zAxis))
|
||||||
|
}
|
||||||
|
export async function getSketchOrientationDetails(
|
||||||
|
sketchPathToNode: PathToNode
|
||||||
|
): Promise<{
|
||||||
|
quat: Quaternion
|
||||||
|
sketchDetails: SketchDetails
|
||||||
|
}> {
|
||||||
|
const sketchGroup = sketchGroupFromPathToNode({
|
||||||
|
pathToNode: sketchPathToNode,
|
||||||
|
ast: kclManager.ast,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
})
|
||||||
|
if (sketchGroup.on.type === 'plane') {
|
||||||
|
const zAxis = sketchGroup?.zAxis
|
||||||
|
return {
|
||||||
|
quat: getQuaternionFromZAxis(massageFormats(zAxis)),
|
||||||
|
sketchDetails: {
|
||||||
|
sketchPathToNode,
|
||||||
|
zAxis: [zAxis.x, zAxis.y, zAxis.z],
|
||||||
|
yAxis: [sketchGroup.yAxis.x, sketchGroup.yAxis.y, sketchGroup.yAxis.z],
|
||||||
|
origin: [0, 0, 0],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sketchGroup.on.type === 'face') {
|
||||||
|
const faceInfo: Models['FaceIsPlanar_type'] = (
|
||||||
|
await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'face_is_planar',
|
||||||
|
object_id: sketchGroup.on.faceId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)?.data?.data
|
||||||
|
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
||||||
|
throw new Error('faceInfo')
|
||||||
|
const { z_axis, y_axis, origin } = faceInfo
|
||||||
|
const quaternion = quaternionFromUpNForward(
|
||||||
|
new Vector3(y_axis.x, y_axis.y, y_axis.z),
|
||||||
|
new Vector3(z_axis.x, z_axis.y, z_axis.z)
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
quat: quaternion,
|
||||||
|
sketchDetails: {
|
||||||
|
sketchPathToNode,
|
||||||
|
zAxis: [z_axis.x, z_axis.y, z_axis.z],
|
||||||
|
yAxis: [y_axis.x, y_axis.y, y_axis.z],
|
||||||
|
origin: [origin.x, origin.y, origin.z],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
'sketchGroup.on.type not recognized, has a new type been added?'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
|
||||||
const dummyCam = new PerspectiveCamera()
|
const dummyCam = new PerspectiveCamera()
|
||||||
dummyCam.up.set(0, 0, 1)
|
dummyCam.up.set(0, 0, 1)
|
||||||
const _zAxis = massageFormats(zAxis)
|
dummyCam.position.copy(zAxis)
|
||||||
dummyCam.position.copy(_zAxis)
|
|
||||||
dummyCam.lookAt(0, 0, 0)
|
dummyCam.lookAt(0, 0, 0)
|
||||||
dummyCam.updateMatrix()
|
dummyCam.updateMatrix()
|
||||||
const quaternion = dummyCam.quaternion.clone()
|
const quaternion = dummyCam.quaternion.clone()
|
||||||
@ -1075,7 +1207,7 @@ export function getSketchQuaternion(
|
|||||||
|
|
||||||
// because vertical quaternions are a gimbal lock, for the orbit controls
|
// because vertical quaternions are a gimbal lock, for the orbit controls
|
||||||
// it's best to set them explicitly to the vertical position with a known good camera up
|
// it's best to set them explicitly to the vertical position with a known good camera up
|
||||||
if (isVert && _zAxis.z < 0) {
|
if (isVert && zAxis.z < 0) {
|
||||||
quaternion.set(0, 1, 0, 0)
|
quaternion.set(0, 1, 0, 0)
|
||||||
} else if (isVert) {
|
} else if (isVert) {
|
||||||
quaternion.set(0, 0, 0, 1)
|
quaternion.set(0, 0, 0, 1)
|
||||||
|
@ -37,8 +37,10 @@ export const ZOOM_MAGIC_NUMBER = 63.5
|
|||||||
|
|
||||||
export const INTERSECTION_PLANE_LAYER = 1
|
export const INTERSECTION_PLANE_LAYER = 1
|
||||||
export const SKETCH_LAYER = 2
|
export const SKETCH_LAYER = 2
|
||||||
export const DEBUG_SHOW_INTERSECTION_PLANE = false
|
|
||||||
export const DEBUG_SHOW_BOTH_SCENES = false
|
// redundant types so that it can be changed temporarily but CI will catch the wrong type
|
||||||
|
export const DEBUG_SHOW_INTERSECTION_PLANE: false = false
|
||||||
|
export const DEBUG_SHOW_BOTH_SCENES: false = false
|
||||||
|
|
||||||
export const RAYCASTABLE_PLANE = 'raycastable-plane'
|
export const RAYCASTABLE_PLANE = 'raycastable-plane'
|
||||||
export const DEFAULT_PLANES = 'default-planes'
|
export const DEFAULT_PLANES = 'default-planes'
|
||||||
@ -97,13 +99,13 @@ class SceneInfra {
|
|||||||
_baseUnitMultiplier = 1
|
_baseUnitMultiplier = 1
|
||||||
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||||
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
|
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
|
||||||
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
|
onClickCallback: (arg: OnClickCallbackArgs) => void = () => {}
|
||||||
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
||||||
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
||||||
setCallbacks = (callbacks: {
|
setCallbacks = (callbacks: {
|
||||||
onDrag?: (arg: OnDragCallbackArgs) => void
|
onDrag?: (arg: OnDragCallbackArgs) => void
|
||||||
onMove?: (arg: OnMoveCallbackArgs) => void
|
onMove?: (arg: OnMoveCallbackArgs) => void
|
||||||
onClick?: (arg?: OnClickCallbackArgs) => void
|
onClick?: (arg: OnClickCallbackArgs) => void
|
||||||
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
|
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
|
||||||
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
|
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
|
||||||
}) => {
|
}) => {
|
||||||
@ -272,16 +274,19 @@ class SceneInfra {
|
|||||||
let transformedPoint = intersectPoint.clone()
|
let transformedPoint = intersectPoint.clone()
|
||||||
if (transformedPoint) {
|
if (transformedPoint) {
|
||||||
transformedPoint.applyQuaternion(inversePlaneQuaternion)
|
transformedPoint.applyQuaternion(inversePlaneQuaternion)
|
||||||
transformedPoint?.sub(
|
|
||||||
new Vector3(...planePosition).applyQuaternion(inversePlaneQuaternion)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
const twoD = new Vector2(
|
||||||
return {
|
// I think the intersection plane doesn't get scale when nearly everything else does, maybe that should change
|
||||||
twoD: new Vector2(
|
|
||||||
transformedPoint.x / this._baseUnitMultiplier,
|
transformedPoint.x / this._baseUnitMultiplier,
|
||||||
transformedPoint.y / this._baseUnitMultiplier
|
transformedPoint.y / this._baseUnitMultiplier
|
||||||
), // z should be 0
|
) // z should be 0
|
||||||
|
const planePositionCorrected = new Vector3(
|
||||||
|
...planePosition
|
||||||
|
).applyQuaternion(inversePlaneQuaternion)
|
||||||
|
twoD.sub(new Vector2(...planePositionCorrected))
|
||||||
|
|
||||||
|
return {
|
||||||
|
twoD,
|
||||||
threeD: intersectPoint.divideScalar(this._baseUnitMultiplier),
|
threeD: intersectPoint.divideScalar(this._baseUnitMultiplier),
|
||||||
intersection: planeIntersects[0],
|
intersection: planeIntersects[0],
|
||||||
}
|
}
|
||||||
@ -464,7 +469,7 @@ class SceneInfra {
|
|||||||
intersects,
|
intersects,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.onClickCallback()
|
this.onClickCallback({ mouseEvent, intersects })
|
||||||
}
|
}
|
||||||
// Clear the selected state whether it was dragged or not
|
// Clear the selected state whether it was dragged or not
|
||||||
this.selected = null
|
this.selected = null
|
||||||
@ -478,7 +483,7 @@ class SceneInfra {
|
|||||||
intersects,
|
intersects,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.onClickCallback()
|
this.onClickCallback({ mouseEvent, intersects })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
showDefaultPlanes() {
|
showDefaultPlanes() {
|
||||||
|
@ -38,7 +38,26 @@ describe('processMemory', () => {
|
|||||||
myVar: 5,
|
myVar: 5,
|
||||||
myFn: undefined,
|
myFn: undefined,
|
||||||
otherVar: 3,
|
otherVar: 3,
|
||||||
theExtrude: [],
|
theExtrude: [
|
||||||
|
{
|
||||||
|
type: 'extrudePlane',
|
||||||
|
position: [0, 0, 0],
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
faceId: expect.any(String),
|
||||||
|
name: '',
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [170, 194],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'extrudePlane',
|
||||||
|
position: [0, 0, 0],
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
faceId: expect.any(String),
|
||||||
|
name: '',
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [202, 230],
|
||||||
|
},
|
||||||
|
],
|
||||||
theSketch: [
|
theSketch: [
|
||||||
{ type: 'ToPoint', to: [-3.35, 0.17], from: [0, 0], name: '' },
|
{ type: 'ToPoint', to: [-3.35, 0.17], from: [0, 0], name: '' },
|
||||||
{ type: 'ToPoint', to: [0.98, 5.16], from: [-3.35, 0.17], name: '' },
|
{ type: 'ToPoint', to: [0.98, 5.16], from: [-3.35, 0.17], name: '' },
|
||||||
|
@ -23,9 +23,9 @@ import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
|||||||
import { pathMapToSelections } from 'lang/util'
|
import { pathMapToSelections } from 'lang/util'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import {
|
import {
|
||||||
|
Selections,
|
||||||
canExtrudeSelection,
|
canExtrudeSelection,
|
||||||
handleSelectionBatch,
|
handleSelectionBatch,
|
||||||
handleSelectionWithShift,
|
|
||||||
isSelectionLastLine,
|
isSelectionLastLine,
|
||||||
isSketchPipe,
|
isSketchPipe,
|
||||||
} from 'lib/selections'
|
} from 'lib/selections'
|
||||||
@ -34,14 +34,20 @@ import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
|||||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||||
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||||
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
||||||
import { getSketchQuaternion } from 'clientSideScene/sceneEntities'
|
import {
|
||||||
import { startSketchOnDefault } from 'lang/modifyAst'
|
getSketchQuaternion,
|
||||||
import { Program } from 'lang/wasm'
|
getSketchOrientationDetails,
|
||||||
import { isSingleCursorInPipe } from 'lang/queryAst'
|
} from 'clientSideScene/sceneEntities'
|
||||||
|
import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst'
|
||||||
|
import { Program, parse } from 'lang/wasm'
|
||||||
|
import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
|
||||||
import { TEST } from 'env'
|
import { TEST } from 'env'
|
||||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
import { Models } from '@kittycad/lib/dist/types/src'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
import { EditorSelection } from '@uiw/react-codemirror'
|
||||||
|
import { Vector3 } from 'three'
|
||||||
|
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -69,9 +75,15 @@ export const ModelingMachineProvider = ({
|
|||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
useSetupEngineManager(streamRef, token)
|
useSetupEngineManager(streamRef, token)
|
||||||
|
|
||||||
const { isShiftDown, editorView } = useStore((s) => ({
|
const {
|
||||||
|
isShiftDown,
|
||||||
|
editorView,
|
||||||
|
setLastCodeMirrorSelectionUpdatedFromScene,
|
||||||
|
} = useStore((s) => ({
|
||||||
isShiftDown: s.isShiftDown,
|
isShiftDown: s.isShiftDown,
|
||||||
editorView: s.editorView,
|
editorView: s.editorView,
|
||||||
|
setLastCodeMirrorSelectionUpdatedFromScene:
|
||||||
|
s.setLastCodeMirrorSelectionUpdatedFromScene,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Settings machine setup
|
// Settings machine setup
|
||||||
@ -92,92 +104,98 @@ export const ModelingMachineProvider = ({
|
|||||||
{
|
{
|
||||||
actions: {
|
actions: {
|
||||||
'sketch exit execute': () => {
|
'sketch exit execute': () => {
|
||||||
|
try {
|
||||||
|
kclManager.executeAst(parse(kclManager.code))
|
||||||
|
} catch (e) {
|
||||||
kclManager.executeAst()
|
kclManager.executeAst()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'Set selection': assign(({ selectionRanges }, event) => {
|
'Set selection': assign(({ selectionRanges }, event) => {
|
||||||
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
|
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
|
||||||
const setSelections = event.data
|
const setSelections = event.data
|
||||||
if (!editorView) return {}
|
if (!editorView) return {}
|
||||||
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
|
const dispatchSelection = (selection?: EditorSelection) => {
|
||||||
return { selectionRanges: setSelections.selection }
|
if (!selection) return // TODO less of hack for the below please
|
||||||
else if (setSelections.selectionType === 'otherSelection') {
|
setLastCodeMirrorSelectionUpdatedFromScene(Date.now())
|
||||||
const {
|
setTimeout(() => editorView.dispatch({ selection }))
|
||||||
codeMirrorSelection,
|
}
|
||||||
selectionRangeTypeMap,
|
let selections: Selections = {
|
||||||
otherSelections,
|
codeBasedSelections: [],
|
||||||
} = handleSelectionWithShift({
|
otherSelections: [],
|
||||||
otherSelection: setSelections.selection,
|
}
|
||||||
currentSelections: selectionRanges,
|
if (setSelections.selectionType === 'singleCodeCursor') {
|
||||||
isShiftDown,
|
if (!setSelections.selection && isShiftDown) {
|
||||||
})
|
} else if (!setSelections.selection && !isShiftDown) {
|
||||||
setTimeout(() => {
|
selections = {
|
||||||
editorView.dispatch({
|
codeBasedSelections: [],
|
||||||
selection: codeMirrorSelection,
|
otherSelections: [],
|
||||||
})
|
}
|
||||||
})
|
} else if (setSelections.selection && !isShiftDown) {
|
||||||
return {
|
selections = {
|
||||||
selectionRangeTypeMap,
|
codeBasedSelections: [setSelections.selection],
|
||||||
selectionRanges: {
|
otherSelections: [],
|
||||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
}
|
||||||
otherSelections,
|
} else if (setSelections.selection && isShiftDown) {
|
||||||
},
|
selections = {
|
||||||
|
codeBasedSelections: [
|
||||||
|
...selectionRanges.codeBasedSelections,
|
||||||
|
setSelections.selection,
|
||||||
|
],
|
||||||
|
otherSelections: selectionRanges.otherSelections,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (setSelections.selectionType === 'singleCodeCursor') {
|
|
||||||
// This DOES NOT set the `selectionRanges` in xstate context
|
|
||||||
// instead it updates/dispatches to the editor, which in turn updates the xstate context
|
|
||||||
// I've found this the best way to deal with the editor without causing an infinite loop
|
|
||||||
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
|
|
||||||
// because we want to respect the user manually placing the cursor too.
|
|
||||||
|
|
||||||
// for more details on how selections see `src/lib/selections.ts`.
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
engineEvents,
|
||||||
codeMirrorSelection,
|
codeMirrorSelection,
|
||||||
selectionRangeTypeMap,
|
updateSceneObjectColors,
|
||||||
otherSelections,
|
} = handleSelectionBatch({
|
||||||
} = handleSelectionWithShift({
|
selections,
|
||||||
codeSelection: setSelections.selection,
|
|
||||||
currentSelections: selectionRanges,
|
|
||||||
isShiftDown,
|
|
||||||
})
|
})
|
||||||
if (codeMirrorSelection) {
|
codeMirrorSelection && dispatchSelection(codeMirrorSelection)
|
||||||
setTimeout(() => {
|
engineEvents &&
|
||||||
editorView.dispatch({
|
engineEvents.forEach((event) =>
|
||||||
selection: codeMirrorSelection,
|
engineCommandManager.sendSceneCommand(event)
|
||||||
})
|
)
|
||||||
})
|
updateSceneObjectColors()
|
||||||
}
|
|
||||||
if (!setSelections.selection) {
|
|
||||||
return {
|
return {
|
||||||
selectionRangeTypeMap,
|
selectionRanges: selections,
|
||||||
selectionRanges: {
|
|
||||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
|
||||||
otherSelections,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (setSelections.selectionType === 'mirrorCodeMirrorSelections') {
|
||||||
return {
|
return {
|
||||||
selectionRangeTypeMap,
|
selectionRanges: setSelections.selection,
|
||||||
selectionRanges: {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setSelections.selectionType === 'otherSelection') {
|
||||||
|
if (isShiftDown) {
|
||||||
|
selections = {
|
||||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
codeBasedSelections: selectionRanges.codeBasedSelections,
|
||||||
otherSelections,
|
otherSelections: [setSelections.selection],
|
||||||
},
|
}
|
||||||
|
} else {
|
||||||
|
selections = {
|
||||||
|
codeBasedSelections: [],
|
||||||
|
otherSelections: [setSelections.selection],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This DOES NOT set the `selectionRanges` in xstate context
|
const { engineEvents, updateSceneObjectColors } =
|
||||||
// same as comment above
|
|
||||||
const { codeMirrorSelection, selectionRangeTypeMap } =
|
|
||||||
handleSelectionBatch({
|
handleSelectionBatch({
|
||||||
selections: setSelections.selection,
|
selections,
|
||||||
})
|
|
||||||
if (codeMirrorSelection) {
|
|
||||||
setTimeout(() => {
|
|
||||||
editorView.dispatch({
|
|
||||||
selection: codeMirrorSelection,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
engineEvents &&
|
||||||
|
engineEvents.forEach((event) =>
|
||||||
|
engineCommandManager.sendSceneCommand(event)
|
||||||
|
)
|
||||||
|
updateSceneObjectColors()
|
||||||
|
return {
|
||||||
|
selectionRanges: selections,
|
||||||
}
|
}
|
||||||
return { selectionRangeTypeMap }
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
}),
|
}),
|
||||||
'Engine export': (_, event) => {
|
'Engine export': (_, event) => {
|
||||||
if (event.type !== 'Export' || TEST) return
|
if (event.type !== 'Export' || TEST) return
|
||||||
@ -255,10 +273,10 @@ export const ModelingMachineProvider = ({
|
|||||||
kclManager.kclErrors.length === 0 && kclManager.ast.body.length > 0,
|
kclManager.kclErrors.length === 0 && kclManager.ast.body.length > 0,
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
'AST-undo-startSketchOn': async ({ sketchPathToNode }) => {
|
'AST-undo-startSketchOn': async ({ sketchDetails }) => {
|
||||||
if (!sketchPathToNode) return
|
if (!sketchDetails) return
|
||||||
const newAst: Program = JSON.parse(JSON.stringify(kclManager.ast))
|
const newAst: Program = JSON.parse(JSON.stringify(kclManager.ast))
|
||||||
const varDecIndex = sketchPathToNode[1][0]
|
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
|
||||||
// remove body item at varDecIndex
|
// remove body item at varDecIndex
|
||||||
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
||||||
await kclManager.executeAstMock(newAst, { updates: 'code' })
|
await kclManager.executeAstMock(newAst, { updates: 'code' })
|
||||||
@ -267,28 +285,69 @@ export const ModelingMachineProvider = ({
|
|||||||
onDrag: () => {},
|
onDrag: () => {},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
'animate-to-face': async (_, { data: { plane, normal } }) => {
|
'animate-to-face': async (_, { data }) => {
|
||||||
|
if (data.type === 'extrudeFace') {
|
||||||
|
const { modifiedAst, pathToNode: pathToNewSketchNode } =
|
||||||
|
sketchOnExtrudedFace(
|
||||||
|
kclManager.ast,
|
||||||
|
data.extrudeSegmentPathToNode,
|
||||||
|
kclManager.programMemory,
|
||||||
|
data.cap
|
||||||
|
)
|
||||||
|
await kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
||||||
|
|
||||||
|
const forward = new Vector3(...data.zAxis)
|
||||||
|
const up = new Vector3(...data.yAxis)
|
||||||
|
|
||||||
|
let target = new Vector3(...data.position).multiplyScalar(
|
||||||
|
sceneInfra._baseUnitMultiplier
|
||||||
|
)
|
||||||
|
const quaternion = quaternionFromUpNForward(up, forward)
|
||||||
|
await sceneInfra.camControls.tweenCameraToQuaternion(
|
||||||
|
quaternion,
|
||||||
|
target
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
sketchPathToNode: pathToNewSketchNode,
|
||||||
|
zAxis: data.zAxis,
|
||||||
|
yAxis: data.yAxis,
|
||||||
|
origin: data.position,
|
||||||
|
}
|
||||||
|
}
|
||||||
const { modifiedAst, pathToNode } = startSketchOnDefault(
|
const { modifiedAst, pathToNode } = startSketchOnDefault(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
plane
|
data.plane
|
||||||
)
|
)
|
||||||
await kclManager.updateAst(modifiedAst, false)
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
const quaternion = getSketchQuaternion(pathToNode, normal)
|
const quat = await getSketchQuaternion(pathToNode, data.zAxis)
|
||||||
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
|
await sceneInfra.camControls.tweenCameraToQuaternion(quat)
|
||||||
return {
|
return {
|
||||||
sketchPathToNode: pathToNode,
|
sketchPathToNode: pathToNode,
|
||||||
sketchNormalBackUp: normal,
|
zAxis: data.zAxis,
|
||||||
|
yAxis: data.yAxis,
|
||||||
|
origin: [0, 0, 0],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'animate-to-sketch': async ({
|
'animate-to-sketch': async ({ selectionRanges }) => {
|
||||||
sketchPathToNode,
|
const sourceRange = selectionRanges.codeBasedSelections[0].range
|
||||||
sketchNormalBackUp,
|
const sketchPathToNode = getNodePathFromSourceRange(
|
||||||
}) => {
|
kclManager.ast,
|
||||||
const quaternion = getSketchQuaternion(
|
sourceRange
|
||||||
sketchPathToNode || [],
|
|
||||||
sketchNormalBackUp
|
|
||||||
)
|
)
|
||||||
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
|
const info = await getSketchOrientationDetails(sketchPathToNode || [])
|
||||||
|
await sceneInfra.camControls.tweenCameraToQuaternion(
|
||||||
|
info.quat,
|
||||||
|
new Vector3(...info.sketchDetails.origin)
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
sketchPathToNode: sketchPathToNode || [],
|
||||||
|
zAxis: info.sketchDetails.zAxis || null,
|
||||||
|
yAxis: info.sketchDetails.yAxis || null,
|
||||||
|
origin: info.sketchDetails.origin.map(
|
||||||
|
(a) => a / sceneInfra._baseUnitMultiplier
|
||||||
|
) as [number, number, number],
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'Get horizontal info': async ({
|
'Get horizontal info': async ({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { getNormalisedCoordinates } from '../lib/utils'
|
import { getNormalisedCoordinates } from '../lib/utils'
|
||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { Models } from '@kittycad/lib'
|
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { useKclContext } from 'lang/KclSingleton'
|
import { useKclContext } from 'lang/KclSingleton'
|
||||||
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
|
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
|
||||||
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
|
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
|
||||||
|
import { butName } from 'lib/cameraControls'
|
||||||
|
import { sendSelectEventToEngine } from 'lib/selections'
|
||||||
|
|
||||||
export const Stream = ({ className = '' }: { className?: string }) => {
|
export const Stream = ({ className = '' }: { className?: string }) => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
@ -60,50 +59,14 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
|||||||
setClickCoords({ x, y })
|
setClickCoords({ x, y })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
|
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
clientX,
|
|
||||||
clientY,
|
|
||||||
ctrlKey,
|
|
||||||
}) => {
|
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
setButtonDownInStream(undefined)
|
setButtonDownInStream(undefined)
|
||||||
if (state.matches('Sketch')) return
|
if (state.matches('Sketch')) return
|
||||||
if (state.matches('Sketch no face')) return
|
if (state.matches('Sketch no face')) return
|
||||||
const { x, y } = getNormalisedCoordinates({
|
|
||||||
clientX,
|
|
||||||
clientY,
|
|
||||||
el: videoRef.current,
|
|
||||||
...streamDimensions,
|
|
||||||
})
|
|
||||||
|
|
||||||
const newCmdId = uuidv4()
|
if (!didDragInStream && butName(e).left) {
|
||||||
const interaction = ctrlKey ? 'pan' : 'rotate'
|
sendSelectEventToEngine(e, videoRef.current, streamDimensions)
|
||||||
|
|
||||||
const command: Models['WebSocketRequest_type'] = {
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'camera_drag_end',
|
|
||||||
interaction,
|
|
||||||
window: { x, y },
|
|
||||||
},
|
|
||||||
cmd_id: newCmdId,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!didDragInStream) {
|
|
||||||
command.cmd = {
|
|
||||||
type: 'select_with_point',
|
|
||||||
selected_at_window: { x, y },
|
|
||||||
selection_type: 'add',
|
|
||||||
}
|
|
||||||
engineCommandManager.sendSceneCommand(command)
|
|
||||||
} else if (didDragInStream) {
|
|
||||||
command.cmd = {
|
|
||||||
type: 'handle_mouse_drag_end',
|
|
||||||
window: { x, y },
|
|
||||||
}
|
|
||||||
void engineCommandManager.sendSceneCommand(command)
|
|
||||||
} else {
|
|
||||||
engineCommandManager.sendSceneCommand(command)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setDidDragInStream(false)
|
setDidDragInStream(false)
|
||||||
@ -143,6 +106,7 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
|||||||
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
|
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
|
||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
||||||
|
id="video-stream"
|
||||||
/>
|
/>
|
||||||
<ClientSideScene cameraControls={settings.context?.cameraControls} />
|
<ClientSideScene cameraControls={settings.context?.cameraControls} />
|
||||||
{!isNetworkOkay && !isLoading && (
|
{!isNetworkOkay && !isLoading && (
|
||||||
|
@ -3,6 +3,7 @@ import ReactCodeMirror, {
|
|||||||
Extension,
|
Extension,
|
||||||
ViewUpdate,
|
ViewUpdate,
|
||||||
keymap,
|
keymap,
|
||||||
|
SelectionRange,
|
||||||
} from '@uiw/react-codemirror'
|
} from '@uiw/react-codemirror'
|
||||||
import { TEST } from 'env'
|
import { TEST } from 'env'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
@ -75,7 +76,7 @@ export const TextEditor = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
context: { selectionRanges, selectionRangeTypeMap },
|
context: { selectionRanges },
|
||||||
send,
|
send,
|
||||||
state,
|
state,
|
||||||
} = useModelingContext()
|
} = useModelingContext()
|
||||||
@ -91,10 +92,27 @@ export const TextEditor = ({
|
|||||||
if (isNetworkOkay) kclManager.setCodeAndExecute(newCode)
|
if (isNetworkOkay) kclManager.setCodeAndExecute(newCode)
|
||||||
else kclManager.setCode(newCode)
|
else kclManager.setCode(newCode)
|
||||||
} //, []);
|
} //, []);
|
||||||
|
const lastSelection = useRef('')
|
||||||
const onUpdate = (viewUpdate: ViewUpdate) => {
|
const onUpdate = (viewUpdate: ViewUpdate) => {
|
||||||
if (!editorView) {
|
if (!editorView) {
|
||||||
setEditorView(viewUpdate.view)
|
setEditorView(viewUpdate.view)
|
||||||
}
|
}
|
||||||
|
const selString = stringifyRanges(
|
||||||
|
viewUpdate?.state?.selection?.ranges || []
|
||||||
|
)
|
||||||
|
if (selString === lastSelection.current) {
|
||||||
|
// onUpdate is noisy and is fired a lot by extensions
|
||||||
|
// since we're only interested in selections changes we can ignore most of these.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastSelection.current = selString
|
||||||
|
|
||||||
|
if (
|
||||||
|
// TODO find a less lazy way of getting the last
|
||||||
|
Date.now() - useStore.getState().lastCodeMirrorSelectionUpdatedFromScene <
|
||||||
|
150
|
||||||
|
)
|
||||||
|
return // update triggered by scene selection
|
||||||
if (sceneInfra.selected) return // mid drag
|
if (sceneInfra.selected) return // mid drag
|
||||||
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
||||||
'Equip Line tool',
|
'Equip Line tool',
|
||||||
@ -104,7 +122,6 @@ export const TextEditor = ({
|
|||||||
const eventInfo = processCodeMirrorRanges({
|
const eventInfo = processCodeMirrorRanges({
|
||||||
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
selectionRangeTypeMap,
|
|
||||||
isShiftDown,
|
isShiftDown,
|
||||||
})
|
})
|
||||||
if (!eventInfo) return
|
if (!eventInfo) return
|
||||||
@ -226,3 +243,7 @@ export const TextEditor = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stringifyRanges(ranges: readonly SelectionRange[]): string {
|
||||||
|
return ranges.map(({ to, from }) => `${to}->${from}`).join('&')
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { executeAst, executeCode } from 'useStore'
|
import { executeAst, executeCode } from 'useStore'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import {
|
import {
|
||||||
EngineCommandManager,
|
EngineCommandManager,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
@ -14,6 +15,8 @@ import {
|
|||||||
Program,
|
Program,
|
||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
recast,
|
recast,
|
||||||
|
SketchGroup,
|
||||||
|
ExtrudeGroup,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
import { createContext, useContext, useEffect, useState } from 'react'
|
import { createContext, useContext, useEffect, useState } from 'react'
|
||||||
@ -235,7 +238,6 @@ class KclManager {
|
|||||||
updateCode = false,
|
updateCode = false,
|
||||||
executionId?: number
|
executionId?: number
|
||||||
) {
|
) {
|
||||||
console.trace('executeAst')
|
|
||||||
const currentExecutionId = executionId || Date.now()
|
const currentExecutionId = executionId || Date.now()
|
||||||
this._cancelTokens.set(currentExecutionId, false)
|
this._cancelTokens.set(currentExecutionId, false)
|
||||||
|
|
||||||
@ -245,6 +247,7 @@ class KclManager {
|
|||||||
ast,
|
ast,
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
})
|
})
|
||||||
|
enterEditMode(programMemory)
|
||||||
this.isExecuting = false
|
this.isExecuting = false
|
||||||
// Check the cancellation token for this execution before applying side effects
|
// Check the cancellation token for this execution before applying side effects
|
||||||
if (this._cancelTokens.get(currentExecutionId)) {
|
if (this._cancelTokens.get(currentExecutionId)) {
|
||||||
@ -333,6 +336,7 @@ class KclManager {
|
|||||||
}
|
}
|
||||||
if (!result.isChange) return
|
if (!result.isChange) return
|
||||||
const { logs, errors, programMemory, ast } = result
|
const { logs, errors, programMemory, ast } = result
|
||||||
|
enterEditMode(programMemory)
|
||||||
this.logs = logs
|
this.logs = logs
|
||||||
this.kclErrors = errors
|
this.kclErrors = errors
|
||||||
this.programMemory = programMemory
|
this.programMemory = programMemory
|
||||||
@ -520,3 +524,18 @@ function safteLSSetItem(key: string, value: string) {
|
|||||||
if (typeof window === 'undefined') return
|
if (typeof window === 'undefined') return
|
||||||
localStorage?.setItem(key, value)
|
localStorage?.setItem(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enterEditMode(programMemory: ProgramMemory) {
|
||||||
|
const firstSketchOrExtrudeGroup = Object.values(programMemory.root).find(
|
||||||
|
(node) => node.type === 'ExtrudeGroup' || node.type === 'SketchGroup'
|
||||||
|
) as SketchGroup | ExtrudeGroup
|
||||||
|
firstSketchOrExtrudeGroup &&
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'edit_mode_enter',
|
||||||
|
target: firstSketchOrExtrudeGroup.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -74,16 +74,56 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
expect(sketch001).toEqual({
|
expect(sketch001).toEqual({
|
||||||
type: 'ExtrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
value: [],
|
value: [
|
||||||
|
{
|
||||||
|
type: 'extrudePlane',
|
||||||
|
position: [0, 0, 0],
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
faceId: expect.any(String),
|
||||||
|
name: '',
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [77, 102],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'extrudePlane',
|
||||||
|
position: [0, 0, 0],
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
faceId: expect.any(String),
|
||||||
|
name: '',
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [108, 132],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sketchGroupValues: [
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [0, 0],
|
||||||
|
to: [-1.59, -1.54],
|
||||||
|
name: '',
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [77, 102],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [-1.59, -1.54],
|
||||||
|
to: [0.46, -5.82],
|
||||||
|
name: '',
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [108, 132],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
endCapId: null,
|
|
||||||
startCapId: null,
|
|
||||||
sketchGroupValues: expect.any(Array),
|
|
||||||
xAxis: { x: 1, y: 0, z: 0 },
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
yAxis: { x: 0, y: 1, z: 0 },
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
zAxis: { x: 0, y: 0, z: 1 },
|
zAxis: { x: 0, y: 0, z: 1 },
|
||||||
|
startCapId: expect.any(String),
|
||||||
|
endCapId: expect.any(String),
|
||||||
__meta: [{ sourceRange: [46, 71] }],
|
__meta: [{ sourceRange: [46, 71] }],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -117,32 +157,149 @@ const sk2 = startSketchOn('XY')
|
|||||||
{
|
{
|
||||||
type: 'ExtrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
value: [],
|
value: [
|
||||||
|
{
|
||||||
|
type: 'extrudePlane',
|
||||||
|
position: [0, 0, 0],
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
faceId: expect.any(String),
|
||||||
|
name: '',
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [69, 89],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'extrudePlane',
|
||||||
|
position: [0, 0, 0],
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
faceId: expect.any(String),
|
||||||
|
name: 'p',
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [95, 118],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'extrudePlane',
|
||||||
|
position: [0, 0, 0],
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
faceId: expect.any(String),
|
||||||
|
name: '',
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [124, 143],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sketchGroupValues: [
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [0, 0],
|
||||||
|
to: [-2.5, 0],
|
||||||
|
name: '',
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [69, 89],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [-2.5, 0],
|
||||||
|
to: [0, 10],
|
||||||
|
name: 'p',
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [95, 118],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [0, 10],
|
||||||
|
to: [2.5, 0],
|
||||||
|
name: '',
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [124, 143],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
endCapId: null,
|
|
||||||
startCapId: null,
|
|
||||||
sketchGroupValues: expect.any(Array),
|
|
||||||
xAxis: { x: 1, y: 0, z: 0 },
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
yAxis: { x: 0, y: 1, z: 0 },
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
zAxis: { x: 0, y: 0, z: 1 },
|
zAxis: { x: 0, y: 0, z: 1 },
|
||||||
|
startCapId: expect.any(String),
|
||||||
|
endCapId: expect.any(String),
|
||||||
__meta: [{ sourceRange: [38, 63] }],
|
__meta: [{ sourceRange: [38, 63] }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'ExtrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
value: [],
|
value: [
|
||||||
|
{
|
||||||
|
type: 'extrudePlane',
|
||||||
|
position: [0, 0, 0],
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
faceId: expect.any(String),
|
||||||
|
name: '',
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [374, 394],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'extrudePlane',
|
||||||
|
position: [0, 0, 0],
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
faceId: expect.any(String),
|
||||||
|
name: 'p',
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [400, 422],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'extrudePlane',
|
||||||
|
position: [0, 0, 0],
|
||||||
|
rotation: [0, 0, 0, 1],
|
||||||
|
faceId: expect.any(String),
|
||||||
|
name: '',
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [428, 447],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sketchGroupValues: [
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [0, 0],
|
||||||
|
to: [-2.5, 0],
|
||||||
|
name: '',
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [374, 394],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [-2.5, 0],
|
||||||
|
to: [0, 3],
|
||||||
|
name: 'p',
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [400, 422],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [0, 3],
|
||||||
|
to: [2.5, 0],
|
||||||
|
name: '',
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [428, 447],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
|
|
||||||
endCapId: null,
|
|
||||||
startCapId: null,
|
|
||||||
sketchGroupValues: expect.any(Array),
|
|
||||||
xAxis: { x: 1, y: 0, z: 0 },
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
yAxis: { x: 0, y: 1, z: 0 },
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
zAxis: { x: 0, y: 0, z: 1 },
|
zAxis: { x: 0, y: 0, z: 1 },
|
||||||
|
startCapId: expect.any(String),
|
||||||
|
endCapId: expect.any(String),
|
||||||
__meta: [{ sourceRange: [343, 368] }],
|
__meta: [{ sourceRange: [343, 368] }],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
@ -12,8 +12,10 @@ import {
|
|||||||
addSketchTo,
|
addSketchTo,
|
||||||
giveSketchFnCallTag,
|
giveSketchFnCallTag,
|
||||||
moveValueIntoNewVariable,
|
moveValueIntoNewVariable,
|
||||||
|
sketchOnExtrudedFace,
|
||||||
} from './modifyAst'
|
} from './modifyAst'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
|
import { getNodePathFromSourceRange } from './queryAst'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
@ -274,3 +276,89 @@ const yo2 = hmm([identifierGuy + 5])`
|
|||||||
expect(newCode).toContain(`const yo2 = hmm([newVar])`)
|
expect(newCode).toContain(`const yo2 = hmm([newVar])`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('testing sketchOnExtrudedFace', () => {
|
||||||
|
test('it should be able to extrude on regular segments', async () => {
|
||||||
|
const code = `const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([3.58, 2.06], %)
|
||||||
|
|> line([9.7, 9.19], %)
|
||||||
|
|> line([8.62, -9.57], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)`
|
||||||
|
const ast = parse(code)
|
||||||
|
const programMemory = await enginelessExecutor(ast)
|
||||||
|
const snippet = `line([9.7, 9.19], %)`
|
||||||
|
const range: [number, number] = [
|
||||||
|
code.indexOf(snippet),
|
||||||
|
code.indexOf(snippet) + snippet.length,
|
||||||
|
]
|
||||||
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
|
|
||||||
|
const { modifiedAst } = sketchOnExtrudedFace(ast, pathToNode, programMemory)
|
||||||
|
const newCode = recast(modifiedAst)
|
||||||
|
expect(newCode).toContain(`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([3.58, 2.06], %)
|
||||||
|
|> line([9.7, 9.19], %, 'seg01')
|
||||||
|
|> line([8.62, -9.57], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)
|
||||||
|
const part002 = startSketchOn(part001, 'seg01')`)
|
||||||
|
})
|
||||||
|
test('it should be able to extrude on close segments', async () => {
|
||||||
|
const code = `const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([3.58, 2.06], %)
|
||||||
|
|> line([9.7, 9.19], %)
|
||||||
|
|> line([8.62, -9.57], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)`
|
||||||
|
const ast = parse(code)
|
||||||
|
const programMemory = await enginelessExecutor(ast)
|
||||||
|
const snippet = `close(%)`
|
||||||
|
const range: [number, number] = [
|
||||||
|
code.indexOf(snippet),
|
||||||
|
code.indexOf(snippet) + snippet.length,
|
||||||
|
]
|
||||||
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
|
|
||||||
|
const { modifiedAst } = sketchOnExtrudedFace(ast, pathToNode, programMemory)
|
||||||
|
const newCode = recast(modifiedAst)
|
||||||
|
expect(newCode).toContain(`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([3.58, 2.06], %)
|
||||||
|
|> line([9.7, 9.19], %)
|
||||||
|
|> line([8.62, -9.57], %)
|
||||||
|
|> close(%, 'seg01')
|
||||||
|
|> extrude(5 + 7, %)
|
||||||
|
const part002 = startSketchOn(part001, 'seg01')`)
|
||||||
|
})
|
||||||
|
test('it should be able to extrude on start-end caps', async () => {
|
||||||
|
const code = `const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([3.58, 2.06], %)
|
||||||
|
|> line([9.7, 9.19], %)
|
||||||
|
|> line([8.62, -9.57], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)`
|
||||||
|
const ast = parse(code)
|
||||||
|
const programMemory = await enginelessExecutor(ast)
|
||||||
|
const snippet = `startProfileAt([3.58, 2.06], %)`
|
||||||
|
const range: [number, number] = [
|
||||||
|
code.indexOf(snippet),
|
||||||
|
code.indexOf(snippet) + snippet.length,
|
||||||
|
]
|
||||||
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
|
|
||||||
|
const { modifiedAst } = sketchOnExtrudedFace(
|
||||||
|
ast,
|
||||||
|
pathToNode,
|
||||||
|
programMemory,
|
||||||
|
'end'
|
||||||
|
)
|
||||||
|
const newCode = recast(modifiedAst)
|
||||||
|
expect(newCode).toContain(`const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([3.58, 2.06], %)
|
||||||
|
|> line([9.7, 9.19], %)
|
||||||
|
|> line([8.62, -9.57], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(5 + 7, %)
|
||||||
|
const part002 = startSketchOn(part001, 'END')`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { ToolTip } from '../useStore'
|
|
||||||
import { Selection } from 'lib/selections'
|
import { Selection } from 'lib/selections'
|
||||||
import {
|
import {
|
||||||
Program,
|
Program,
|
||||||
@ -64,7 +63,6 @@ export function addStartProfileAt(
|
|||||||
pathToNode: PathToNode,
|
pathToNode: PathToNode,
|
||||||
at: [number, number]
|
at: [number, number]
|
||||||
): { modifiedAst: Program; pathToNode: PathToNode } {
|
): { modifiedAst: Program; pathToNode: PathToNode } {
|
||||||
console.log('addStartProfileAt called')
|
|
||||||
const variableDeclaration = getNodeFromPath<VariableDeclaration>(
|
const variableDeclaration = getNodeFromPath<VariableDeclaration>(
|
||||||
node,
|
node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -317,12 +315,12 @@ export function extrudeSketch(
|
|||||||
export function sketchOnExtrudedFace(
|
export function sketchOnExtrudedFace(
|
||||||
node: Program,
|
node: Program,
|
||||||
pathToNode: PathToNode,
|
pathToNode: PathToNode,
|
||||||
programMemory: ProgramMemory
|
programMemory: ProgramMemory,
|
||||||
|
cap: 'none' | 'start' | 'end' = 'none'
|
||||||
): { modifiedAst: Program; pathToNode: PathToNode } {
|
): { modifiedAst: Program; pathToNode: PathToNode } {
|
||||||
let _node = { ...node }
|
let _node = { ...node }
|
||||||
const newSketchName = findUniqueName(node, 'part')
|
const newSketchName = findUniqueName(node, 'part')
|
||||||
const { node: oldSketchNode, shallowPath: pathToOldSketch } =
|
const { node: oldSketchNode } = getNodeFromPath<VariableDeclarator>(
|
||||||
getNodeFromPath<VariableDeclarator>(
|
|
||||||
_node,
|
_node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
'VariableDeclarator',
|
'VariableDeclarator',
|
||||||
@ -335,6 +333,8 @@ export function sketchOnExtrudedFace(
|
|||||||
'CallExpression'
|
'CallExpression'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let _tag = ''
|
||||||
|
if (cap === 'none') {
|
||||||
const { modifiedAst, tag } = addTagForSketchOnFace(
|
const { modifiedAst, tag } = addTagForSketchOnFace(
|
||||||
{
|
{
|
||||||
previousProgramMemory: programMemory,
|
previousProgramMemory: programMemory,
|
||||||
@ -343,34 +343,34 @@ export function sketchOnExtrudedFace(
|
|||||||
},
|
},
|
||||||
expression.callee.name
|
expression.callee.name
|
||||||
)
|
)
|
||||||
|
_tag = tag
|
||||||
_node = modifiedAst
|
_node = modifiedAst
|
||||||
|
} else {
|
||||||
|
_tag = cap.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
const newSketch = createVariableDeclaration(
|
const newSketch = createVariableDeclaration(
|
||||||
newSketchName,
|
newSketchName,
|
||||||
createPipeExpression([
|
createCallExpressionStdLib('startSketchOn', [
|
||||||
createCallExpressionStdLib('startSketchAt', [
|
|
||||||
createArrayExpression([createLiteral(0), createLiteral(0)]),
|
|
||||||
]),
|
|
||||||
createCallExpressionStdLib('lineTo', [
|
|
||||||
createArrayExpression([createLiteral(1), createLiteral(1)]),
|
|
||||||
createPipeSubstitution(),
|
|
||||||
]),
|
|
||||||
createCallExpression('transform', [
|
|
||||||
createCallExpressionStdLib('getExtrudeWallTransform', [
|
|
||||||
createLiteral(tag),
|
|
||||||
createIdentifier(oldSketchName),
|
createIdentifier(oldSketchName),
|
||||||
]),
|
createLiteral(_tag),
|
||||||
createPipeSubstitution(),
|
|
||||||
]),
|
|
||||||
]),
|
]),
|
||||||
'const'
|
'const'
|
||||||
)
|
)
|
||||||
const expressionIndex = getLastIndex(pathToOldSketch)
|
|
||||||
|
const expressionIndex = pathToNode[1][0] as number
|
||||||
_node.body.splice(expressionIndex + 1, 0, newSketch)
|
_node.body.splice(expressionIndex + 1, 0, newSketch)
|
||||||
|
const newpathToNode: PathToNode = [
|
||||||
|
['body', ''],
|
||||||
|
[expressionIndex + 1, 'index'],
|
||||||
|
['declarations', 'VariableDeclaration'],
|
||||||
|
[0, 'index'],
|
||||||
|
['init', 'VariableDeclarator'],
|
||||||
|
]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode: [...pathToNode.slice(0, -1), [expressionIndex, 'index']],
|
pathToNode: newpathToNode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ log(5, myVar)
|
|||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted.trim()).toBe(code.trim())
|
expect(recasted.trim()).toBe(code.trim())
|
||||||
})
|
})
|
||||||
it('recast long object exectution', () => {
|
it('recast long object execution', () => {
|
||||||
const code = `const three = 3
|
const code = `const three = 3
|
||||||
const yo = {
|
const yo = {
|
||||||
aStr: 'str',
|
aStr: 'str',
|
||||||
@ -163,7 +163,7 @@ const yo = {
|
|||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
expect(recasted).toBe(code)
|
expect(recasted).toBe(code)
|
||||||
})
|
})
|
||||||
it('recast short object exectution', () => {
|
it('recast short object execution', () => {
|
||||||
const code = `const yo = { key: 'val' }
|
const code = `const yo = { key: 'val' }
|
||||||
`
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
|
@ -13,6 +13,10 @@ interface CommandInfo {
|
|||||||
range: SourceRange
|
range: SourceRange
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
parentId?: string
|
parentId?: string
|
||||||
|
additionalData?: {
|
||||||
|
type: 'cap'
|
||||||
|
info: 'start' | 'end'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
||||||
@ -1069,14 +1073,42 @@ export class EngineCommandManager {
|
|||||||
} as const
|
} as const
|
||||||
this.artifactMap[id] = artifact
|
this.artifactMap[id] = artifact
|
||||||
if (
|
if (
|
||||||
command.commandType === 'entity_linear_pattern' ||
|
(command.commandType === 'entity_linear_pattern' &&
|
||||||
command.commandType === 'entity_circular_pattern'
|
modelingResponse.type === 'entity_linear_pattern') ||
|
||||||
|
(command.commandType === 'entity_circular_pattern' &&
|
||||||
|
modelingResponse.type === 'entity_circular_pattern')
|
||||||
) {
|
) {
|
||||||
const entities = (modelingResponse as any)?.data?.entity_ids
|
const entities = modelingResponse.data.entity_ids
|
||||||
entities?.forEach((entity: string) => {
|
entities?.forEach((entity: string) => {
|
||||||
this.artifactMap[entity] = artifact
|
this.artifactMap[entity] = artifact
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
command?.commandType === 'solid3d_get_extrusion_face_info' &&
|
||||||
|
modelingResponse.type === 'solid3d_get_extrusion_face_info'
|
||||||
|
) {
|
||||||
|
console.log('modelingResposne', modelingResponse)
|
||||||
|
const parent = this.artifactMap[command?.parentId || '']
|
||||||
|
modelingResponse.data.faces.forEach((face) => {
|
||||||
|
if (face.cap !== 'none' && face.face_id && parent) {
|
||||||
|
this.artifactMap[face.face_id] = {
|
||||||
|
...parent,
|
||||||
|
commandType: 'solid3d_get_extrusion_face_info',
|
||||||
|
additionalData: {
|
||||||
|
type: 'cap',
|
||||||
|
info: face.cap === 'bottom' ? 'start' : 'end',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const curveArtifact = this.artifactMap[face?.curve_id || '']
|
||||||
|
if (curveArtifact && face?.face_id) {
|
||||||
|
this.artifactMap[face.face_id] = {
|
||||||
|
...curveArtifact,
|
||||||
|
commandType: 'solid3d_get_extrusion_face_info',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
resolve({
|
resolve({
|
||||||
id,
|
id,
|
||||||
commandType: command.commandType,
|
commandType: command.commandType,
|
||||||
@ -1388,12 +1420,6 @@ export class EngineCommandManager {
|
|||||||
const promise = new Promise((_resolve, reject) => {
|
const promise = new Promise((_resolve, reject) => {
|
||||||
resolve = _resolve
|
resolve = _resolve
|
||||||
})
|
})
|
||||||
const getParentId = (): string | undefined => {
|
|
||||||
if (command.type === 'extend_path') {
|
|
||||||
return command.path
|
|
||||||
}
|
|
||||||
// TODO handle other commands that have a parent
|
|
||||||
}
|
|
||||||
const pathToNode = ast
|
const pathToNode = ast
|
||||||
? getNodePathFromSourceRange(ast, range || [0, 0])
|
? getNodePathFromSourceRange(ast, range || [0, 0])
|
||||||
: []
|
: []
|
||||||
@ -1402,7 +1428,6 @@ export class EngineCommandManager {
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
type: 'pending',
|
type: 'pending',
|
||||||
commandType: command.type,
|
commandType: command.type,
|
||||||
parentId: getParentId(),
|
|
||||||
promise,
|
promise,
|
||||||
resolve,
|
resolve,
|
||||||
}
|
}
|
||||||
@ -1419,10 +1444,14 @@ export class EngineCommandManager {
|
|||||||
resolve = _resolve
|
resolve = _resolve
|
||||||
})
|
})
|
||||||
const getParentId = (): string | undefined => {
|
const getParentId = (): string | undefined => {
|
||||||
if (command.type === 'extend_path') {
|
if (command.type === 'extend_path') return command.path
|
||||||
return command.path
|
if (command.type === 'solid3d_get_extrusion_face_info') {
|
||||||
|
const edgeArtifact = this.artifactMap[command.edge_id]
|
||||||
|
// edges's parent id is to the original "start_path" artifact
|
||||||
|
if (edgeArtifact?.parentId) return edgeArtifact.parentId
|
||||||
}
|
}
|
||||||
// TODO handle other commands that have a parent
|
if (command.type === 'close_path') return command.path_id
|
||||||
|
// handle other commands that have a parent here
|
||||||
}
|
}
|
||||||
const pathToNode = ast
|
const pathToNode = ast
|
||||||
? getNodePathFromSourceRange(ast, range || [0, 0])
|
? getNodePathFromSourceRange(ast, range || [0, 0])
|
||||||
|
@ -1155,11 +1155,14 @@ export function addTagForSketchOnFace(
|
|||||||
a: ModifyAstBase,
|
a: ModifyAstBase,
|
||||||
expressionName: string
|
expressionName: string
|
||||||
) {
|
) {
|
||||||
|
if (expressionName === 'close') {
|
||||||
|
return addTag(1)(a)
|
||||||
|
}
|
||||||
if (expressionName in sketchLineHelperMap) {
|
if (expressionName in sketchLineHelperMap) {
|
||||||
const { addTag } = sketchLineHelperMap[expressionName]
|
const { addTag } = sketchLineHelperMap[expressionName]
|
||||||
return addTag(a)
|
return addTag(a)
|
||||||
}
|
}
|
||||||
throw new Error('not a sketch line helper')
|
throw new Error(`"${expressionName}" is not a sketch line helper`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAngleLiteral(lineArugement: Value): boolean {
|
function isAngleLiteral(lineArugement: Value): boolean {
|
||||||
@ -1174,7 +1177,7 @@ function isAngleLiteral(lineArugement: Value): boolean {
|
|||||||
|
|
||||||
type addTagFn = (a: ModifyAstBase) => { modifiedAst: Program; tag: string }
|
type addTagFn = (a: ModifyAstBase) => { modifiedAst: Program; tag: string }
|
||||||
|
|
||||||
function addTag(): addTagFn {
|
function addTag(tagIndex = 2): addTagFn {
|
||||||
return ({ node, pathToNode }) => {
|
return ({ node, pathToNode }) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
|
const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
|
||||||
@ -1184,12 +1187,12 @@ function addTag(): addTagFn {
|
|||||||
)
|
)
|
||||||
// Tag is always 3rd expression now, using arg index feels brittle
|
// Tag is always 3rd expression now, using arg index feels brittle
|
||||||
// but we can come up with a better way to identify tag later.
|
// but we can come up with a better way to identify tag later.
|
||||||
const thirdArg = primaryCallExp.arguments?.[2]
|
const thirdArg = primaryCallExp.arguments?.[tagIndex]
|
||||||
const tagLiteral =
|
const tagLiteral =
|
||||||
thirdArg || (createLiteral(findUniqueName(_node, 'seg', 2)) as Literal)
|
thirdArg || (createLiteral(findUniqueName(_node, 'seg', 2)) as Literal)
|
||||||
const isTagExisting = !!thirdArg
|
const isTagExisting = !!thirdArg
|
||||||
if (!isTagExisting) {
|
if (!isTagExisting) {
|
||||||
primaryCallExp.arguments[2] = tagLiteral
|
primaryCallExp.arguments[tagIndex] = tagLiteral
|
||||||
}
|
}
|
||||||
if ('value' in tagLiteral) {
|
if ('value' in tagLiteral) {
|
||||||
// Now TypeScript knows tagLiteral has a value property
|
// Now TypeScript knows tagLiteral has a value property
|
||||||
|
@ -66,6 +66,7 @@ export type { Position } from '../wasm-lib/kcl/bindings/Position'
|
|||||||
export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
|
export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
|
||||||
export type { Path } from '../wasm-lib/kcl/bindings/Path'
|
export type { Path } from '../wasm-lib/kcl/bindings/Path'
|
||||||
export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
|
export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
|
||||||
|
export type { ExtrudeGroup } from '../wasm-lib/kcl/bindings/ExtrudeGroup'
|
||||||
export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
||||||
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ export interface MouseGuard {
|
|||||||
rotate: MouseGuardHandler
|
rotate: MouseGuardHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
const butName = (e: React.MouseEvent) => ({
|
export const butName = (e: React.MouseEvent) => ({
|
||||||
middle: !!(e.buttons & 4) || e.button === 1,
|
middle: !!(e.buttons & 4) || e.button === 1,
|
||||||
right: !!(e.buttons & 2) || e.button === 2,
|
right: !!(e.buttons & 2) || e.button === 2,
|
||||||
left: !!(e.buttons & 1) || e.button === 0,
|
left: !!(e.buttons & 1) || e.button === 0,
|
||||||
|
@ -117,7 +117,7 @@ export const modelingMachineConfig: CommandSetConfig<
|
|||||||
args: {
|
args: {
|
||||||
selection: {
|
selection: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
selectionTypes: ['face'],
|
selectionTypes: ['extrude-wall', 'start-cap', 'end-cap'],
|
||||||
multiple: false, // TODO: multiple selection
|
multiple: false, // TODO: multiple selection
|
||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: true,
|
||||||
|
@ -6,7 +6,7 @@ import { v4 as uuidv4 } from 'uuid'
|
|||||||
import { EditorSelection } from '@codemirror/state'
|
import { EditorSelection } from '@codemirror/state'
|
||||||
import { kclManager } from 'lang/KclSingleton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
import { SelectionRange } from '@uiw/react-codemirror'
|
import { SelectionRange } from '@uiw/react-codemirror'
|
||||||
import { isOverlap } from 'lib/utils'
|
import { getNormalisedCoordinates, isOverlap } from 'lib/utils'
|
||||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
import { Program } from 'lang/wasm'
|
import { Program } from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
@ -22,70 +22,12 @@ import {
|
|||||||
getParentGroup,
|
getParentGroup,
|
||||||
PROFILE_START,
|
PROFILE_START,
|
||||||
} from 'clientSideScene/sceneEntities'
|
} from 'clientSideScene/sceneEntities'
|
||||||
import { Mesh } from 'three'
|
import { Mesh, Object3D, Object3DEventMap } from 'three'
|
||||||
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
|
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
|
||||||
|
|
||||||
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
||||||
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
||||||
|
|
||||||
/*
|
|
||||||
How selections work is complex due to the nature that we rely on the engine
|
|
||||||
to tell what has been selected after we send a click command. But than the
|
|
||||||
app needs these selections to be based on cursors, therefore the app must
|
|
||||||
be in control of selections. On top of that because we need to set cursor
|
|
||||||
positions in code-mirror for selections, both from app logic, and still
|
|
||||||
allow the user to add multiple cursors like a normal editor, it's best to
|
|
||||||
let code mirror control cursor positions and associate those source ranges
|
|
||||||
with entity ids from code-mirror events later.
|
|
||||||
|
|
||||||
So it's a lot of back and forth. conceptually the back and forth is:
|
|
||||||
|
|
||||||
1) we send a click command to the engine
|
|
||||||
2) the engine sends back ids of entities that were clicked
|
|
||||||
3) we associate that source ranges with those ids
|
|
||||||
4) we set the codemirror selection based on those source ranges (taking
|
|
||||||
into account if the user is holding shift to add to current selections
|
|
||||||
or not). we also create and remember a SelectionRangeTypeMap
|
|
||||||
5) Code mirror fires a an event that cursors have changed, we loop through
|
|
||||||
these ranges and associate them with entity ids again with the ArtifactMap,
|
|
||||||
but also we can pick up selection types using the SelectionRangeTypeMap
|
|
||||||
6) we clear all previous selections in the engine and set the new ones
|
|
||||||
|
|
||||||
The above is less likely to get stale but below is some more details,
|
|
||||||
because this wonders all over the code-base, I've tried to centeralise it
|
|
||||||
by putting relevant utils in this file. All of the functions below are
|
|
||||||
pure with the exception of getEventForSelectWithPoint which makes a call
|
|
||||||
to the engine, but it's a query call (not mutation) so I'm okay with this.
|
|
||||||
Actual side effects that change cursors or tell the engine what's selected
|
|
||||||
are still done throughout the in their relevant parts in the codebase.
|
|
||||||
|
|
||||||
In detail:
|
|
||||||
|
|
||||||
1) Click commands are mostly sent in stream.tsx search for
|
|
||||||
"select_with_point"
|
|
||||||
2) The handler for when the engine sends back entity ids calls
|
|
||||||
getEventForSelectWithPoint, it fires an XState event to update our
|
|
||||||
selections is xstate context
|
|
||||||
3 and 4) The XState handler for the above uses handleSelectionBatch and
|
|
||||||
handleSelectionWithShift to update the selections in xstate context as
|
|
||||||
well as returning our SelectionRangeTypeMap and a codeMirror specific
|
|
||||||
event to be dispatched.
|
|
||||||
5) The codeMirror handler for changes to the cursor uses
|
|
||||||
processCodeMirrorRanges to associate the ranges back with their original
|
|
||||||
types and the entity ids (the id can vary depending on the type, as
|
|
||||||
there's only one source range for a given segment, but depending on if
|
|
||||||
the user selected the segment directly or the vertex, the id will be
|
|
||||||
different)
|
|
||||||
6) We take all of the ids and create events for the engine with
|
|
||||||
resetAndSetEngineEntitySelectionCmds
|
|
||||||
|
|
||||||
An important note is that if a user changes the cursor directly themselves
|
|
||||||
then they skip directly to step 5, And these selections get a type of
|
|
||||||
"default".
|
|
||||||
|
|
||||||
There are a few more nuances than this, but best to find them in the code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
|
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
|
||||||
|
|
||||||
export type Selection = {
|
export type Selection = {
|
||||||
@ -93,7 +35,9 @@ export type Selection = {
|
|||||||
| 'default'
|
| 'default'
|
||||||
| 'line-end'
|
| 'line-end'
|
||||||
| 'line-mid'
|
| 'line-mid'
|
||||||
| 'face'
|
| 'extrude-wall'
|
||||||
|
| 'start-cap'
|
||||||
|
| 'end-cap'
|
||||||
| 'point'
|
| 'point'
|
||||||
| 'edge'
|
| 'edge'
|
||||||
| 'line'
|
| 'line'
|
||||||
@ -106,15 +50,6 @@ export type Selections = {
|
|||||||
codeBasedSelections: Selection[]
|
codeBasedSelections: Selection[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectionRangeTypeMap {
|
|
||||||
[key: number]: Selection['type']
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RangeAndId {
|
|
||||||
id: string
|
|
||||||
range: SourceRange
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getEventForSelectWithPoint(
|
export async function getEventForSelectWithPoint(
|
||||||
{
|
{
|
||||||
data,
|
data,
|
||||||
@ -139,8 +74,32 @@ export async function getEventForSelectWithPoint(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const sourceRange = engineCommandManager.artifactMap[data.entity_id]?.range
|
const _artifact = engineCommandManager.artifactMap[data.entity_id]
|
||||||
if (engineCommandManager.artifactMap[data.entity_id]) {
|
const sourceRange = _artifact?.range
|
||||||
|
if (_artifact) {
|
||||||
|
if (_artifact.commandType === 'solid3d_get_extrusion_face_info') {
|
||||||
|
if (_artifact?.additionalData)
|
||||||
|
return {
|
||||||
|
type: 'Set selection',
|
||||||
|
data: {
|
||||||
|
selectionType: 'singleCodeCursor',
|
||||||
|
selection: {
|
||||||
|
range: sourceRange,
|
||||||
|
type:
|
||||||
|
_artifact?.additionalData.info === 'end'
|
||||||
|
? 'end-cap'
|
||||||
|
: 'start-cap',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'Set selection',
|
||||||
|
data: {
|
||||||
|
selectionType: 'singleCodeCursor',
|
||||||
|
selection: { range: sourceRange, type: 'extrude-wall' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
type: 'Set selection',
|
type: 'Set selection',
|
||||||
data: {
|
data: {
|
||||||
@ -148,46 +107,17 @@ export async function getEventForSelectWithPoint(
|
|||||||
selection: { range: sourceRange, type: 'default' },
|
selection: { range: sourceRange, type: 'default' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if (!sketchEnginePathId) return null
|
// if we don't recognise the entity, select nothing
|
||||||
// selected a vertex
|
|
||||||
const res = await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'path_get_curve_uuids_for_vertices',
|
|
||||||
vertex_ids: [data.entity_id],
|
|
||||||
path_id: sketchEnginePathId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const curveIds = res?.data?.data?.curve_ids
|
|
||||||
const ranges: RangeAndId[] = curveIds
|
|
||||||
.map(
|
|
||||||
(id: string): RangeAndId => ({
|
|
||||||
id,
|
|
||||||
range: engineCommandManager.artifactMap[id].range,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.sort((a: RangeAndId, b: RangeAndId) => a.range[0] - b.range[0])
|
|
||||||
// default to the head of the curve selected
|
|
||||||
const _sourceRange = ranges?.[0].range
|
|
||||||
const artifact = engineCommandManager.artifactMap[ranges?.[0]?.id]
|
|
||||||
if (artifact.type === 'result') {
|
|
||||||
artifact.headVertexId = data.entity_id
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
type: 'Set selection',
|
type: 'Set selection',
|
||||||
data: {
|
data: { selectionType: 'singleCodeCursor' },
|
||||||
selectionType: 'singleCodeCursor',
|
}
|
||||||
// line-end is used to indicate that headVertexId should be sent to the engine as "selected"
|
|
||||||
// not the whole curve
|
|
||||||
selection: { range: _sourceRange, type: 'line-end' },
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEventForSegmentSelection(
|
export function getEventForSegmentSelection(
|
||||||
obj: any
|
obj: Object3D<Object3DEventMap>
|
||||||
): ModelingMachineEvent | null {
|
): ModelingMachineEvent | null {
|
||||||
const group = getParentGroup(obj, [
|
const group = getParentGroup(obj, [
|
||||||
STRAIGHT_SEGMENT,
|
STRAIGHT_SEGMENT,
|
||||||
@ -231,107 +161,54 @@ export function handleSelectionBatch({
|
|||||||
}: {
|
}: {
|
||||||
selections: Selections
|
selections: Selections
|
||||||
}): {
|
}): {
|
||||||
selectionRangeTypeMap: SelectionRangeTypeMap
|
engineEvents: Models['WebSocketRequest_type'][]
|
||||||
codeMirrorSelection?: EditorSelection
|
codeMirrorSelection: EditorSelection
|
||||||
otherSelections: Axis[]
|
otherSelections: Axis[]
|
||||||
|
updateSceneObjectColors: () => void
|
||||||
} {
|
} {
|
||||||
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
|
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
|
||||||
const selectionRangeTypeMap: SelectionRangeTypeMap = {}
|
const engineEvents: Models['WebSocketRequest_type'][] =
|
||||||
|
resetAndSetEngineEntitySelectionCmds(
|
||||||
|
codeToIdSelections(selections.codeBasedSelections)
|
||||||
|
)
|
||||||
selections.codeBasedSelections.forEach(({ range, type }) => {
|
selections.codeBasedSelections.forEach(({ range, type }) => {
|
||||||
if (range?.[1]) {
|
if (range?.[1]) {
|
||||||
ranges.push(EditorSelection.cursor(range[1]))
|
ranges.push(EditorSelection.cursor(range[1]))
|
||||||
selectionRangeTypeMap[range[1]] = type
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (ranges.length)
|
if (ranges.length)
|
||||||
return {
|
return {
|
||||||
selectionRangeTypeMap,
|
engineEvents,
|
||||||
codeMirrorSelection: EditorSelection.create(
|
codeMirrorSelection: EditorSelection.create(
|
||||||
ranges,
|
ranges,
|
||||||
selections.codeBasedSelections.length - 1
|
selections.codeBasedSelections.length - 1
|
||||||
),
|
),
|
||||||
otherSelections: selections.otherSelections,
|
otherSelections: selections.otherSelections,
|
||||||
|
updateSceneObjectColors: () =>
|
||||||
|
updateSceneObjectColors(selections.codeBasedSelections),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectionRangeTypeMap,
|
codeMirrorSelection: EditorSelection.create(
|
||||||
|
[EditorSelection.cursor(kclManager.code.length)],
|
||||||
|
0
|
||||||
|
),
|
||||||
|
engineEvents,
|
||||||
otherSelections: selections.otherSelections,
|
otherSelections: selections.otherSelections,
|
||||||
|
updateSceneObjectColors: () =>
|
||||||
|
updateSceneObjectColors(selections.codeBasedSelections),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleSelectionWithShift({
|
|
||||||
codeSelection,
|
|
||||||
otherSelection,
|
|
||||||
currentSelections,
|
|
||||||
isShiftDown,
|
|
||||||
}: {
|
|
||||||
codeSelection?: Selection
|
|
||||||
otherSelection?: Axis
|
|
||||||
currentSelections: Selections
|
|
||||||
isShiftDown: boolean
|
|
||||||
}): {
|
|
||||||
selectionRangeTypeMap: SelectionRangeTypeMap
|
|
||||||
otherSelections: Axis[]
|
|
||||||
codeMirrorSelection?: EditorSelection
|
|
||||||
} {
|
|
||||||
const code = kclManager.code
|
|
||||||
if (codeSelection && otherSelection) {
|
|
||||||
throw new Error('cannot have both code and other selection')
|
|
||||||
}
|
|
||||||
if (!codeSelection && !otherSelection) {
|
|
||||||
return handleSelectionBatch({
|
|
||||||
selections: {
|
|
||||||
otherSelections: [],
|
|
||||||
codeBasedSelections: [
|
|
||||||
{
|
|
||||||
range: [0, code.length ? code.length : 0],
|
|
||||||
type: 'default',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (otherSelection) {
|
|
||||||
return handleSelectionBatch({
|
|
||||||
selections: {
|
|
||||||
codeBasedSelections: isShiftDown
|
|
||||||
? currentSelections.codeBasedSelections
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
range: [0, code.length ? code.length : 0],
|
|
||||||
type: 'default',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
otherSelections: [otherSelection],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const isEndOfFileDumbySelection =
|
|
||||||
currentSelections.codeBasedSelections.length === 1 &&
|
|
||||||
currentSelections.codeBasedSelections[0].range[0] === kclManager.code.length
|
|
||||||
const newCodeBasedSelections = !isShiftDown
|
|
||||||
? [codeSelection!]
|
|
||||||
: isEndOfFileDumbySelection
|
|
||||||
? [codeSelection!]
|
|
||||||
: [...currentSelections.codeBasedSelections, codeSelection!]
|
|
||||||
const selections: Selections = {
|
|
||||||
otherSelections: isShiftDown ? currentSelections.otherSelections : [],
|
|
||||||
codeBasedSelections: newCodeBasedSelections,
|
|
||||||
}
|
|
||||||
return handleSelectionBatch({ selections })
|
|
||||||
}
|
|
||||||
|
|
||||||
type SelectionToEngine = { type: Selection['type']; id: string }
|
type SelectionToEngine = { type: Selection['type']; id: string }
|
||||||
|
|
||||||
export function processCodeMirrorRanges({
|
export function processCodeMirrorRanges({
|
||||||
codeMirrorRanges,
|
codeMirrorRanges,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
selectionRangeTypeMap,
|
|
||||||
isShiftDown,
|
isShiftDown,
|
||||||
}: {
|
}: {
|
||||||
codeMirrorRanges: readonly SelectionRange[]
|
codeMirrorRanges: readonly SelectionRange[]
|
||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
selectionRangeTypeMap: SelectionRangeTypeMap
|
|
||||||
isShiftDown: boolean
|
isShiftDown: boolean
|
||||||
}): null | {
|
}): null | {
|
||||||
modelingEvent: ModelingMachineEvent
|
modelingEvent: ModelingMachineEvent
|
||||||
@ -349,41 +226,13 @@ export function processCodeMirrorRanges({
|
|||||||
if (!isChange) return null
|
if (!isChange) return null
|
||||||
const codeBasedSelections: Selections['codeBasedSelections'] =
|
const codeBasedSelections: Selections['codeBasedSelections'] =
|
||||||
codeMirrorRanges.map(({ from, to }) => {
|
codeMirrorRanges.map(({ from, to }) => {
|
||||||
if (selectionRangeTypeMap[to]) {
|
|
||||||
return {
|
|
||||||
type: selectionRangeTypeMap[to],
|
|
||||||
range: [from, to],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
type: 'default',
|
type: 'default',
|
||||||
range: [from, to],
|
range: [from, to],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const idBasedSelections: SelectionToEngine[] = codeBasedSelections
|
const idBasedSelections: SelectionToEngine[] =
|
||||||
.flatMap(({ type, range }): null | SelectionToEngine[] => {
|
codeToIdSelections(codeBasedSelections)
|
||||||
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
|
||||||
const entriesWithOverlap = Object.entries(
|
|
||||||
engineCommandManager.artifactMap || {}
|
|
||||||
).filter(([_, artifact]) => {
|
|
||||||
return artifact.range && isOverlap(artifact.range, range)
|
|
||||||
? artifact
|
|
||||||
: false
|
|
||||||
})
|
|
||||||
if (entriesWithOverlap.length) {
|
|
||||||
return entriesWithOverlap.map(([id, artifact]) => ({
|
|
||||||
type,
|
|
||||||
id:
|
|
||||||
type === 'line-end' &&
|
|
||||||
artifact.type === 'result' &&
|
|
||||||
artifact.headVertexId
|
|
||||||
? artifact.headVertexId
|
|
||||||
: id,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
.filter(Boolean) as any
|
|
||||||
|
|
||||||
if (!selectionRanges) return null
|
if (!selectionRanges) return null
|
||||||
updateSceneObjectColors(codeBasedSelections)
|
updateSceneObjectColors(codeBasedSelections)
|
||||||
@ -486,24 +335,21 @@ export type CommonASTNode = {
|
|||||||
ast: Program
|
ast: Program
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildCommonNodeFromSelection(
|
function buildCommonNodeFromSelection(selectionRanges: Selections, i: number) {
|
||||||
selectionRanges: Selections,
|
|
||||||
i: number
|
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
selection: selectionRanges.codeBasedSelections[i],
|
selection: selectionRanges.codeBasedSelections[i],
|
||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nodeHasExtrude(node: CommonASTNode) {
|
function nodeHasExtrude(node: CommonASTNode) {
|
||||||
return doesPipeHaveCallExp({
|
return doesPipeHaveCallExp({
|
||||||
calleeName: 'extrude',
|
calleeName: 'extrude',
|
||||||
...node,
|
...node,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nodeHasClose(node: CommonASTNode) {
|
function nodeHasClose(node: CommonASTNode) {
|
||||||
return doesPipeHaveCallExp({
|
return doesPipeHaveCallExp({
|
||||||
calleeName: 'close',
|
calleeName: 'close',
|
||||||
...node,
|
...node,
|
||||||
@ -521,7 +367,7 @@ export function canExtrudeSelection(selection: Selections) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canExtrudeSelectionItem(selection: Selections, i: number) {
|
function canExtrudeSelectionItem(selection: Selections, i: number) {
|
||||||
const commonNode = buildCommonNodeFromSelection(selection, i)
|
const commonNode = buildCommonNodeFromSelection(selection, i)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -547,7 +393,7 @@ export function getSelectionType(
|
|||||||
return selection.codeBasedSelections
|
return selection.codeBasedSelections
|
||||||
.map((s, i) => {
|
.map((s, i) => {
|
||||||
if (canExtrudeSelectionItem(selection, i)) {
|
if (canExtrudeSelectionItem(selection, i)) {
|
||||||
return ['face', 1] as ResolvedSelectionType // This is implicitly determining what a face is, which is bad
|
return ['extrude-wall', 1] as ResolvedSelectionType // This is implicitly determining what a face is, which is bad
|
||||||
} else {
|
} else {
|
||||||
return ['other', 1] as ResolvedSelectionType
|
return ['other', 1] as ResolvedSelectionType
|
||||||
}
|
}
|
||||||
@ -590,3 +436,100 @@ export function canSubmitSelectionArg(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function codeToIdSelections(
|
||||||
|
codeBasedSelections: Selection[]
|
||||||
|
): SelectionToEngine[] {
|
||||||
|
return codeBasedSelections
|
||||||
|
.flatMap(({ type, range, ...rest }): null | SelectionToEngine[] => {
|
||||||
|
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
||||||
|
const entriesWithOverlap = Object.entries(
|
||||||
|
engineCommandManager.artifactMap || {}
|
||||||
|
)
|
||||||
|
.map(([id, artifact]) => {
|
||||||
|
return artifact.range && isOverlap(artifact.range, range)
|
||||||
|
? {
|
||||||
|
artifact,
|
||||||
|
selection: { type, range, ...rest },
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
let bestCandidate
|
||||||
|
entriesWithOverlap.forEach((entry) => {
|
||||||
|
if (!entry) return
|
||||||
|
if (
|
||||||
|
type === 'default' &&
|
||||||
|
entry.artifact.commandType === 'extend_path'
|
||||||
|
) {
|
||||||
|
bestCandidate = entry
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
type === 'start-cap' &&
|
||||||
|
entry.artifact.commandType === 'solid3d_get_extrusion_face_info' &&
|
||||||
|
entry?.artifact?.additionalData?.info === 'start'
|
||||||
|
) {
|
||||||
|
bestCandidate = entry
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
type === 'end-cap' &&
|
||||||
|
entry.artifact.commandType === 'solid3d_get_extrusion_face_info' &&
|
||||||
|
entry?.artifact?.additionalData?.info === 'end'
|
||||||
|
) {
|
||||||
|
bestCandidate = entry
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
type === 'extrude-wall' &&
|
||||||
|
entry.artifact.commandType === 'solid3d_get_extrusion_face_info'
|
||||||
|
) {
|
||||||
|
bestCandidate = entry
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (bestCandidate) {
|
||||||
|
const _bestCandidate = bestCandidate as {
|
||||||
|
artifact: any
|
||||||
|
selection: any
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
id: _bestCandidate.id,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
.filter(Boolean) as any
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendSelectEventToEngine(
|
||||||
|
e: MouseEvent | React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||||
|
el: HTMLVideoElement,
|
||||||
|
streamDimensions: { streamWidth: number; streamHeight: number }
|
||||||
|
) {
|
||||||
|
const { x, y } = getNormalisedCoordinates({
|
||||||
|
clientX: e.clientX,
|
||||||
|
clientY: e.clientY,
|
||||||
|
el,
|
||||||
|
...streamDimensions,
|
||||||
|
})
|
||||||
|
const result: Promise<Models['SelectWithPoint_type']> = engineCommandManager
|
||||||
|
.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'select_with_point',
|
||||||
|
selected_at_window: { x, y },
|
||||||
|
selection_type: 'add',
|
||||||
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
})
|
||||||
|
.then((res) => res.data.data)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
import { PathToNode, VariableDeclarator } from 'lang/wasm'
|
import { PathToNode, VariableDeclarator } from 'lang/wasm'
|
||||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
import { Axis, Selection, Selections } from 'lib/selections'
|
||||||
import {
|
|
||||||
Axis,
|
|
||||||
Selection,
|
|
||||||
SelectionRangeTypeMap,
|
|
||||||
Selections,
|
|
||||||
} from 'lib/selections'
|
|
||||||
import { assign, createMachine } from 'xstate'
|
import { assign, createMachine } from 'xstate'
|
||||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { kclManager } from 'lang/KclSingleton'
|
import { kclManager } from 'lang/KclSingleton'
|
||||||
import {
|
import {
|
||||||
@ -26,7 +19,6 @@ import {
|
|||||||
} from 'components/Toolbar/EqualLength'
|
} from 'components/Toolbar/EqualLength'
|
||||||
import { addStartProfileAt, extrudeSketch } from 'lang/modifyAst'
|
import { addStartProfileAt, extrudeSketch } from 'lang/modifyAst'
|
||||||
import { getNodeFromPath } from '../lang/queryAst'
|
import { getNodeFromPath } from '../lang/queryAst'
|
||||||
import { CallExpression, PipeExpression } from '../lang/wasm'
|
|
||||||
import {
|
import {
|
||||||
applyConstraintEqualAngle,
|
applyConstraintEqualAngle,
|
||||||
equalAngleInfo,
|
equalAngleInfo,
|
||||||
@ -45,10 +37,10 @@ import { ModelingCommandSchema } from 'lib/commandBarConfigs/modelingCommandConf
|
|||||||
import {
|
import {
|
||||||
DefaultPlaneStr,
|
DefaultPlaneStr,
|
||||||
sceneEntitiesManager,
|
sceneEntitiesManager,
|
||||||
quaternionFromSketchGroup,
|
|
||||||
sketchGroupFromPathToNode,
|
|
||||||
} from 'clientSideScene/sceneEntities'
|
} from 'clientSideScene/sceneEntities'
|
||||||
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
||||||
|
import { Vector3 } from 'three'
|
||||||
|
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
||||||
|
|
||||||
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
||||||
|
|
||||||
@ -70,6 +62,13 @@ export type SetSelections =
|
|||||||
selection: Selections
|
selection: Selections
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SketchDetails {
|
||||||
|
sketchPathToNode: PathToNode
|
||||||
|
zAxis: [number, number, number]
|
||||||
|
yAxis: [number, number, number]
|
||||||
|
origin: [number, number, number]
|
||||||
|
}
|
||||||
|
|
||||||
export type ModelingMachineEvent =
|
export type ModelingMachineEvent =
|
||||||
| {
|
| {
|
||||||
type: 'Enter sketch'
|
type: 'Enter sketch'
|
||||||
@ -77,9 +76,24 @@ export type ModelingMachineEvent =
|
|||||||
forceNewSketch?: boolean
|
forceNewSketch?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
| { type: 'Sketch On Face' }
|
||||||
| {
|
| {
|
||||||
type: 'Select default plane'
|
type: 'Select default plane'
|
||||||
data: { plane: DefaultPlaneStr; normal: [number, number, number] }
|
data: {
|
||||||
|
zAxis: [number, number, number]
|
||||||
|
yAxis: [number, number, number]
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
type: 'defaultPlane'
|
||||||
|
plane: DefaultPlaneStr
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'extrudeFace'
|
||||||
|
position: [number, number, number]
|
||||||
|
extrudeSegmentPathToNode: PathToNode
|
||||||
|
cap: 'start' | 'end' | 'none'
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
| { type: 'Set selection'; data: SetSelections }
|
| { type: 'Set selection'; data: SetSelections }
|
||||||
| { type: 'Sketch no face' }
|
| { type: 'Sketch no face' }
|
||||||
@ -109,18 +123,15 @@ export type ModelingMachineEvent =
|
|||||||
| { type: 'Equip Line tool' }
|
| { type: 'Equip Line tool' }
|
||||||
| { type: 'Equip tangential arc to' }
|
| { type: 'Equip tangential arc to' }
|
||||||
| {
|
| {
|
||||||
type: 'done.invoke.animate-to-face'
|
type: 'done.invoke.animate-to-face' | 'done.invoke.animate-to-sketch'
|
||||||
data: {
|
data: SketchDetails
|
||||||
sketchPathToNode: PathToNode
|
|
||||||
sketchNormalBackUp: [number, number, number] | null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MoveDesc = { line: number; snippet: string }
|
export type MoveDesc = { line: number; snippet: string }
|
||||||
|
|
||||||
export const modelingMachine = createMachine(
|
export const modelingMachine = createMachine(
|
||||||
{
|
{
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBLWFJDWV5hVSZZTrlBUKjS1FZiUrDeWVDasaQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iaF07bCNLiRYlGiZUyZDbFYQzfIaLSQ5Sw0TGZQnM6eNodLoEW73J6wV7sD5UQyRZisMbcP4IQRrGiSdRmfIqUxrCrQlbKcoQ4RHDTWUx5DHNLGXXHXPjsB4AVwwX0psXGCSEFThkNEymFGlECkM0MM+0ZMgUa1MwuypgRhlFHlaEpufBYD3YiuiVN+qrpdg0klMwkFaU0ermwuhwlUkiBhxUyKUazt5za3mJH2IZEomFTb0+9BGnpVoESRrNkmMga0ahMplkhqOZVL+sM4ksCj1SfFOZJ70k3Y+AEkrj0AkEugNwvnvoWad7DIGyuqOe2rPNDRCLIKaKJa6JBXrRJ2Hf3eyeh7jkCRXn0oABbMB3fwAN0enHIJEw7p+Rf4RhkpkbapNEjTRUgKfQjA0FsUnBOQJHyJQNCPC4Tz7NN3nPbpL2vII7wfAJ3lQB5sAAL24dgPy-Gd4mLP9A0kVQzW3I18mRGRDXEI1yl1LJDBWYwKhkZCU3QtDc0w4huFgGUSDwfxCOIsi7g-fwIGwaTMzAKjlVnWiECsPdNzSGgqnSAD1GhNJ2QsCpRHBXUAJbYSxJ7FzB2HIgpJkuSX1dbB30wVT1IoigtKnJVqRo399OWBR-TyFdlCgtRBUs1lymsAVYRjPdnNQs8PK8h5ZNwfwAEEACFvH8AANbTItpKx21mcQxEMGxOP-NJLJ1eL41gndagaVxTjFY9RIK3FPNwaTirkyrqoATXqr09Ka7ZrDsZQ5H2ZQtXYiDijUOKgVsvi1ErPKJvQiTptmkr-DIKAuhWn8S3SKQQRBU15H1YRTEsuy4QPbKtX+gMrtzNyMMKmbvNKrp8HYPMKQ9HSove1rpD21QANkTjREsw4Tu1KD-oRBNhEh1zJu6O74f8JhHiZ3A1PIWVMBIJ41I00LXt06KrFNWYLU6jIjlUZRLP1P1+SqAMgQRDQxGpj5oduoqHoU0jyI-TA9EenAoCGcK0YaudlhkJlQ3bAEMhWQ1UR5f9Q1hfItRKVXTxu2H7p819-L1g2P2wY3+YxujzByU1-qdsxUUNbbtj1AMqjWf9HGGpp7RQ67xN9hnYFwEgmH8dhUFq8PGs4qRlhBNQA2qMxLOyK2ZDkeZ5m3Rx2699WC7m0qi5LsuK+W03vwF97oMyfjYVa4wNEs9qZj3Hud3nOYcj72nJLhwf-DAABHWUVMRqBkari3a3KFf-qyPIJHAop51bFqTHBbccdMHefamzW5JMC5nragE9qLV3yBYdu-EdzpE3oaWsMwTCcUOBaTIahDwjUxONKGu96YHweGAW8qAXz+HIAAu4sAr5rWsHFQ41h9qaH2DoQ6RxOIWD4rYbIYIzRCSwWNXOuC-7dAAEpgEEGAPgIRZT3GoYLOoUgWyGFrKkdIYgE6HXUFbdIqRkSdwTLafhOcRJCPzpKE+2BS4ABk8BgFHqgT8YD0aNQUNZCQKgbDCnSBZTRdReSK3bnuPaRpf5mJuBY0uIUYB3GwCpLm5BR5yMSFtOKIIPZ8V+go+slhoxrFUJkhESU+5lQAO6yQIkRHWylAo8xCpQfweAABmqACAQG4GAdouAnyoFeJIGA7BBDayUhRTAggmmoCSYgYyfpCkZADGYBW4hLJQXMO2U0aROEdypkY5M0NJClPKfJSpwyVK1M0g03AzSCCPAeERSQTBObsGaQ8W8fS-CDOObrUZ4zJkIGMtsI0NoWy6lNAGSy31Nw0FUCYPIkZDHZ12ahA5HBnwBwCkFXm9TxmtPaZ07pvT+mCF8m+D8YzLkTKcebPSGyrYtkUPYZEyj8hpSUEyMQcg7ClCBMUspKLiWBxqcFc52Kbl3IeSQJ5RFXmEv5QFMlzTfk0qZNuMQAExCaDSoKcsONVFaOZDyw5C1aoXKuW03AHS8D4o6YSkgAAjWAgg+DyopajSeEc-koNyTIWCcg6jrEOv+ba5YMhQSSrIbatQDUoqNTVE1LTRUPHuY8550r3l2odU6n5lLVrRQ2TMHIIIlA7m3FoHqfFpB6j2pxbIgYf47K7KJZFAQjWLTjTi81eKenWrTfawQehnWKs9XUeQFRbCIN1NCVsyIRauLsnqVsuV604Nck28qVV-CtpFQ8W5ibxWSpeW8gZ6a+0DuzW9KZnEkGsVassFYobJ18T9FC9k20do3qjQEJ6XQ21motV0rth7BBfvEVm114DvR5pSEaOocgLQaKKHBHkKi7JGgqGaWEH7Hr4G-VundSaJUpsA8B09YHnEQc4idbITUrAN3nIDec5YWzyFSHO5Y2yEUNqhqu8+yMf24stQBwlPH3gkYLGR6lBMWpWTJluZlAaOQWBWJelQ84rCYeE22hN+H92poGcJ0T05xO5s9cYGF+o1jKwOghpKVt5i0OtGIFE6Il2CJXbygITMHgszZhzLmGK6l2Oxb+ztBL3mee8-5XzDxBBnNCgZiKObkmW3ikoawr706EwDdUbYC85h7Tg76zD4WHw+c5tzIVoVNPbrFcmqVgHius0i2VmLFXKDxbNolqZyXhSpbmNA7LllsrSH5HlncwpCsuZMa5Gx5r7GYBHH0cccRFXeqjjRo4mQjh5CWQGgM5YesmVoYxPhHHl1qxm3Y8uDi8QRLLk9fCsTArxMSWeqeUzYR+n2GYcE6h5ZS0OgrFqrVHBZHZH3C7c3JADlwBwAgK2VBMjSRt60sGuT6kkMGWskJWr-TSOD2xkPoew7JKRqlub8hWzsqaR9NHtQRn+rMFYEhbBakhNufHs2ruYEkLgKVH4FtjhCMt177rrC1GkKafUvCMhan+0UJKMwFzKz1O1KCnEOeXdQA4yQAA5CuAAFVAeB2CwAIGVCAEBAgUVdIzI3dxFXzD9JqfLgpFhJS5NqFqqcsiKCUAoDXkO9f+EN8b03pBQqONJ51-SmhzAmGDKtrQep-VFF4iLVBpRTQrD7kT9gcOReNUsDyJPWQVDbkT4aYEjPIzLChboqCOeYd55J2JsnJZmJMkhJCOYshwTtUrwBaMts5irg6s507rm1YABV7sxLiQ8BJ5cBf9GF1H89fztrmG1EoLxh3-yV6gkyR93rLC6g7JNvZM-8APfn4vlp1xbsQ65w7hE79FA7jsttaE1oeRuzSMxqFOYdjUaYxPZWUVmCuDSV0E8AAeVwHbT-StX2W8Cn0EHALaUECgPYFgJNjXze30nxkZxMF1BMkyEWGfkQB3DihXDQzpU8UwQnymw+H8F538EaRIEoB6CW1UjAHYI5g805nNV+UEHanMDSRhSNEDAkGUS5DkH9BsEODmEsDgmAOwQuDIGwFvAlVaFHkZkEO6GCwE16Q0K0PuEEHLkEHYMoF+QqEAjIOyB9ShEOm2lSSsDBFBUDHUGchMO0PwF0PFXNWXyW0GEVSBEYw5R3EYkDGhDXnLFWDsHBBWBqG8Jh1MJ0PLkPj4GCh0KJFzAQJCw6R8LMIsNyJ7BsL2gYisFqAtHbjSFhGhCghywSkAN9BcKQhOF5wwHgCiDUKgFb2j0EEWDKFyEUDL3UC0B2yKEGJNC1EsFqCFGMDNGchxDAH6PX3pC0HKGRHmRDGAmhHUEXFbH1GkOMmUS9jWPwPZDihLxMHyQr0OiQQWGtEGlsHmAwwv3ymEQuPdRbAtAxySn1GB19UyxfiZTiNalSGqAAmhMwyGS+X82FXJW+JcTWFmDmUUH5BqFSHBWSABFbChW1HyHhRAMRUbXc1RT8nRViyxSRMMzbyMC0W1T7wUU3wNADTkEZFSCghlnmFsMwxjTjWRLnFcSkDUE6lrG2lMwBl23YV1HjFUHOj1BOxJM4zc0NXXU3VpIS3XyOGSAShyA21SkOinQ2jghHSqChMw2A0FLpOj11O2BWHshKCqDyFrEBjUCZGDAqFUBrzqHUwfAvneBtO1PwN1MZAyFWBVwlLXHkwR3+mUUcG2LlKzhVLO17FXQa1Kz82pMCy1I6x1PbF-3ZDslAgUJKEG3RzlnbAAnVDBw+NEify10wCFOpXsAsCGnbBsHUDWBiKjG5MEmQVtnH1TMn17EbO11zxbNzX2DKEyBKE7ItJ7OcOSD4jvR72721ADy5x5z52bNtPXyFHzXnn-GrFkCs0QDTwUOUUzzNFkC3KbN1wNztxNynOSV1FriUHan1HjxUEmIvKDWp2gyOEjC0GJN6L2UnP3NDO3C311EFAjJ3371YQbEYwOJbDbHP0YMv1n04Fv1HlfKmVDGjBjBUB+nBnXDyH+LNA5AfgMj7nQMgOt2wPQjgIIv0nNHKDqCTyZ1S2lKKCCUZyZTP3jC1C9hYIrisNWKgvdSmDkIkBWVUDqH+i-2cLkKgnsDnQmNNBSM0N8KgH8P0LYr4ikADG1CtHkFp1kJ5BMHUEjDXiONUIETaCKPSIrkkWyL8NKI+CMscAx1hCYlanZE5UnUjF5HsHIMswAnaKwpcr8IyICLsQAApyEmA9AABKNikQCQCXTJRiTQFhIoHhRTF9QJOlZU8C2K-S+K-Q-wZK1AVKtK2qlK9KzKsQT6OQUYvKiYydBESnHcawA0muYUFwFwIAA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBLWFJDWV5hVSZZTrlBUKjS1FZiUrDeWVDasaQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iaFlmZWZSmZRaKyGQzKYQaDYIGSmDSzdKyDSGcQ0KrAk5nTxtDpdAi3e5PWCvdgfKiGSLMVhjbh-BCCaw0WaQrZqdGLAr6RAZGSSWzCZSiIEKNFi5TY5q4y4E658dgPACuGC+NNi4wSQgqM3hNFEwNEGlEYthENELJkCjWplMFpsGlRUo8rVlNz4LAe7DV0Vpvy1jKtCkkVuZMjsDpNsOEwjKUIkcxNqWhomd5za3jJH2IZEomEzb0+9BGfs1oESEOtkmM0K0ahMplkZqOZUrYrRlgUJrTMoL5Pekj7HwAklcegEgl0BuFi99S-SA4ZoWUdWsTSLlsozZlzNkZPrG0a5F2e66hwPz6OCcgSK8+lAALZgO7+ABuj045BImB9PzL-CMeFW2qTQhU0ZMzVRKR0RoVJ6nyJQNFPC5z0HLN3ivbobzvIJH2fAJ3lQB5sAAL24dhv1-ed4nLQDoUkVRrX1CF8ihGQzXECFymNLJIU4-U8mQjN0LQwtMOIbhYEVEg8H8QjiLIu5v38CBsCk3MwCojUF1ohArCNCwNDSDFENMI51h5OEzGUCwKgtbJRDMtEhNE-tXJHMciEk6TZPfL1sC-TAVLUiiKE02d1TpGiAL05ZgztK0RVBYxNHYyzw1qcprCODQY2EZYjRc1DL087yHhk3B-AAQQAIW8fwAA0tKihkrC7JExEMGxOPhNJYXhAy7ShRY4NMWoGlcU5pTPESSoJLzcCk8rZNq+qAE1mv9XS2u2aw7GUOR9mFA7YU5YN8rstlHEdIrZvQ8SFqWir-DIKAuk2-8K2RaQlBA+QxWEUx+otXV1zyg1AbG27C3cjDSsWnzKq6fB2CLalfW06KvvEGYVANDE0TSfZ+sOc7gVRQHHSUKFobcubukexH-CYR4WdwVTyCVTASCeVT1LCj6dJiqwrVmW0eoyI5VC3dKxURWMqjG-LHVy1NJpxGaYfpiSEeWyr5NI8jv0wPQXpwKAhgijGWsXZZ+S7TQu3SI0aBWM0kpDO1QPysQVgmpoXRQu6xPhp7fI-ALjdN79sAtwWsbo8wcitQGkrMEUzQO7YTTGzFqiqWpaY+WGHrK57YFwEgmH8dhUEa+PWs4qRlnERYFDG6ozH6vcQzkeZ5n1RxwyLi97tDpmK6rmu642q2-yFr60QsIVOJjHHjBhdKupmI0h8cpc5hyEeS-HvX-DAABHJVlORqBUYb23G3KbfAayPIJG5Iol3RJETFg-VjqmGPtrRmZ8mA82NtQOe1FG75AsOGFY+xGwU03l-RsMwTCcUOLaTIag1YB3TLDE+80y6yQeGAB8qB3z+HIKQu4sAH7bWsMGQ41gDRWlRCacQZolBlEsMCfcOROQHWAWPAkAAlMAggwB8BCEqe4jDhZ1CkGiVQ+kdQGlhCCGyjk14misGZbIoiQ5yivtgauAAZPAYBp6oB-NAzGrV246JKKCW0CZ1CwiUNnTIe05CJghMY-s4lrhmOrqFGAdxsDKR5uQaeijEj7WDK3A0WD-rKObJYSQYFVDpMdKCY+VUADuMkCJEUNkpIKfNQqUH8HgAAZqgAgEBuBgHaLgV8qBXiSBgOwQQBtFIUUwIIBpqAEmIGMoifJGQxpmCVtw9KqJzBdlDFg2wENCklI4HJcpgzlLVI0nU3AjSCCPAeERSQTBubsEaQ8B8PS-D9N2UbYZozxlWX1NWLqqI0TGitGNfqrd+TZFdrBOorFDCbNKW+COgVgr81qaM5prT2mdO6b0wQflPzfhGccsZDiba6SJvyNEih7BQkMIKfqVopCOHfnYUo+UoXbKxZHKpIVDlIrORcq5JAblEXuRi1lgVcWNPecSyQix5D7zEJoalRlqzHVSK3ECShmUBFWo1I5JyWm4DaXgNFbSMUkAAEawEEHwUV+L0bzwTlZSE2SDqwXDLsCyRR4QHWrBkVEoJZAHULuraaQcYbFOhZqhq2qmncoeJc65tzBWPNNeay1byCVbRikTGYQj1B1FMPqLQ-UzI2TqNaCQG9oRAMDYHYSIatkarqv4NakbkV6tRV0o1iazWCD0Fa8VWDpAaMcvCHGxpTquyzYrC0XD83quqg2ptXKHjnJjby-ldyHl9KTd23tabPoTNXvAle+V5jpEhKdSEiJXbaJdaSmQs7XpdGbbq-VHT20bsEA+qRqabUwIDJmlIEJwWOTtIYYGBoBTogtOaE0UJhD3vwI+xdy7Y18vje+z9O6f2OL-Zxc62Q2pWDUGZT+EyRSAjRPIOC+oASztvqjJ9KKDVvoxXR94mGSzYaJZxbYyczAoP3PkEm7VXaMpUEuKwtHnx33eM26NKG10Jr6ax9jc5OMZv7cYEw+i1i5TSu60E-J5jMIRGIGMBpZ0sweGzDmXMebwpqTYpFz623oseZZ6zAVbMPEEAcsKKnIrpsSXbAUXDrAHTinIfq1RtjrzmMKdxch-ZTWrUQ0N2z3PPhs9zXmHKwqyaXTyuNAr30ZfZp57LPncuUH89bQLEzgtexROFtYkX0p5WkLGOLwH35JY1sGtyVi9W2MwOOPoU44jiv3EnQjRxMhHDyAs91Y1qxewxMwxid6q2ENQoNmxtc7GEjCTXV6+FolBVifE3dC8JkxkRPsMwsF1CKxlkUJWSIcaOCyCCY+u3huSGHLgDgBBJsqElSk2bCI5BA0smsMoaR24PZxoDNIP3rF-YB0DykWHCUZvyPyC0VoL2EeBNGQGswVgSHWcCWC+DkvbZEr9-bmBJC4AFd+Ubk4QgTau3a6wmVZDqClsyA0L3ECghmMuVWcsoKQq272BnaOmeSAAHJ1wAAqoDwOwWABAqoQAgIECiXpmaa7uOK+YiI80GkckZRYoJYTzCTmYV+mQc0KFR0NpXqv-Aa61zr0gYV7HY7q3pTQu5ZBAoxFoE0brEC8TFtg0oVoVjHwx+wYHPPWr8NmF2LIKhqNR7NGkKQcwhTLFBcsJ0cvNZuTT8DqkHGccVmYpKvNea5iyFgl1IvZlslOzmECbqkpq-9eLgAFRO1EmJDw4m1w5-0bnwe91wgOuYYESg7TolsPCIvqJJUXv3JYY03YR81rchP-Ap3p+z6aaEpU5j-CM9QHY83jpf6KEchaE6lkEQ2RjOGOoLIV2UvY+JUdmOudSL0c8AAeVwBbRfUNUkCqm8DH0EDAJaUEEgPYBgMtiX2uz0hKGDDyFqCckdDqDUFhCTAFGwTUFylbhKDg1P1hn8FZ38HqRIEoB6HGxUjAHYK5gCF5T1XeSZC4iOARCRy0ChBsFEAd2WH3zSByEhiFBsBHhYLrnYM4N8AnAX0GHeSFC+R0wkDmWyEOFhAdAgxKDsCUAGhXhcjIGwAfD5VaGnmZm5j1XgJczaXsMcPuEEFrkEA0PCjwLtQqGAldxBSWEyFkOSSsBKFSHbmhHUDsMBx8OcNrlcLIG6C0LGy510Mzz-Xyi+TkCh0YmhEoK0GrFWDsFghWBqGSIcKcPwBcJkRCmcNJELA8KY26W8L5SkX8PaP7HeSzngX9SqH2EUL00QFwwYihFbhBCe1yndxOFZwwHgCiD6ygEbxD0EEWDKFyEUHz3UC0EWyEEWElT3ByE03UCyCQiYPxDAC2OX0EFBCIKhFmU0BNFyi8TFHKE5Hu1iMsErQIXl0LEePwPmJz0o3zzyELxhwYjtDAnZBXm+yYOKjEQeNUybyMAyGbjGlylsA+MBljz0kpTu1RGMj9jz16yDTP2LjSzKQUheXs05TxTBLtSlmDEdAxEUFjBqFSEBWSGdhgkd3yFl2BJrzpLrRhX8jhV80RVZMxJD3MhJVUEsGUVX1A3SjkBZFSFRDlnmFCNnXDUjTZKcSfg5AMRUGqIxELU4h2GGjUVyWOFRJEnpLnXWhNMVOXzEPxw4Vwz3DHWFF7jsjECqHzngzekcwVIC29Pbm2BWFgiESqDyEbGBjUElXhwqFUDLzqEkxRhk1GVNMXHbhZBxOtzFEbH7hJlB0BkpUcDeONBTxdNrWhVKyyzszlKjMaSLO2i7D-xBAtHAhsD9n6jJWyTrP3h1BRPFNHwHCfzsR7IzXsGXkIKNDDLWEoNUBDFREPnX0dGnLpxBIG0V2f2ZzT0XMSX2D4XGi7BsHUA3Jh2SEhBWHyQezmCBMPIlLnJPLsRZzZ0wAvImRsCzTXj1EWFkEmIQHj2HMpST2tFkA9z21PJV3V1N210ArhGNGbl4TBBMDSDdksjLxDHbDRCOGULFFT0B3YAwuMEcgYmNCMhxI3270skrFbHMkA07BPxnNpIHAv0iU4Gv2ngwuPHlmPStNz1tG3DyBDDWAWEow3Fpw2KIXQIgKN2wPQlgJoptF+N3lSVC2hyKCNBsgH1sGP2GnM2bP7DULYI4IxJjPwKZBKAYhNAEyMgM35MsjoJDHYRBBqBWEbHqNSKaPSMEPstq29JWAFCFHbwROJ1kJshMGuOFGOLUEYJ4skB6M4BCrrhaKkjaPQhouW19mFCyAjE4lsCixZAHhLOZGtCtBcBcCAA */
|
||||||
id: 'Modeling',
|
id: 'Modeling',
|
||||||
|
|
||||||
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
||||||
@ -135,11 +146,14 @@ export const modelingMachine = createMachine(
|
|||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
codeBasedSelections: [],
|
codeBasedSelections: [],
|
||||||
} as Selections,
|
} as Selections,
|
||||||
selectionRangeTypeMap: {} as SelectionRangeTypeMap,
|
sketchDetails: {
|
||||||
sketchPathToNode: null as PathToNode | null, // maybe too specific, and we should have a generic pathToNode, but being specific seems less risky when I'm not sure
|
sketchPathToNode: [],
|
||||||
sketchEnginePathId: '' as string,
|
zAxis: [0, 0, 1],
|
||||||
|
yAxis: [0, 1, 0],
|
||||||
|
origin: [0, 0, 0],
|
||||||
|
} as null | SketchDetails,
|
||||||
sketchPlaneId: '' as string,
|
sketchPlaneId: '' as string,
|
||||||
sketchNormalBackUp: null as null | [number, number, number],
|
sketchEnginePathId: '' as string,
|
||||||
moveDescs: [] as MoveDesc[],
|
moveDescs: [] as MoveDesc[],
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -160,7 +174,6 @@ export const modelingMachine = createMachine(
|
|||||||
{
|
{
|
||||||
target: 'animating to existing sketch',
|
target: 'animating to existing sketch',
|
||||||
cond: 'Selection is on face',
|
cond: 'Selection is on face',
|
||||||
actions: ['set sketch metadata'],
|
|
||||||
},
|
},
|
||||||
'Sketch no face',
|
'Sketch no face',
|
||||||
],
|
],
|
||||||
@ -504,6 +517,11 @@ export const modelingMachine = createMachine(
|
|||||||
target: 'animating to plane',
|
target: 'animating to plane',
|
||||||
actions: ['reset sketch metadata'],
|
actions: ['reset sketch metadata'],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Set selection': {
|
||||||
|
target: 'Sketch no face',
|
||||||
|
internal: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -532,15 +550,15 @@ export const modelingMachine = createMachine(
|
|||||||
{
|
{
|
||||||
src: 'animate-to-sketch',
|
src: 'animate-to-sketch',
|
||||||
id: 'animate-to-sketch',
|
id: 'animate-to-sketch',
|
||||||
onDone: 'Sketch',
|
onDone: {
|
||||||
|
target: 'Sketch',
|
||||||
|
actions: 'set new sketch metadata',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
entry: 'clientToEngine cam sync direction',
|
entry: 'clientToEngine cam sync direction',
|
||||||
},
|
},
|
||||||
|
|
||||||
'animating to plane (copy)': {},
|
|
||||||
'animating to plane (copy) (copy)': {},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
initial: 'idle',
|
initial: 'idle',
|
||||||
@ -562,13 +580,13 @@ export const modelingMachine = createMachine(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
guards: {
|
guards: {
|
||||||
'is editing existing sketch': ({ sketchPathToNode }) => {
|
'is editing existing sketch': ({ sketchDetails }) => {
|
||||||
// should check that the variable declaration is a pipeExpression
|
// should check that the variable declaration is a pipeExpression
|
||||||
// and that the pipeExpression contains a "startProfileAt" callExpression
|
// and that the pipeExpression contains a "startProfileAt" callExpression
|
||||||
if (!sketchPathToNode) return false
|
if (!sketchDetails?.sketchPathToNode) return false
|
||||||
const variableDeclaration = getNodeFromPath<VariableDeclarator>(
|
const variableDeclaration = getNodeFromPath<VariableDeclarator>(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
sketchPathToNode,
|
sketchDetails.sketchPathToNode,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
).node
|
).node
|
||||||
if (variableDeclaration.type !== 'VariableDeclarator') return false
|
if (variableDeclaration.type !== 'VariableDeclarator') return false
|
||||||
@ -621,128 +639,154 @@ export const modelingMachine = createMachine(
|
|||||||
},
|
},
|
||||||
// end guards
|
// end guards
|
||||||
actions: {
|
actions: {
|
||||||
'set sketchMetadata from pathToNode': assign(({ sketchPathToNode }) => {
|
'set sketchMetadata from pathToNode': assign(({ sketchDetails }) => {
|
||||||
if (!sketchPathToNode) return {}
|
if (!sketchDetails?.sketchPathToNode || !sketchDetails) return {}
|
||||||
return getSketchMetadataFromPathToNode(sketchPathToNode)
|
return {
|
||||||
|
sketchDetails: {
|
||||||
|
...sketchDetails,
|
||||||
|
sketchPathToNode: sketchDetails.sketchPathToNode,
|
||||||
|
},
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
'hide default planes': () => {
|
'hide default planes': () => {
|
||||||
sceneInfra.removeDefaultPlanes()
|
sceneInfra.removeDefaultPlanes()
|
||||||
kclManager.hidePlanes()
|
kclManager.hidePlanes()
|
||||||
},
|
},
|
||||||
'reset sketch metadata': assign({
|
'reset sketch metadata': assign({
|
||||||
sketchPathToNode: null,
|
sketchDetails: null,
|
||||||
sketchEnginePathId: '',
|
sketchEnginePathId: '',
|
||||||
sketchPlaneId: '',
|
sketchPlaneId: '',
|
||||||
}),
|
}),
|
||||||
'set sketch metadata': assign(({ selectionRanges }) => {
|
'set new sketch metadata': assign((_, { data }) => ({
|
||||||
const sourceRange = selectionRanges.codeBasedSelections[0].range
|
sketchDetails: data,
|
||||||
const sketchPathToNode = getNodePathFromSourceRange(
|
})),
|
||||||
kclManager.ast,
|
|
||||||
sourceRange
|
|
||||||
)
|
|
||||||
return getSketchMetadataFromPathToNode(
|
|
||||||
sketchPathToNode,
|
|
||||||
selectionRanges
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
'set new sketch metadata': assign((_, { data }) => data),
|
|
||||||
// TODO implement source ranges for all of these constraints
|
// TODO implement source ranges for all of these constraints
|
||||||
// need to make the async like the modal constraints
|
// need to make the async like the modal constraints
|
||||||
'Make selection horizontal': ({ selectionRanges, sketchPathToNode }) => {
|
'Make selection horizontal': ({ selectionRanges, sketchDetails }) => {
|
||||||
const { modifiedAst } = applyConstraintHorzVert(
|
const { modifiedAst } = applyConstraintHorzVert(
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
'horizontal',
|
'horizontal',
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
kclManager.programMemory
|
kclManager.programMemory
|
||||||
)
|
)
|
||||||
|
if (!sketchDetails) return
|
||||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
sketchPathToNode || [],
|
sketchDetails.sketchPathToNode,
|
||||||
modifiedAst
|
modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Make selection vertical': ({ selectionRanges, sketchPathToNode }) => {
|
'Make selection vertical': ({ selectionRanges, sketchDetails }) => {
|
||||||
const { modifiedAst } = applyConstraintHorzVert(
|
const { modifiedAst } = applyConstraintHorzVert(
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
'vertical',
|
'vertical',
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
kclManager.programMemory
|
kclManager.programMemory
|
||||||
)
|
)
|
||||||
|
if (!sketchDetails) return
|
||||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
sketchPathToNode || [],
|
sketchDetails.sketchPathToNode || [],
|
||||||
modifiedAst
|
modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Constrain horizontally align': ({
|
'Constrain horizontally align': ({ selectionRanges, sketchDetails }) => {
|
||||||
selectionRanges,
|
|
||||||
sketchPathToNode,
|
|
||||||
}) => {
|
|
||||||
const { modifiedAst } = applyConstraintHorzVertAlign({
|
const { modifiedAst } = applyConstraintHorzVertAlign({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
constraint: 'setVertDistance',
|
constraint: 'setVertDistance',
|
||||||
})
|
})
|
||||||
|
if (!sketchDetails) return
|
||||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
sketchPathToNode || [],
|
sketchDetails?.sketchPathToNode || [],
|
||||||
modifiedAst
|
modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Constrain vertically align': ({ selectionRanges, sketchPathToNode }) => {
|
'Constrain vertically align': ({ selectionRanges, sketchDetails }) => {
|
||||||
const { modifiedAst } = applyConstraintHorzVertAlign({
|
const { modifiedAst } = applyConstraintHorzVertAlign({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
constraint: 'setHorzDistance',
|
constraint: 'setHorzDistance',
|
||||||
})
|
})
|
||||||
|
if (!sketchDetails) return
|
||||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
sketchPathToNode || [],
|
sketchDetails?.sketchPathToNode || [],
|
||||||
modifiedAst
|
modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Constrain snap to X': ({ selectionRanges, sketchPathToNode }) => {
|
'Constrain snap to X': ({ selectionRanges, sketchDetails }) => {
|
||||||
const { modifiedAst } = applyConstraintAxisAlign({
|
const { modifiedAst } = applyConstraintAxisAlign({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
constraint: 'snapToXAxis',
|
constraint: 'snapToXAxis',
|
||||||
})
|
})
|
||||||
|
if (!sketchDetails) return
|
||||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
sketchPathToNode || [],
|
sketchDetails?.sketchPathToNode || [],
|
||||||
modifiedAst
|
modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Constrain snap to Y': ({ selectionRanges, sketchPathToNode }) => {
|
'Constrain snap to Y': ({ selectionRanges, sketchDetails }) => {
|
||||||
const { modifiedAst } = applyConstraintAxisAlign({
|
const { modifiedAst } = applyConstraintAxisAlign({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
constraint: 'snapToYAxis',
|
constraint: 'snapToYAxis',
|
||||||
})
|
})
|
||||||
|
if (!sketchDetails) return
|
||||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
sketchPathToNode || [],
|
sketchDetails?.sketchPathToNode || [],
|
||||||
modifiedAst
|
modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Constrain equal length': ({ selectionRanges, sketchPathToNode }) => {
|
'Constrain equal length': ({ selectionRanges, sketchDetails }) => {
|
||||||
const { modifiedAst } = applyConstraintEqualLength({
|
const { modifiedAst } = applyConstraintEqualLength({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
|
if (!sketchDetails) return
|
||||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
sketchPathToNode || [],
|
sketchDetails?.sketchPathToNode || [],
|
||||||
modifiedAst
|
modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Constrain parallel': ({ selectionRanges, sketchPathToNode }) => {
|
'Constrain parallel': ({ selectionRanges, sketchDetails }) => {
|
||||||
const { modifiedAst } = applyConstraintEqualAngle({
|
const { modifiedAst } = applyConstraintEqualAngle({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
|
if (!sketchDetails) return
|
||||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
sketchPathToNode || [],
|
sketchDetails?.sketchPathToNode || [],
|
||||||
modifiedAst
|
modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Constrain remove constraints': ({
|
'Constrain remove constraints': ({ selectionRanges, sketchDetails }) => {
|
||||||
selectionRanges,
|
|
||||||
sketchPathToNode,
|
|
||||||
}) => {
|
|
||||||
const { modifiedAst } = applyRemoveConstrainingValues({
|
const { modifiedAst } = applyRemoveConstrainingValues({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
|
if (!sketchDetails) return
|
||||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
sketchPathToNode || [],
|
sketchDetails?.sketchPathToNode || [],
|
||||||
modifiedAst
|
modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'AST extrude': (_, event) => {
|
'AST extrude': (_, event) => {
|
||||||
@ -754,7 +798,6 @@ export const modelingMachine = createMachine(
|
|||||||
distance.variableName &&
|
distance.variableName &&
|
||||||
distance.insertIndex !== undefined
|
distance.insertIndex !== undefined
|
||||||
) {
|
) {
|
||||||
console.log('adding variable!', distance)
|
|
||||||
const newBody = [...ast.body]
|
const newBody = [...ast.body]
|
||||||
newBody.splice(
|
newBody.splice(
|
||||||
distance.insertIndex,
|
distance.insertIndex,
|
||||||
@ -785,18 +828,25 @@ export const modelingMachine = createMachine(
|
|||||||
sceneInfra.modelingSend('Equip Line tool')
|
sceneInfra.modelingSend('Equip Line tool')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'setup client side sketch segments': ({ sketchPathToNode }, { type }) => {
|
'setup client side sketch segments': ({ sketchDetails }) => {
|
||||||
|
if (!sketchDetails) return
|
||||||
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
|
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
|
||||||
sceneEntitiesManager
|
sceneEntitiesManager
|
||||||
.tearDownSketch({ removeAxis: false })
|
.tearDownSketch({ removeAxis: false })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
sceneEntitiesManager.setupSketch({
|
sceneEntitiesManager.setupSketch({
|
||||||
sketchPathToNode: sketchPathToNode || [],
|
sketchPathToNode: sketchDetails?.sketchPathToNode || [],
|
||||||
|
forward: sketchDetails.zAxis,
|
||||||
|
up: sketchDetails.yAxis,
|
||||||
|
position: sketchDetails.origin,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
sceneEntitiesManager.setupSketch({
|
sceneEntitiesManager.setupSketch({
|
||||||
sketchPathToNode: sketchPathToNode || [],
|
sketchPathToNode: sketchDetails?.sketchPathToNode || [],
|
||||||
|
forward: sketchDetails.zAxis,
|
||||||
|
up: sketchDetails.yAxis,
|
||||||
|
position: sketchDetails.origin,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -809,43 +859,60 @@ export const modelingMachine = createMachine(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'remove sketch grid': () => sceneEntitiesManager.removeSketchGrid(),
|
'remove sketch grid': () => sceneEntitiesManager.removeSketchGrid(),
|
||||||
'set up draft line': ({ sketchPathToNode }) => {
|
'set up draft line': ({ sketchDetails }) => {
|
||||||
sceneEntitiesManager.setUpDraftLine(sketchPathToNode || [])
|
if (!sketchDetails) return
|
||||||
|
sceneEntitiesManager.setUpDraftLine(
|
||||||
|
sketchDetails.sketchPathToNode,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis
|
||||||
|
)
|
||||||
},
|
},
|
||||||
'set up draft arc': ({ sketchPathToNode }) => {
|
'set up draft arc': ({ sketchDetails }) => {
|
||||||
sceneEntitiesManager.setUpDraftArc(sketchPathToNode || [])
|
if (!sketchDetails) return
|
||||||
|
sceneEntitiesManager.setUpDraftArc(
|
||||||
|
sketchDetails.sketchPathToNode,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis
|
||||||
|
)
|
||||||
},
|
},
|
||||||
'set up draft line without teardown': ({ sketchPathToNode }) =>
|
'set up draft line without teardown': ({ sketchDetails }) =>
|
||||||
sceneEntitiesManager.setupSketch({
|
sceneEntitiesManager.setupSketch({
|
||||||
sketchPathToNode: sketchPathToNode || [],
|
sketchPathToNode: sketchDetails?.sketchPathToNode || [],
|
||||||
draftSegment: 'line',
|
draftSegment: 'line',
|
||||||
|
forward: sketchDetails?.zAxis || [0, 1, 0],
|
||||||
|
up: sketchDetails?.yAxis || [0, 0, 1],
|
||||||
|
position: sketchDetails?.origin,
|
||||||
}),
|
}),
|
||||||
'show default planes': () => {
|
'show default planes': () => {
|
||||||
sceneInfra.showDefaultPlanes()
|
sceneInfra.showDefaultPlanes()
|
||||||
sceneEntitiesManager.setupDefaultPlaneHover()
|
sceneEntitiesManager.setupDefaultPlaneHover()
|
||||||
kclManager.showPlanes()
|
kclManager.showPlanes()
|
||||||
},
|
},
|
||||||
'setup noPoints onClick listener': ({ sketchPathToNode }) => {
|
'setup noPoints onClick listener': ({ sketchDetails }) => {
|
||||||
|
if (!sketchDetails) return
|
||||||
sceneEntitiesManager.createIntersectionPlane()
|
sceneEntitiesManager.createIntersectionPlane()
|
||||||
const sketchGroup = sketchGroupFromPathToNode({
|
const quaternion = quaternionFromUpNForward(
|
||||||
pathToNode: sketchPathToNode || [],
|
new Vector3(...sketchDetails.yAxis),
|
||||||
ast: kclManager.ast,
|
new Vector3(...sketchDetails.zAxis)
|
||||||
programMemory: kclManager.programMemory,
|
)
|
||||||
})
|
|
||||||
const quaternion = quaternionFromSketchGroup(sketchGroup)
|
|
||||||
sceneEntitiesManager.intersectionPlane &&
|
sceneEntitiesManager.intersectionPlane &&
|
||||||
sceneEntitiesManager.intersectionPlane.setRotationFromQuaternion(
|
sceneEntitiesManager.intersectionPlane.setRotationFromQuaternion(
|
||||||
quaternion
|
quaternion
|
||||||
)
|
)
|
||||||
|
sceneEntitiesManager.intersectionPlane &&
|
||||||
|
sceneEntitiesManager.intersectionPlane.position.copy(
|
||||||
|
new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
|
||||||
|
)
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onClick: async (args) => {
|
onClick: async (args) => {
|
||||||
if (!args) return
|
if (!args) return
|
||||||
if (args.mouseEvent.which !== 1) return
|
if (args.mouseEvent.which !== 1) return
|
||||||
const { intersectionPoint } = args
|
const { intersectionPoint } = args
|
||||||
if (!intersectionPoint?.twoD || !sketchPathToNode) return
|
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode)
|
||||||
|
return
|
||||||
const { modifiedAst } = addStartProfileAt(
|
const { modifiedAst } = addStartProfileAt(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
sketchPathToNode,
|
sketchDetails.sketchPathToNode,
|
||||||
[intersectionPoint.twoD.x, intersectionPoint.twoD.y]
|
[intersectionPoint.twoD.x, intersectionPoint.twoD.y]
|
||||||
)
|
)
|
||||||
await kclManager.updateAst(modifiedAst, false)
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
@ -854,8 +921,15 @@ export const modelingMachine = createMachine(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
'add axis n grid': ({ sketchPathToNode }) =>
|
'add axis n grid': ({ sketchDetails }) => {
|
||||||
sceneEntitiesManager.createSketchAxis(sketchPathToNode || []),
|
if (!sketchDetails) return
|
||||||
|
sceneEntitiesManager.createSketchAxis(
|
||||||
|
sketchDetails.sketchPathToNode || [],
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
|
)
|
||||||
|
},
|
||||||
'reset client scene mouse handlers': () => {
|
'reset client scene mouse handlers': () => {
|
||||||
// when not in sketch mode we don't need any mouse listeners
|
// when not in sketch mode we don't need any mouse listeners
|
||||||
// (note the orbit controls are always active though)
|
// (note the orbit controls are always active though)
|
||||||
@ -871,44 +945,3 @@ export const modelingMachine = createMachine(
|
|||||||
// end actions
|
// end actions
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
function getSketchMetadataFromPathToNode(
|
|
||||||
pathToNode: PathToNode,
|
|
||||||
selectionRanges?: Selections
|
|
||||||
) {
|
|
||||||
const pipeExpression = getNodeFromPath<PipeExpression>(
|
|
||||||
kclManager.ast,
|
|
||||||
pathToNode,
|
|
||||||
'PipeExpression'
|
|
||||||
).node
|
|
||||||
if (pipeExpression.type !== 'PipeExpression') return {}
|
|
||||||
const sketchCallExpression = pipeExpression.body.find(
|
|
||||||
(e) => e.type === 'CallExpression' && e.callee.name === 'startSketchOn'
|
|
||||||
) as CallExpression
|
|
||||||
if (!sketchCallExpression) return {}
|
|
||||||
|
|
||||||
let sketchEnginePathId: string
|
|
||||||
if (selectionRanges) {
|
|
||||||
sketchEnginePathId =
|
|
||||||
isCursorInSketchCommandRange(
|
|
||||||
engineCommandManager.artifactMap,
|
|
||||||
selectionRanges
|
|
||||||
) || ''
|
|
||||||
} else {
|
|
||||||
const _selectionRanges: Selections = {
|
|
||||||
otherSelections: [],
|
|
||||||
codeBasedSelections: [
|
|
||||||
{ range: [pipeExpression.start, pipeExpression.end], type: 'default' },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
sketchEnginePathId =
|
|
||||||
isCursorInSketchCommandRange(
|
|
||||||
engineCommandManager.artifactMap,
|
|
||||||
_selectionRanges
|
|
||||||
) || ''
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
sketchPathToNode: pathToNode,
|
|
||||||
sketchEnginePathId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -93,6 +93,8 @@ export interface StoreState {
|
|||||||
path: string
|
path: string
|
||||||
}[]
|
}[]
|
||||||
setHomeMenuItems: (items: { name: string; path: string }[]) => void
|
setHomeMenuItems: (items: { name: string; path: string }[]) => void
|
||||||
|
lastCodeMirrorSelectionUpdatedFromScene: number
|
||||||
|
setLastCodeMirrorSelectionUpdatedFromScene: (time: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useStore = create<StoreState>()(
|
export const useStore = create<StoreState>()(
|
||||||
@ -156,6 +158,9 @@ export const useStore = create<StoreState>()(
|
|||||||
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
|
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
|
||||||
homeMenuItems: [],
|
homeMenuItems: [],
|
||||||
setHomeMenuItems: (homeMenuItems) => set({ homeMenuItems }),
|
setHomeMenuItems: (homeMenuItems) => set({ homeMenuItems }),
|
||||||
|
lastCodeMirrorSelectionUpdatedFromScene: Date.now(),
|
||||||
|
setLastCodeMirrorSelectionUpdatedFromScene: (time) =>
|
||||||
|
set({ lastCodeMirrorSelectionUpdatedFromScene: time }),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -242,6 +242,8 @@ pub struct Face {
|
|||||||
pub y_axis: Point3d,
|
pub y_axis: Point3d,
|
||||||
/// The z-axis (normal).
|
/// The z-axis (normal).
|
||||||
pub z_axis: Point3d,
|
pub z_axis: Point3d,
|
||||||
|
/// the face id the sketch is on
|
||||||
|
pub face_id: uuid::Uuid,
|
||||||
#[serde(rename = "__meta")]
|
#[serde(rename = "__meta")]
|
||||||
pub meta: Vec<Metadata>,
|
pub meta: Vec<Metadata>,
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use derive_docs::stdlib;
|
use derive_docs::stdlib;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
@ -113,8 +114,9 @@ async fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: Args)
|
|||||||
|
|
||||||
// Create a hashmap for quick id lookup
|
// Create a hashmap for quick id lookup
|
||||||
let mut face_id_map = std::collections::HashMap::new();
|
let mut face_id_map = std::collections::HashMap::new();
|
||||||
let mut start_cap_id = None;
|
// creating fake ids for start and end caps is to make extrudes mock-execute safe
|
||||||
let mut end_cap_id = None;
|
let mut start_cap_id = Some(Uuid::new_v4());
|
||||||
|
let mut end_cap_id = Some(Uuid::new_v4());
|
||||||
|
|
||||||
for face_info in face_infos {
|
for face_info in face_infos {
|
||||||
match face_info.cap {
|
match face_info.cap {
|
||||||
@ -160,6 +162,18 @@ async fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: Args)
|
|||||||
new_value.push(extrude_surface);
|
new_value.push(extrude_surface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
new_value.push(ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
|
||||||
|
position: sketch_group.position, // TODO should be for the extrude surface
|
||||||
|
rotation: sketch_group.rotation, // TODO should be for the extrude surface
|
||||||
|
// pushing this values with a fake face_id to make extrudes mock-execute safe
|
||||||
|
face_id: Uuid::new_v4(),
|
||||||
|
name: path.get_base().name.clone(),
|
||||||
|
geo_meta: GeoMeta {
|
||||||
|
id: path.get_base().geo_meta.id,
|
||||||
|
metadata: path.get_base().geo_meta.metadata.clone(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -955,6 +955,7 @@ async fn start_sketch_on_face(
|
|||||||
y_axis: extrude_group.y_axis,
|
y_axis: extrude_group.y_axis,
|
||||||
z_axis: extrude_group.z_axis,
|
z_axis: extrude_group.z_axis,
|
||||||
meta: vec![args.source_range.into()],
|
meta: vec![args.source_range.into()],
|
||||||
|
face_id: extrude_plane_id,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user