diff --git a/docs/kcl/angleToMatchLengthX.md b/docs/kcl/angleToMatchLengthX.md index 8757a9bdfc..4b01e052a0 100644 --- a/docs/kcl/angleToMatchLengthX.md +++ b/docs/kcl/angleToMatchLengthX.md @@ -69,6 +69,8 @@ const part001 = startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/angleToMatchLengthY.md b/docs/kcl/angleToMatchLengthY.md index 1d4972e9e3..f1f369a8a2 100644 --- a/docs/kcl/angleToMatchLengthY.md +++ b/docs/kcl/angleToMatchLengthY.md @@ -69,6 +69,8 @@ const part001 = startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/angledLine.md b/docs/kcl/angledLine.md index 827e7b1bf4..576b8e39c3 100644 --- a/docs/kcl/angledLine.md +++ b/docs/kcl/angledLine.md @@ -76,6 +76,8 @@ startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/angledLineOfXLength.md b/docs/kcl/angledLineOfXLength.md index a909a12fab..494a2b860e 100644 --- a/docs/kcl/angledLineOfXLength.md +++ b/docs/kcl/angledLineOfXLength.md @@ -76,6 +76,8 @@ startSketchOn('XZ') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/angledLineOfYLength.md b/docs/kcl/angledLineOfYLength.md index 6f21a65ed7..6ecb934323 100644 --- a/docs/kcl/angledLineOfYLength.md +++ b/docs/kcl/angledLineOfYLength.md @@ -77,6 +77,8 @@ startSketchOn('YZ') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/angledLineThatIntersects.md b/docs/kcl/angledLineThatIntersects.md index ecbae1c0e3..7f833df091 100644 --- a/docs/kcl/angledLineThatIntersects.md +++ b/docs/kcl/angledLineThatIntersects.md @@ -82,6 +82,8 @@ const part001 = startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/angledLineToX.md b/docs/kcl/angledLineToX.md index ea470eac49..2c5790b244 100644 --- a/docs/kcl/angledLineToX.md +++ b/docs/kcl/angledLineToX.md @@ -76,6 +76,8 @@ startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/angledLineToY.md b/docs/kcl/angledLineToY.md index a4f865f7d9..d28ed4e276 100644 --- a/docs/kcl/angledLineToY.md +++ b/docs/kcl/angledLineToY.md @@ -75,6 +75,8 @@ startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/arc.md b/docs/kcl/arc.md index d8ef4d2cef..1e8836dc01 100644 --- a/docs/kcl/arc.md +++ b/docs/kcl/arc.md @@ -86,6 +86,8 @@ startSketchOn('-YZ') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/bezierCurve.md b/docs/kcl/bezierCurve.md index aa4a52d48f..12adab14c0 100644 --- a/docs/kcl/bezierCurve.md +++ b/docs/kcl/bezierCurve.md @@ -79,6 +79,8 @@ startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/circle.md b/docs/kcl/circle.md index 88b5d907d1..395d3cd6f1 100644 --- a/docs/kcl/circle.md +++ b/docs/kcl/circle.md @@ -71,6 +71,8 @@ const rectangle = startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/close.md b/docs/kcl/close.md index 596513613a..cd5d6aea1f 100644 --- a/docs/kcl/close.md +++ b/docs/kcl/close.md @@ -72,6 +72,8 @@ startSketchOn('YZ') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/extrude.md b/docs/kcl/extrude.md index 8e26f582f7..4eb330ab7a 100644 --- a/docs/kcl/extrude.md +++ b/docs/kcl/extrude.md @@ -67,6 +67,8 @@ startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/hole.md b/docs/kcl/hole.md index 1d043d8f07..7b85f95286 100644 --- a/docs/kcl/hole.md +++ b/docs/kcl/hole.md @@ -68,6 +68,8 @@ const square = startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/lastSegX.md b/docs/kcl/lastSegX.md index 6714035da3..088af85582 100644 --- a/docs/kcl/lastSegX.md +++ b/docs/kcl/lastSegX.md @@ -66,6 +66,8 @@ startSketchOn("YZ") }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/lastSegY.md b/docs/kcl/lastSegY.md index 57bf61e47e..ca5ab03233 100644 --- a/docs/kcl/lastSegY.md +++ b/docs/kcl/lastSegY.md @@ -66,6 +66,8 @@ startSketchOn("YZ") }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/line.md b/docs/kcl/line.md index 423a4aed85..fe12775fad 100644 --- a/docs/kcl/line.md +++ b/docs/kcl/line.md @@ -66,6 +66,8 @@ startSketchOn('-XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/lineTo.md b/docs/kcl/lineTo.md index 7f0453efa2..005d932c74 100644 --- a/docs/kcl/lineTo.md +++ b/docs/kcl/lineTo.md @@ -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. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/patternCircular2d.md b/docs/kcl/patternCircular2d.md index 7b2cebbc38..52ae998921 100644 --- a/docs/kcl/patternCircular2d.md +++ b/docs/kcl/patternCircular2d.md @@ -80,6 +80,8 @@ const part = startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/patternLinear2d.md b/docs/kcl/patternLinear2d.md index 3c1702eabb..2034b66880 100644 --- a/docs/kcl/patternLinear2d.md +++ b/docs/kcl/patternLinear2d.md @@ -77,6 +77,8 @@ const part = startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/segAng.md b/docs/kcl/segAng.md index fddb2ee9cd..f8aa586424 100644 --- a/docs/kcl/segAng.md +++ b/docs/kcl/segAng.md @@ -69,6 +69,8 @@ const part001 = startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/segEndX.md b/docs/kcl/segEndX.md index 3df6b9bac7..c1ab1372f7 100644 --- a/docs/kcl/segEndX.md +++ b/docs/kcl/segEndX.md @@ -67,6 +67,8 @@ startSketchOn("YZ") }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/segEndY.md b/docs/kcl/segEndY.md index c165b07967..c2dd9e3554 100644 --- a/docs/kcl/segEndY.md +++ b/docs/kcl/segEndY.md @@ -67,6 +67,8 @@ startSketchOn("YZ") }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/segLen.md b/docs/kcl/segLen.md index 97f40fa54b..ae5f5271e5 100644 --- a/docs/kcl/segLen.md +++ b/docs/kcl/segLen.md @@ -67,6 +67,8 @@ startSketchOn("YZ") }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/startProfileAt.md b/docs/kcl/startProfileAt.md index 2c62fa3b81..0d6bb01eb1 100644 --- a/docs/kcl/startProfileAt.md +++ b/docs/kcl/startProfileAt.md @@ -57,6 +57,8 @@ startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/startSketchAt.md b/docs/kcl/startSketchAt.md index c537753666..52c7ea3d64 100644 --- a/docs/kcl/startSketchAt.md +++ b/docs/kcl/startSketchAt.md @@ -65,6 +65,8 @@ startSketchAt([0, 0]) }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/startSketchOn.md b/docs/kcl/startSketchOn.md index db64bad887..658d5a690c 100644 --- a/docs/kcl/startSketchOn.md +++ b/docs/kcl/startSketchOn.md @@ -254,6 +254,8 @@ string }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/std.json b/docs/kcl/std.json index 205d7c7e76..575d5c2442 100644 --- a/docs/kcl/std.json +++ b/docs/kcl/std.json @@ -308,6 +308,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -340,6 +341,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -1308,6 +1314,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -1340,6 +1347,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -2331,6 +2343,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -2363,6 +2376,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -3302,6 +3320,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -3334,6 +3353,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -4315,6 +4339,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -4347,6 +4372,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -5286,6 +5316,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -5318,6 +5349,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -6299,6 +6335,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -6331,6 +6368,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -7270,6 +7312,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -7302,6 +7345,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -8273,6 +8321,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -8305,6 +8354,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -9244,6 +9298,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -9276,6 +9331,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -10242,6 +10302,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -10274,6 +10335,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -11213,6 +11279,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -11245,6 +11312,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -12211,6 +12283,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -12243,6 +12316,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -13182,6 +13260,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -13214,6 +13293,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -14227,6 +14311,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -14259,6 +14344,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -15198,6 +15288,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -15230,6 +15321,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -16279,6 +16375,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -16311,6 +16408,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -17250,6 +17352,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -17282,6 +17385,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -18238,6 +18346,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -18270,6 +18379,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -18584,6 +18698,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -18616,6 +18731,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -19548,6 +19668,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -19580,6 +19701,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -20521,6 +20647,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -20553,6 +20680,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -21492,6 +21624,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -21524,6 +21657,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -22527,6 +22665,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -22559,6 +22698,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -29046,6 +29190,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -29078,6 +29223,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -30013,6 +30163,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -30045,6 +30196,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -30988,6 +31144,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -31020,6 +31177,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -31950,6 +32112,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -31982,6 +32145,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -33580,6 +33748,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -33612,6 +33781,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -34563,6 +34737,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -34595,6 +34770,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -35680,6 +35860,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -35712,6 +35893,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -36651,6 +36837,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -36683,6 +36870,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -37638,6 +37830,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -37670,6 +37863,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -38609,6 +38807,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -38641,6 +38840,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -39825,6 +40029,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -39857,6 +40062,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -40789,6 +40999,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -40821,6 +41032,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -43381,6 +43597,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -43413,6 +43630,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -44345,6 +44567,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -44377,6 +44600,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -46953,6 +47181,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -46985,6 +47214,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -47944,6 +48178,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -47976,6 +48211,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -48935,6 +49175,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -48967,6 +49208,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -49926,6 +50172,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -49958,6 +50205,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -50934,6 +51186,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -50966,6 +51219,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -51295,6 +51553,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -51327,6 +51586,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -52283,6 +52547,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -52315,6 +52580,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -54160,6 +54430,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -54192,6 +54463,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -54594,6 +54870,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -54626,6 +54903,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -55565,6 +55847,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -55597,6 +55880,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -56552,6 +56840,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -56584,6 +56873,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -57523,6 +57817,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -57555,6 +57850,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -58588,6 +58888,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -58620,6 +58921,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -59559,6 +59865,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -59591,6 +59898,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -60541,6 +60853,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -60573,6 +60886,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -61512,6 +61830,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -61544,6 +61863,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -62494,6 +62818,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -62526,6 +62851,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -63465,6 +63795,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -63497,6 +63828,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -64447,6 +64783,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -64479,6 +64816,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", @@ -65418,6 +65760,7 @@ "type": "object", "required": [ "__meta", + "faceId", "id", "sketchGroupId", "type", @@ -65450,6 +65793,11 @@ } } }, + "faceId": { + "description": "the face id the sketch is on", + "type": "string", + "format": "uuid" + }, "id": { "description": "The id of the face.", "type": "string", diff --git a/docs/kcl/tangentialArc.md b/docs/kcl/tangentialArc.md index 6856988034..385a6a2431 100644 --- a/docs/kcl/tangentialArc.md +++ b/docs/kcl/tangentialArc.md @@ -75,6 +75,8 @@ startSketchOn('-YZ') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/tangentialArcTo.md b/docs/kcl/tangentialArcTo.md index 5737a93f7d..4cc56d25a1 100644 --- a/docs/kcl/tangentialArcTo.md +++ b/docs/kcl/tangentialArcTo.md @@ -65,6 +65,8 @@ startSketchOn('-YZ') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/xLine.md b/docs/kcl/xLine.md index 2206a9e032..2b7a138202 100644 --- a/docs/kcl/xLine.md +++ b/docs/kcl/xLine.md @@ -66,6 +66,8 @@ startSketchOn('YZ') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/xLineTo.md b/docs/kcl/xLineTo.md index 13008c89ac..b522f89f34 100644 --- a/docs/kcl/xLineTo.md +++ b/docs/kcl/xLineTo.md @@ -66,6 +66,8 @@ startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/yLine.md b/docs/kcl/yLine.md index bc26b9d601..81863241d9 100644 --- a/docs/kcl/yLine.md +++ b/docs/kcl/yLine.md @@ -66,6 +66,8 @@ startSketchOn('XY') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/docs/kcl/yLineTo.md b/docs/kcl/yLineTo.md index 6ef4465cb2..46069891f1 100644 --- a/docs/kcl/yLineTo.md +++ b/docs/kcl/yLineTo.md @@ -67,6 +67,8 @@ startSketchOn('XZ') }, } | { + // the face id the sketch is on + faceId: uuid, // The id of the face. id: uuid, // 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. id: uuid, // The original sketch group id of the object we are sketching on. diff --git a/e2e/playwright/flow-tests.spec.ts b/e2e/playwright/flow-tests.spec.ts index dc43f6b365..059888a4f5 100644 --- a/e2e/playwright/flow-tests.spec.ts +++ b/e2e/playwright/flow-tests.spec.ts @@ -20,6 +20,8 @@ const commonPoints = { startAt: '[9.06, -12.22]', num1: 9.14, num2: 18.2, + // num1: 9.64, + // num2: 19.19, } test.beforeEach(async ({ context, page }) => { @@ -76,6 +78,7 @@ test('Basic sketch', async ({ page }) => { await expect(page.locator('.cm-content')).toHaveText( `const part001 = startSketchOn('-XZ')` ) + await u.closeDebugPanel() await page.waitForTimeout(300) // TODO detect animation ending, or disable animation @@ -86,7 +89,6 @@ test('Basic sketch', async ({ page }) => { |> startProfileAt(${commonPoints.startAt}, %)`) await page.waitForTimeout(100) - await u.closeDebugPanel() await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await page.waitForTimeout(100) @@ -625,7 +627,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => { const emptySpaceClick = () => page.mouse.click(728, 343).then(() => page.waitForTimeout(100)) const topHorzSegmentClick = () => - page.mouse.click(709, 289).then(() => page.waitForTimeout(100)) + page.mouse.click(709, 290).then(() => page.waitForTimeout(100)) const bottomHorzSegmentClick = () => 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 const startXPx = 600 + await u.closeDebugPanel() await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await expect(page.locator('.cm-content')) .toHaveText(`const part001 = startSketchOn('-XZ') |> startProfileAt(${commonPoints.startAt}, %)`) - await u.closeDebugPanel() - await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await expect(page.locator('.cm-content')) @@ -727,13 +728,18 @@ test('Selections work on fresh and edited sketch', async ({ page }) => { await emptySpaceClick() // select segment in editor than another segment in scene and check there are two cursors - await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click() - await page.waitForTimeout(300) - await page.keyboard.down('Shift') - await expect(page.locator('.cm-cursor')).toHaveCount(1) + // TODO change this back to shift click in the scene, not cmd click in the editor 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 page.waitForTimeout(500) + await page.keyboard.up(process.platform === 'linux' ? 'Control' : 'Meta') // clear selection by clicking on nothing await emptySpaceClick() @@ -918,13 +924,13 @@ test('Can add multiple sketches', async ({ page }) => { await page.waitForTimeout(500) // TODO detect animation ending, or disable animation const startXPx = 600 + await u.closeDebugPanel() await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await expect(page.locator('.cm-content')) .toHaveText(`const part001 = startSketchOn('-XZ') |> startProfileAt(${commonPoints.startAt}, %)`) await page.waitForTimeout(100) - await u.closeDebugPanel() await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await page.waitForTimeout(100) @@ -1372,10 +1378,129 @@ test('Snap to close works (at any scale)', async ({ page }) => { ) => `const part001 = startSketchOn('XZ') |> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %) |> line([${roundOff(scale * 175.36)}, 0], %) -|> line([0, -${roundOff(scale * 175.37) + fudge}], %) +|> line([0, -${roundOff(scale * 175.36) + fudge}], %) |> close(%)` await doSnapAtDifferentScales([0, 100, 100], codeTemplate(0.01, 0.01)) 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, %)`) +}) diff --git a/e2e/playwright/snapshot-tests.spec.ts b/e2e/playwright/snapshot-tests.spec.ts index 578e11205c..571e740da3 100644 --- a/e2e/playwright/snapshot-tests.spec.ts +++ b/e2e/playwright/snapshot-tests.spec.ts @@ -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 expect(page.locator('.cm-content')) .toHaveText(`const part001 = startSketchOn('-XZ') - |> startProfileAt([230.03, -310.33], %)`) + |> startProfileAt([230.03, -310.32], %)`) await page.waitForTimeout(100) await u.closeDebugPanel() @@ -622,7 +622,7 @@ test('Client side scene scale should match engine scale mm', async ({ await expect(page.locator('.cm-content')) .toHaveText(`const part001 = startSketchOn('-XZ') - |> startProfileAt([230.03, -310.33], %) + |> startProfileAt([230.03, -310.32], %) |> line([232.2, 0], %)`) 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')) .toHaveText(`const part001 = startSketchOn('-XZ') - |> startProfileAt([230.03, -310.33], %) + |> startProfileAt([230.03, -310.32], %) |> line([232.2, 0], %) |> tangentialArcTo([694.43, -78.12], %)`) @@ -658,3 +658,48 @@ test('Client side scene scale should match engine scale mm', async ({ 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) +}) diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-inch-2-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-inch-2-Google-Chrome-linux.png index 65b5adbbce..f877bfca59 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-inch-2-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-inch-2-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-mm-2-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-mm-2-Google-Chrome-linux.png index eaf00b7abd..b521637b8c 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-mm-2-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-mm-2-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Sketch-on-face-with-none-z-up-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Sketch-on-face-with-none-z-up-1-Google-Chrome-linux.png new file mode 100644 index 0000000000..e77b8e97da Binary files /dev/null and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Sketch-on-face-with-none-z-up-1-Google-Chrome-linux.png differ diff --git a/src/clientSideScene/CameraControls.ts b/src/clientSideScene/CameraControls.ts index 960353bfda..21d667d609 100644 --- a/src/clientSideScene/CameraControls.ts +++ b/src/clientSideScene/CameraControls.ts @@ -425,6 +425,7 @@ export class CameraControls { if (this.camera instanceof OrthographicCamera) return const { x: px, y: py, z: pz } = this.camera.position const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion + const oldCamUp = this.camera.up.clone() const aspect = window.innerWidth / window.innerHeight this.lastPerspectiveFov = this.camera.fov const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov) @@ -436,7 +437,8 @@ export class CameraControls { z_near, z_far ) - this.camera.up.set(0, 0, 1) + + this.camera.up.copy(oldCamUp) this.camera.layers.enable(SKETCH_LAYER) if (DEBUG_SHOW_INTERSECTION_PLANE) this.camera.layers.enable(INTERSECTION_PLANE_LAYER) @@ -458,13 +460,14 @@ export class CameraControls { } private createPerspectiveCamera = () => { const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov) + const previousCamUp = this.camera.up.clone() this.camera = new PerspectiveCamera( this.lastPerspectiveFov, window.innerWidth / window.innerHeight, z_near, z_far ) - this.camera.up.set(0, 0, 1) + this.camera.up.copy(previousCamUp) this.camera.layers.enable(SKETCH_LAYER) if (DEBUG_SHOW_INTERSECTION_PLANE) this.camera.layers.enable(INTERSECTION_PLANE_LAYER) @@ -618,7 +621,7 @@ export class CameraControls { didChange = true } - this.safeLookAtTarget() + this.safeLookAtTarget(this.camera.up) // Update the camera's matrices this.camera.updateMatrixWorld() @@ -683,48 +686,48 @@ export class CameraControls { targetAngle = -Math.PI / 2, duration = 500 ): Promise { - // 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 - const xyRadius = Math.sqrt( - (this.target.x - this.camera.position.x) ** 2 + - (this.target.y - this.camera.position.y) ** 2 - ) - const xyAngle = Math.atan2( - this.camera.position.y - this.target.y, - this.camera.position.x - this.target.x - ) - this._isCamMovingCallback(true, true) return new Promise((resolve) => { + // 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 + const xyRadius = Math.sqrt( + (this.target.x - this.camera.position.x) ** 2 + + (this.target.y - this.camera.position.y) ** 2 + ) + const xyAngle = Math.atan2( + this.camera.position.y - this.target.y, + this.camera.position.x - this.target.x + ) + const camAtTime = (obj: { angle: number }) => { + 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() + } + const onComplete = (obj: { angle: number }) => { + camAtTime(obj) + this._isCamMovingCallback(false, true) + + // resolve after a couple of frames + requestAnimationFrame(() => { + requestAnimationFrame(() => resolve()) + }) + } + this._isCamMovingCallback(true, true) + + if (isReducedMotion()) { + onComplete({ angle: targetAngle }) + return + } + 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 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() - this._isCamMovingCallback(false, true) - - // resolve after a couple of frames - requestAnimationFrame(() => { - requestAnimationFrame(() => resolve()) - }) - }) + .onUpdate(camAtTime) + .onComplete(onComplete) .start() }) } @@ -778,6 +781,8 @@ export class CameraControls { targetQuaternion, animationProgress ) + const up = new Vector3(0, 0, 1).applyQuaternion(currentQ) + this.camera.up.copy(up) const currentTarget = tempVec.lerpVectors( initialTarget, targetPosition, @@ -802,7 +807,7 @@ export class CameraControls { const onComplete = async () => { if (isReducedMotion() && toOrthographic) { - cameraAtTime(0.99) + cameraAtTime(0.9999) this.useOrthographicCamera() } else if (toOrthographic) { await this.animateToOrthographic() @@ -863,37 +868,40 @@ export class CameraControls { animateFovChange() // Start the animation }) - animateToPerspective = () => + animateToPerspective = (targetCamUp = new Vector3(0, 0, 1)) => new Promise((resolve) => { - if (this.syncDirection === 'engineToClient') + if (this.syncDirection === 'engineToClient') { console.warn( 'animate To Perspective not design to work with engineToClient syncDirection.' ) + } this.isFovAnimationInProgress = true - // Immediately set the camera to perspective with a very low FOV const targetFov = this.fovBeforeOrtho // Target FOV for perspective this.lastPerspectiveFov = 4 let currentFov = 4 - this.camera.updateProjectionMatrix() - const fovAnimationStep = (targetFov - currentFov) / FRAMES_TO_ANIMATE_IN + const initialCameraUp = this.camera.up.clone() this.usePerspectiveCamera() + const tempVec = new Vector3() - const animateFovChange = () => { - if (this.camera instanceof OrthographicCamera) return - if (this.camera.fov < targetFov) { - // Increase the FOV - currentFov = Math.min(currentFov + fovAnimationStep, targetFov) - // this.camera.fov = currentFov - this.camera.updateProjectionMatrix() - this.dollyZoom(currentFov) - requestAnimationFrame(animateFovChange) // Continue the animation - } else { - // Set the flag to false as the FOV animation is complete - this.isFovAnimationInProgress = false - resolve(true) - } + const cameraAtTime = (t: number) => { + currentFov = + this.lastPerspectiveFov + (targetFov - this.lastPerspectiveFov) * t + const currentUp = tempVec.lerpVectors(initialCameraUp, targetCamUp, t) + this.camera.up.copy(currentUp) + this.dollyZoom(currentFov) } - animateFovChange() // Start the animation + + const onComplete = () => { + this.isFovAnimationInProgress = false + resolve(true) + } + + 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 = () => {} diff --git a/src/clientSideScene/helpers.ts b/src/clientSideScene/helpers.ts index 233ff3219c..4a2e2f0270 100644 --- a/src/clientSideScene/helpers.ts +++ b/src/clientSideScene/helpers.ts @@ -40,3 +40,12 @@ export function isQuaternionVertical(q: Quaternion) { // no x or y components means it's vertical 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() +} diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index cdee9ffd77..cdc33bfd18 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -5,7 +5,6 @@ import { Group, Intersection, LineCurve3, - Matrix4, Mesh, MeshBasicMaterial, Object3D, @@ -37,7 +36,7 @@ import { Y_AXIS, YZ_PLANE, } from './sceneInfra' -import { isQuaternionVertical } from './helpers' +import { isQuaternionVertical, quaternionFromUpNForward } from './helpers' import { CallExpression, getTangentialArcToInfo, @@ -55,7 +54,7 @@ import { } from 'lang/wasm' import { kclManager } from 'lang/KclSingleton' import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' -import { executeAst } from 'useStore' +import { executeAst, useStore } from 'useStore' import { engineCommandManager } from 'lang/std/engineConnection' import { createArcGeometry, @@ -70,16 +69,22 @@ import { changeSketchArguments, updateStartProfileAtArgs, } from 'lang/std/sketch' -import { isReducedMotion, throttle } from 'lib/utils' +import { throttle } from 'lib/utils' import { createArrayExpression, createCallExpressionStdLib, createLiteral, createPipeSubstitution, } from 'lang/modifyAst' -import { getEventForSegmentSelection } from 'lib/selections' +import { + getEventForSegmentSelection, + sendSelectEventToEngine, +} from 'lib/selections' import { getTangentPointFromPreviousArc } from 'lib/utils2d' 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' @@ -164,7 +169,7 @@ class SceneEntities { console.warn('createIntersectionPlane called when it already exists') return } - const hundredM = 1000000 + const hundredM = 100_0000 const planeGeometry = new PlaneGeometry(hundredM, hundredM) const planeMaterial = new MeshBasicMaterial({ color: 0xff0000, @@ -178,7 +183,12 @@ class SceneEntities { this.intersectionPlane.layers.set(INTERSECTION_PLANE_LAYER) 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 baseXColor = 0x000055 const baseYColor = 0x550000 @@ -238,14 +248,12 @@ class SceneEntities { child.layers.set(SKETCH_LAYER) }) - const quat = quaternionFromSketchGroup( - sketchGroupFromPathToNode({ - pathToNode: sketchPathToNode, - ast: kclManager.ast, - programMemory: kclManager.programMemory, - }) + const quat = quaternionFromUpNForward( + new Vector3(...up), + new Vector3(...forward) ) this.axisGroup.setRotationFromQuaternion(quat) + sketchPosition && this.axisGroup.position.set(...sketchPosition) this.scene.add(this.axisGroup) } removeIntersectionPlane() { @@ -258,10 +266,16 @@ class SceneEntities { ast, // is draft line assumes the last segment is a draft line, and mods it as the user moves the mouse draftSegment, + forward, + up, + position, }: { sketchPathToNode: PathToNode ast?: Program draftSegment?: DraftSegment + forward: [number, number, number] + up: [number, number, number] + position?: [number, number, number] }) { sceneInfra.resetMouseListeners() this.createIntersectionPlane() @@ -286,6 +300,7 @@ class SceneEntities { if (!Array.isArray(sketchGroup?.value)) return this.sceneProgramMemory = programMemory const group = new Group() + position && group.position.set(...position) group.userData = { type: SKETCH_GROUP_SEGMENTS, pathToNode: sketchPathToNode, @@ -377,13 +392,18 @@ class SceneEntities { this.activeSegments[JSON.stringify(segPathToNode)] = seg }) - this.currentSketchQuaternion = quaternionFromSketchGroup(sketchGroup) + this.currentSketchQuaternion = quaternionFromUpNForward( + new Vector3(...up), + new Vector3(...forward) + ) group.setRotationFromQuaternion(this.currentSketchQuaternion) this.intersectionPlane && this.intersectionPlane.setRotationFromQuaternion( this.currentSketchQuaternion ) - + this.intersectionPlane && + position && + this.intersectionPlane.position.set(...position) this.scene.add(group) if (!draftSegment) { sceneInfra.setCallbacks({ @@ -453,7 +473,13 @@ class SceneEntities { kclManager.executeAstMock(modifiedAst, { updates: 'code' }) await this.tearDownSketch({ removeAxis: false }) - this.setupSketch({ sketchPathToNode, draftSegment }) + this.setupSketch({ + sketchPathToNode, + draftSegment, + forward, + up, + position, + }) }, onMove: (args) => { this.onDragSegment({ @@ -476,21 +502,37 @@ class SceneEntities { } updateAstAndRejigSketch = async ( sketchPathToNode: PathToNode, - modifiedAst: Program + modifiedAst: Program, + forward: [number, number, number], + up: [number, number, number], + origin: [number, number, number] ) => { await kclManager.updateAst(modifiedAst, 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 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 new Promise((resolve) => setTimeout(resolve, 100)) - this.setupSketch({ sketchPathToNode, draftSegment: 'line' }) + this.setupSketch({ sketchPathToNode, draftSegment: 'line', forward, up }) } onDraftLineMouseMove = () => {} prepareTruncatedMemoryAndAst = ( @@ -785,10 +827,10 @@ class SceneEntities { } } async animateAfterSketch() { - if (isReducedMotion()) { - sceneInfra.camControls.usePerspectiveCamera() - return - } + // if (isReducedMotion()) { + // sceneInfra.camControls.usePerspectiveCamera() + // return + // } await sceneInfra.camControls.animateToPerspective() } removeSketchGrid() { @@ -853,26 +895,81 @@ class SceneEntities { const type: DefaultPlane = selected.userData.type selected.material.color = defaultPlaneColor(type) }, - onClick: (args) => { + onClick: async (args) => { + const checkExtrudeFaceClick = async (): Promise => { + 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.mouseEvent.which !== 1) return const { intersects } = args const type = intersects?.[0].object.name || '' const posNorm = Number(intersects?.[0]?.normal?.z) > 0 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) { 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) { 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({ type: 'Select default plane', data: { + type: 'defaultPlane', plane: planeString, - normal, + zAxis, + yAxis, }, }) }, @@ -1002,33 +1099,10 @@ export function sketchGroupFromPathToNode({ pathToNode, 'VariableDeclarator' ).node + // console.trace('where from?') 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) { const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START]) if (segmentHead) { @@ -1063,10 +1137,68 @@ export function getSketchQuaternion( programMemory: kclManager.programMemory, }) 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() dummyCam.up.set(0, 0, 1) - const _zAxis = massageFormats(zAxis) - dummyCam.position.copy(_zAxis) + dummyCam.position.copy(zAxis) dummyCam.lookAt(0, 0, 0) dummyCam.updateMatrix() const quaternion = dummyCam.quaternion.clone() @@ -1075,7 +1207,7 @@ export function getSketchQuaternion( // 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 - if (isVert && _zAxis.z < 0) { + if (isVert && zAxis.z < 0) { quaternion.set(0, 1, 0, 0) } else if (isVert) { quaternion.set(0, 0, 0, 1) diff --git a/src/clientSideScene/sceneInfra.ts b/src/clientSideScene/sceneInfra.ts index 4a65623fc8..cf8a3bea11 100644 --- a/src/clientSideScene/sceneInfra.ts +++ b/src/clientSideScene/sceneInfra.ts @@ -37,8 +37,10 @@ export const ZOOM_MAGIC_NUMBER = 63.5 export const INTERSECTION_PLANE_LAYER = 1 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 DEFAULT_PLANES = 'default-planes' @@ -97,13 +99,13 @@ class SceneInfra { _baseUnitMultiplier = 1 onDragCallback: (arg: OnDragCallbackArgs) => void = () => {} onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {} - onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {} + onClickCallback: (arg: OnClickCallbackArgs) => void = () => {} onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {} onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {} setCallbacks = (callbacks: { onDrag?: (arg: OnDragCallbackArgs) => void onMove?: (arg: OnMoveCallbackArgs) => void - onClick?: (arg?: OnClickCallbackArgs) => void + onClick?: (arg: OnClickCallbackArgs) => void onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void }) => { @@ -272,16 +274,19 @@ class SceneInfra { let transformedPoint = intersectPoint.clone() if (transformedPoint) { transformedPoint.applyQuaternion(inversePlaneQuaternion) - transformedPoint?.sub( - new Vector3(...planePosition).applyQuaternion(inversePlaneQuaternion) - ) } + const twoD = new Vector2( + // I think the intersection plane doesn't get scale when nearly everything else does, maybe that should change + transformedPoint.x / this._baseUnitMultiplier, + transformedPoint.y / this._baseUnitMultiplier + ) // z should be 0 + const planePositionCorrected = new Vector3( + ...planePosition + ).applyQuaternion(inversePlaneQuaternion) + twoD.sub(new Vector2(...planePositionCorrected)) return { - twoD: new Vector2( - transformedPoint.x / this._baseUnitMultiplier, - transformedPoint.y / this._baseUnitMultiplier - ), // z should be 0 + twoD, threeD: intersectPoint.divideScalar(this._baseUnitMultiplier), intersection: planeIntersects[0], } @@ -464,7 +469,7 @@ class SceneInfra { intersects, }) } else { - this.onClickCallback() + this.onClickCallback({ mouseEvent, intersects }) } // Clear the selected state whether it was dragged or not this.selected = null @@ -478,7 +483,7 @@ class SceneInfra { intersects, }) } else { - this.onClickCallback() + this.onClickCallback({ mouseEvent, intersects }) } } showDefaultPlanes() { diff --git a/src/components/MemoryPanel.test.tsx b/src/components/MemoryPanel.test.tsx index 7e971dfdd9..8bfffd4ca6 100644 --- a/src/components/MemoryPanel.test.tsx +++ b/src/components/MemoryPanel.test.tsx @@ -38,7 +38,26 @@ describe('processMemory', () => { myVar: 5, myFn: undefined, 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: [ { type: 'ToPoint', to: [-3.35, 0.17], from: [0, 0], name: '' }, { type: 'ToPoint', to: [0.98, 5.16], from: [-3.35, 0.17], name: '' }, diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 119bdba24a..774bdfde98 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -23,9 +23,9 @@ import { applyConstraintAngleLength } from './Toolbar/setAngleLength' import { pathMapToSelections } from 'lang/util' import { useStore } from 'useStore' import { + Selections, canExtrudeSelection, handleSelectionBatch, - handleSelectionWithShift, isSelectionLastLine, isSketchPipe, } from 'lib/selections' @@ -34,14 +34,20 @@ import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance' import useStateMachineCommands from 'hooks/useStateMachineCommands' import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig' import { sceneInfra } from 'clientSideScene/sceneInfra' -import { getSketchQuaternion } from 'clientSideScene/sceneEntities' -import { startSketchOnDefault } from 'lang/modifyAst' -import { Program } from 'lang/wasm' -import { isSingleCursorInPipe } from 'lang/queryAst' +import { + getSketchQuaternion, + getSketchOrientationDetails, +} 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 { exportFromEngine } from 'lib/exportFromEngine' import { Models } from '@kittycad/lib/dist/types/src' import toast from 'react-hot-toast' +import { EditorSelection } from '@uiw/react-codemirror' +import { Vector3 } from 'three' +import { quaternionFromUpNForward } from 'clientSideScene/helpers' type MachineContext = { state: StateFrom @@ -69,9 +75,15 @@ export const ModelingMachineProvider = ({ const streamRef = useRef(null) useSetupEngineManager(streamRef, token) - const { isShiftDown, editorView } = useStore((s) => ({ + const { + isShiftDown, + editorView, + setLastCodeMirrorSelectionUpdatedFromScene, + } = useStore((s) => ({ isShiftDown: s.isShiftDown, editorView: s.editorView, + setLastCodeMirrorSelectionUpdatedFromScene: + s.setLastCodeMirrorSelectionUpdatedFromScene, })) // Settings machine setup @@ -92,92 +104,98 @@ export const ModelingMachineProvider = ({ { actions: { 'sketch exit execute': () => { - kclManager.executeAst() + try { + kclManager.executeAst(parse(kclManager.code)) + } catch (e) { + kclManager.executeAst() + } }, '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 const setSelections = event.data if (!editorView) return {} - if (setSelections.selectionType === 'mirrorCodeMirrorSelections') - return { selectionRanges: setSelections.selection } - else if (setSelections.selectionType === 'otherSelection') { + const dispatchSelection = (selection?: EditorSelection) => { + if (!selection) return // TODO less of hack for the below please + setLastCodeMirrorSelectionUpdatedFromScene(Date.now()) + setTimeout(() => editorView.dispatch({ selection })) + } + let selections: Selections = { + codeBasedSelections: [], + otherSelections: [], + } + if (setSelections.selectionType === 'singleCodeCursor') { + if (!setSelections.selection && isShiftDown) { + } else if (!setSelections.selection && !isShiftDown) { + selections = { + codeBasedSelections: [], + otherSelections: [], + } + } else if (setSelections.selection && !isShiftDown) { + selections = { + codeBasedSelections: [setSelections.selection], + otherSelections: [], + } + } else if (setSelections.selection && isShiftDown) { + selections = { + codeBasedSelections: [ + ...selectionRanges.codeBasedSelections, + setSelections.selection, + ], + otherSelections: selectionRanges.otherSelections, + } + } + const { + engineEvents, codeMirrorSelection, - selectionRangeTypeMap, - otherSelections, - } = handleSelectionWithShift({ - otherSelection: setSelections.selection, - currentSelections: selectionRanges, - isShiftDown, - }) - setTimeout(() => { - editorView.dispatch({ - selection: codeMirrorSelection, - }) + updateSceneObjectColors, + } = handleSelectionBatch({ + selections, }) + codeMirrorSelection && dispatchSelection(codeMirrorSelection) + engineEvents && + engineEvents.forEach((event) => + engineCommandManager.sendSceneCommand(event) + ) + updateSceneObjectColors() return { - selectionRangeTypeMap, - selectionRanges: { - codeBasedSelections: selectionRanges.codeBasedSelections, - otherSelections, - }, + selectionRanges: selections, } - } 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 { - codeMirrorSelection, - selectionRangeTypeMap, - otherSelections, - } = handleSelectionWithShift({ - codeSelection: setSelections.selection, - currentSelections: selectionRanges, - isShiftDown, - }) - if (codeMirrorSelection) { - setTimeout(() => { - editorView.dispatch({ - selection: codeMirrorSelection, - }) - }) + if (setSelections.selectionType === 'mirrorCodeMirrorSelections') { + return { + selectionRanges: setSelections.selection, } - if (!setSelections.selection) { - return { - selectionRangeTypeMap, - selectionRanges: { - codeBasedSelections: selectionRanges.codeBasedSelections, - otherSelections, - }, + } + + if (setSelections.selectionType === 'otherSelection') { + if (isShiftDown) { + selections = { + codeBasedSelections: selectionRanges.codeBasedSelections, + otherSelections: [setSelections.selection], + } + } else { + selections = { + codeBasedSelections: [], + otherSelections: [setSelections.selection], } } + const { engineEvents, updateSceneObjectColors } = + handleSelectionBatch({ + selections, + }) + engineEvents && + engineEvents.forEach((event) => + engineCommandManager.sendSceneCommand(event) + ) + updateSceneObjectColors() return { - selectionRangeTypeMap, - selectionRanges: { - codeBasedSelections: selectionRanges.codeBasedSelections, - otherSelections, - }, + selectionRanges: selections, } } - // This DOES NOT set the `selectionRanges` in xstate context - // same as comment above - const { codeMirrorSelection, selectionRangeTypeMap } = - handleSelectionBatch({ - selections: setSelections.selection, - }) - if (codeMirrorSelection) { - setTimeout(() => { - editorView.dispatch({ - selection: codeMirrorSelection, - }) - }) - } - return { selectionRangeTypeMap } + + return {} }), 'Engine export': (_, event) => { if (event.type !== 'Export' || TEST) return @@ -255,10 +273,10 @@ export const ModelingMachineProvider = ({ kclManager.kclErrors.length === 0 && kclManager.ast.body.length > 0, }, services: { - 'AST-undo-startSketchOn': async ({ sketchPathToNode }) => { - if (!sketchPathToNode) return + 'AST-undo-startSketchOn': async ({ sketchDetails }) => { + if (!sketchDetails) return 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 newAst.body = newAst.body.filter((_, i) => i !== varDecIndex) await kclManager.executeAstMock(newAst, { updates: 'code' }) @@ -267,28 +285,69 @@ export const ModelingMachineProvider = ({ 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( kclManager.ast, - plane + data.plane ) await kclManager.updateAst(modifiedAst, false) - const quaternion = getSketchQuaternion(pathToNode, normal) - await sceneInfra.camControls.tweenCameraToQuaternion(quaternion) + const quat = await getSketchQuaternion(pathToNode, data.zAxis) + await sceneInfra.camControls.tweenCameraToQuaternion(quat) return { sketchPathToNode: pathToNode, - sketchNormalBackUp: normal, + zAxis: data.zAxis, + yAxis: data.yAxis, + origin: [0, 0, 0], } }, - 'animate-to-sketch': async ({ - sketchPathToNode, - sketchNormalBackUp, - }) => { - const quaternion = getSketchQuaternion( - sketchPathToNode || [], - sketchNormalBackUp + 'animate-to-sketch': async ({ selectionRanges }) => { + const sourceRange = selectionRanges.codeBasedSelections[0].range + const sketchPathToNode = getNodePathFromSourceRange( + kclManager.ast, + sourceRange + ) + const info = await getSketchOrientationDetails(sketchPathToNode || []) + await sceneInfra.camControls.tweenCameraToQuaternion( + info.quat, + new Vector3(...info.sketchDetails.origin) ) - await sceneInfra.camControls.tweenCameraToQuaternion(quaternion) + 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 ({ selectionRanges, diff --git a/src/components/Stream.tsx b/src/components/Stream.tsx index e7108db842..41120f19be 100644 --- a/src/components/Stream.tsx +++ b/src/components/Stream.tsx @@ -1,15 +1,14 @@ import { MouseEventHandler, useEffect, useRef, useState } from 'react' -import { v4 as uuidv4 } from 'uuid' import { useStore } from '../useStore' import { getNormalisedCoordinates } from '../lib/utils' import Loading from './Loading' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' -import { Models } from '@kittycad/lib' -import { engineCommandManager } from '../lang/std/engineConnection' import { useModelingContext } from 'hooks/useModelingContext' import { useKclContext } from 'lang/KclSingleton' import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp' import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator' +import { butName } from 'lib/cameraControls' +import { sendSelectEventToEngine } from 'lib/selections' export const Stream = ({ className = '' }: { className?: string }) => { const [isLoading, setIsLoading] = useState(true) @@ -60,50 +59,14 @@ export const Stream = ({ className = '' }: { className?: string }) => { setClickCoords({ x, y }) } - const handleMouseUp: MouseEventHandler = ({ - clientX, - clientY, - ctrlKey, - }) => { + const handleMouseUp: MouseEventHandler = (e) => { if (!videoRef.current) return setButtonDownInStream(undefined) if (state.matches('Sketch')) return if (state.matches('Sketch no face')) return - const { x, y } = getNormalisedCoordinates({ - clientX, - clientY, - el: videoRef.current, - ...streamDimensions, - }) - - const newCmdId = uuidv4() - const interaction = ctrlKey ? 'pan' : 'rotate' - - 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) + if (!didDragInStream && butName(e).left) { + sendSelectEventToEngine(e, videoRef.current, streamDimensions) } setDidDragInStream(false) @@ -143,6 +106,7 @@ export const Stream = ({ className = '' }: { className?: string }) => { className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`} disablePictureInPicture style={{ transitionDuration: '200ms', transitionProperty: 'filter' }} + id="video-stream" /> {!isNetworkOkay && !isLoading && ( diff --git a/src/components/TextEditor.tsx b/src/components/TextEditor.tsx index b668edb1a1..e11e24a17c 100644 --- a/src/components/TextEditor.tsx +++ b/src/components/TextEditor.tsx @@ -3,6 +3,7 @@ import ReactCodeMirror, { Extension, ViewUpdate, keymap, + SelectionRange, } from '@uiw/react-codemirror' import { TEST } from 'env' import { useCommandsContext } from 'hooks/useCommandsContext' @@ -75,7 +76,7 @@ export const TextEditor = ({ }) const { - context: { selectionRanges, selectionRangeTypeMap }, + context: { selectionRanges }, send, state, } = useModelingContext() @@ -91,10 +92,27 @@ export const TextEditor = ({ if (isNetworkOkay) kclManager.setCodeAndExecute(newCode) else kclManager.setCode(newCode) } //, []); + const lastSelection = useRef('') const onUpdate = (viewUpdate: ViewUpdate) => { if (!editorView) { 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 const ignoreEvents: ModelingMachineEvent['type'][] = [ 'Equip Line tool', @@ -104,7 +122,6 @@ export const TextEditor = ({ const eventInfo = processCodeMirrorRanges({ codeMirrorRanges: viewUpdate.state.selection.ranges, selectionRanges, - selectionRangeTypeMap, isShiftDown, }) if (!eventInfo) return @@ -226,3 +243,7 @@ export const TextEditor = ({ ) } + +function stringifyRanges(ranges: readonly SelectionRange[]): string { + return ranges.map(({ to, from }) => `${to}->${from}`).join('&') +} diff --git a/src/lang/KclSingleton.tsx b/src/lang/KclSingleton.tsx index dadb04d1a4..3f46409676 100644 --- a/src/lang/KclSingleton.tsx +++ b/src/lang/KclSingleton.tsx @@ -1,6 +1,7 @@ import { executeAst, executeCode } from 'useStore' import { Selections } from 'lib/selections' import { KCLError } from './errors' +import { v4 as uuidv4 } from 'uuid' import { EngineCommandManager, engineCommandManager, @@ -14,6 +15,8 @@ import { Program, ProgramMemory, recast, + SketchGroup, + ExtrudeGroup, } from 'lang/wasm' import { bracket } from 'lib/exampleKcl' import { createContext, useContext, useEffect, useState } from 'react' @@ -235,7 +238,6 @@ class KclManager { updateCode = false, executionId?: number ) { - console.trace('executeAst') const currentExecutionId = executionId || Date.now() this._cancelTokens.set(currentExecutionId, false) @@ -245,6 +247,7 @@ class KclManager { ast, engineCommandManager: this.engineCommandManager, }) + enterEditMode(programMemory) this.isExecuting = false // Check the cancellation token for this execution before applying side effects if (this._cancelTokens.get(currentExecutionId)) { @@ -333,6 +336,7 @@ class KclManager { } if (!result.isChange) return const { logs, errors, programMemory, ast } = result + enterEditMode(programMemory) this.logs = logs this.kclErrors = errors this.programMemory = programMemory @@ -520,3 +524,18 @@ function safteLSSetItem(key: string, value: string) { if (typeof window === 'undefined') return 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, + }, + }) +} diff --git a/src/lang/artifact.test.ts b/src/lang/artifact.test.ts index 26882a55bb..f4fbe2937e 100644 --- a/src/lang/artifact.test.ts +++ b/src/lang/artifact.test.ts @@ -74,16 +74,56 @@ const mySketch001 = startSketchOn('XY') expect(sketch001).toEqual({ type: 'ExtrudeGroup', 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, position: [0, 0, 0], rotation: [0, 0, 0, 1], - endCapId: null, - startCapId: null, - sketchGroupValues: expect.any(Array), xAxis: { x: 1, y: 0, z: 0 }, yAxis: { x: 0, y: 1, z: 0 }, zAxis: { x: 0, y: 0, z: 1 }, + startCapId: expect.any(String), + endCapId: expect.any(String), __meta: [{ sourceRange: [46, 71] }], }) }) @@ -117,32 +157,149 @@ const sk2 = startSketchOn('XY') { type: 'ExtrudeGroup', 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, position: [0, 0, 0], rotation: [0, 0, 0, 1], - endCapId: null, - startCapId: null, - sketchGroupValues: expect.any(Array), xAxis: { x: 1, y: 0, z: 0 }, yAxis: { x: 0, y: 1, z: 0 }, zAxis: { x: 0, y: 0, z: 1 }, + startCapId: expect.any(String), + endCapId: expect.any(String), __meta: [{ sourceRange: [38, 63] }], }, { type: 'ExtrudeGroup', 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, position: [0, 0, 0], rotation: [0, 0, 0, 1], - - endCapId: null, - startCapId: null, - sketchGroupValues: expect.any(Array), xAxis: { x: 1, y: 0, z: 0 }, yAxis: { x: 0, y: 1, z: 0 }, zAxis: { x: 0, y: 0, z: 1 }, + startCapId: expect.any(String), + endCapId: expect.any(String), __meta: [{ sourceRange: [343, 368] }], }, ]) diff --git a/src/lang/modifyAst.test.ts b/src/lang/modifyAst.test.ts index 314e3b1bd6..59e42a64fe 100644 --- a/src/lang/modifyAst.test.ts +++ b/src/lang/modifyAst.test.ts @@ -12,8 +12,10 @@ import { addSketchTo, giveSketchFnCallTag, moveValueIntoNewVariable, + sketchOnExtrudedFace, } from './modifyAst' import { enginelessExecutor } from '../lib/testHelpers' +import { getNodePathFromSourceRange } from './queryAst' beforeAll(() => initPromise) @@ -274,3 +276,89 @@ const yo2 = hmm([identifierGuy + 5])` 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')`) + }) +}) diff --git a/src/lang/modifyAst.ts b/src/lang/modifyAst.ts index 88f3004743..8bef7c2794 100644 --- a/src/lang/modifyAst.ts +++ b/src/lang/modifyAst.ts @@ -1,4 +1,3 @@ -import { ToolTip } from '../useStore' import { Selection } from 'lib/selections' import { Program, @@ -64,7 +63,6 @@ export function addStartProfileAt( pathToNode: PathToNode, at: [number, number] ): { modifiedAst: Program; pathToNode: PathToNode } { - console.log('addStartProfileAt called') const variableDeclaration = getNodeFromPath( node, pathToNode, @@ -317,17 +315,17 @@ export function extrudeSketch( export function sketchOnExtrudedFace( node: Program, pathToNode: PathToNode, - programMemory: ProgramMemory + programMemory: ProgramMemory, + cap: 'none' | 'start' | 'end' = 'none' ): { modifiedAst: Program; pathToNode: PathToNode } { let _node = { ...node } const newSketchName = findUniqueName(node, 'part') - const { node: oldSketchNode, shallowPath: pathToOldSketch } = - getNodeFromPath( - _node, - pathToNode, - 'VariableDeclarator', - true - ) + const { node: oldSketchNode } = getNodeFromPath( + _node, + pathToNode, + 'VariableDeclarator', + true + ) const oldSketchName = oldSketchNode.id.name const { node: expression } = getNodeFromPath( _node, @@ -335,42 +333,44 @@ export function sketchOnExtrudedFace( 'CallExpression' ) - const { modifiedAst, tag } = addTagForSketchOnFace( - { - previousProgramMemory: programMemory, - pathToNode, - node: _node, - }, - expression.callee.name - ) - _node = modifiedAst + let _tag = '' + if (cap === 'none') { + const { modifiedAst, tag } = addTagForSketchOnFace( + { + previousProgramMemory: programMemory, + pathToNode, + node: _node, + }, + expression.callee.name + ) + _tag = tag + _node = modifiedAst + } else { + _tag = cap.toUpperCase() + } const newSketch = createVariableDeclaration( newSketchName, - createPipeExpression([ - createCallExpressionStdLib('startSketchAt', [ - createArrayExpression([createLiteral(0), createLiteral(0)]), - ]), - createCallExpressionStdLib('lineTo', [ - createArrayExpression([createLiteral(1), createLiteral(1)]), - createPipeSubstitution(), - ]), - createCallExpression('transform', [ - createCallExpressionStdLib('getExtrudeWallTransform', [ - createLiteral(tag), - createIdentifier(oldSketchName), - ]), - createPipeSubstitution(), - ]), + createCallExpressionStdLib('startSketchOn', [ + createIdentifier(oldSketchName), + createLiteral(_tag), ]), 'const' ) - const expressionIndex = getLastIndex(pathToOldSketch) + + const expressionIndex = pathToNode[1][0] as number _node.body.splice(expressionIndex + 1, 0, newSketch) + const newpathToNode: PathToNode = [ + ['body', ''], + [expressionIndex + 1, 'index'], + ['declarations', 'VariableDeclaration'], + [0, 'index'], + ['init', 'VariableDeclarator'], + ] return { modifiedAst: _node, - pathToNode: [...pathToNode.slice(0, -1), [expressionIndex, 'index']], + pathToNode: newpathToNode, } } diff --git a/src/lang/recast.test.ts b/src/lang/recast.test.ts index c924de64f6..7985d41b24 100644 --- a/src/lang/recast.test.ts +++ b/src/lang/recast.test.ts @@ -150,7 +150,7 @@ log(5, myVar) const recasted = recast(ast) expect(recasted.trim()).toBe(code.trim()) }) - it('recast long object exectution', () => { + it('recast long object execution', () => { const code = `const three = 3 const yo = { aStr: 'str', @@ -163,7 +163,7 @@ const yo = { const recasted = recast(ast) expect(recasted).toBe(code) }) - it('recast short object exectution', () => { + it('recast short object execution', () => { const code = `const yo = { key: 'val' } ` const { ast } = code2ast(code) diff --git a/src/lang/std/engineConnection.ts b/src/lang/std/engineConnection.ts index df1572b1e4..3af7af86a5 100644 --- a/src/lang/std/engineConnection.ts +++ b/src/lang/std/engineConnection.ts @@ -13,6 +13,10 @@ interface CommandInfo { range: SourceRange pathToNode: PathToNode parentId?: string + additionalData?: { + type: 'cap' + info: 'start' | 'end' + } } type WebSocketResponse = Models['OkWebSocketResponseData_type'] @@ -1069,14 +1073,42 @@ export class EngineCommandManager { } as const this.artifactMap[id] = artifact if ( - command.commandType === 'entity_linear_pattern' || - command.commandType === 'entity_circular_pattern' + (command.commandType === 'entity_linear_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) => { 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({ id, commandType: command.commandType, @@ -1388,12 +1420,6 @@ export class EngineCommandManager { const promise = new Promise((_resolve, reject) => { 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 ? getNodePathFromSourceRange(ast, range || [0, 0]) : [] @@ -1402,7 +1428,6 @@ export class EngineCommandManager { pathToNode, type: 'pending', commandType: command.type, - parentId: getParentId(), promise, resolve, } @@ -1419,10 +1444,14 @@ export class EngineCommandManager { resolve = _resolve }) const getParentId = (): string | undefined => { - if (command.type === 'extend_path') { - return command.path + if (command.type === 'extend_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 ? getNodePathFromSourceRange(ast, range || [0, 0]) diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index db1d0a4fa2..cc5c1768e5 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -1155,11 +1155,14 @@ export function addTagForSketchOnFace( a: ModifyAstBase, expressionName: string ) { + if (expressionName === 'close') { + return addTag(1)(a) + } if (expressionName in sketchLineHelperMap) { const { addTag } = sketchLineHelperMap[expressionName] 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 { @@ -1174,7 +1177,7 @@ function isAngleLiteral(lineArugement: Value): boolean { type addTagFn = (a: ModifyAstBase) => { modifiedAst: Program; tag: string } -function addTag(): addTagFn { +function addTag(tagIndex = 2): addTagFn { return ({ node, pathToNode }) => { const _node = { ...node } const { node: primaryCallExp } = getNodeFromPath( @@ -1184,12 +1187,12 @@ function addTag(): addTagFn { ) // Tag is always 3rd expression now, using arg index feels brittle // 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 = thirdArg || (createLiteral(findUniqueName(_node, 'seg', 2)) as Literal) const isTagExisting = !!thirdArg if (!isTagExisting) { - primaryCallExp.arguments[2] = tagLiteral + primaryCallExp.arguments[tagIndex] = tagLiteral } if ('value' in tagLiteral) { // Now TypeScript knows tagLiteral has a value property diff --git a/src/lang/wasm.ts b/src/lang/wasm.ts index 64845ede66..3895027cfd 100644 --- a/src/lang/wasm.ts +++ b/src/lang/wasm.ts @@ -66,6 +66,7 @@ export type { Position } from '../wasm-lib/kcl/bindings/Position' export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation' export type { Path } from '../wasm-lib/kcl/bindings/Path' 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 { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface' diff --git a/src/lib/cameraControls.ts b/src/lib/cameraControls.ts index a447b5b4ed..4d867866ed 100644 --- a/src/lib/cameraControls.ts +++ b/src/lib/cameraControls.ts @@ -39,7 +39,7 @@ export interface MouseGuard { rotate: MouseGuardHandler } -const butName = (e: React.MouseEvent) => ({ +export const butName = (e: React.MouseEvent) => ({ middle: !!(e.buttons & 4) || e.button === 1, right: !!(e.buttons & 2) || e.button === 2, left: !!(e.buttons & 1) || e.button === 0, diff --git a/src/lib/commandBarConfigs/modelingCommandConfig.ts b/src/lib/commandBarConfigs/modelingCommandConfig.ts index 45c88cf2a1..c0cec93622 100644 --- a/src/lib/commandBarConfigs/modelingCommandConfig.ts +++ b/src/lib/commandBarConfigs/modelingCommandConfig.ts @@ -117,7 +117,7 @@ export const modelingMachineConfig: CommandSetConfig< args: { selection: { inputType: 'selection', - selectionTypes: ['face'], + selectionTypes: ['extrude-wall', 'start-cap', 'end-cap'], multiple: false, // TODO: multiple selection required: true, skip: true, diff --git a/src/lib/selections.ts b/src/lib/selections.ts index 1f8287d5c9..3edde54f8a 100644 --- a/src/lib/selections.ts +++ b/src/lib/selections.ts @@ -6,7 +6,7 @@ import { v4 as uuidv4 } from 'uuid' import { EditorSelection } from '@codemirror/state' import { kclManager } from 'lang/KclSingleton' import { SelectionRange } from '@uiw/react-codemirror' -import { isOverlap } from 'lib/utils' +import { getNormalisedCoordinates, isOverlap } from 'lib/utils' import { isCursorInSketchCommandRange } from 'lang/util' import { Program } from 'lang/wasm' import { @@ -22,70 +22,12 @@ import { getParentGroup, PROFILE_START, } from 'clientSideScene/sceneEntities' -import { Mesh } from 'three' +import { Mesh, Object3D, Object3DEventMap } from 'three' import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra' export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b' 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 Selection = { @@ -93,7 +35,9 @@ export type Selection = { | 'default' | 'line-end' | 'line-mid' - | 'face' + | 'extrude-wall' + | 'start-cap' + | 'end-cap' | 'point' | 'edge' | 'line' @@ -106,15 +50,6 @@ export type Selections = { codeBasedSelections: Selection[] } -export interface SelectionRangeTypeMap { - [key: number]: Selection['type'] -} - -interface RangeAndId { - id: string - range: SourceRange -} - export async function getEventForSelectWithPoint( { data, @@ -139,8 +74,32 @@ export async function getEventForSelectWithPoint( }, } } - const sourceRange = engineCommandManager.artifactMap[data.entity_id]?.range - if (engineCommandManager.artifactMap[data.entity_id]) { + const _artifact = 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 { type: 'Set selection', data: { @@ -148,46 +107,17 @@ export async function getEventForSelectWithPoint( selection: { range: sourceRange, type: 'default' }, }, } - } - if (!sketchEnginePathId) return null - // 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 { - type: 'Set selection', - data: { - 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' }, - }, + } else { + // if we don't recognise the entity, select nothing + return { + type: 'Set selection', + data: { selectionType: 'singleCodeCursor' }, + } } } export function getEventForSegmentSelection( - obj: any + obj: Object3D ): ModelingMachineEvent | null { const group = getParentGroup(obj, [ STRAIGHT_SEGMENT, @@ -231,107 +161,54 @@ export function handleSelectionBatch({ }: { selections: Selections }): { - selectionRangeTypeMap: SelectionRangeTypeMap - codeMirrorSelection?: EditorSelection + engineEvents: Models['WebSocketRequest_type'][] + codeMirrorSelection: EditorSelection otherSelections: Axis[] + updateSceneObjectColors: () => void } { const ranges: ReturnType[] = [] - const selectionRangeTypeMap: SelectionRangeTypeMap = {} + const engineEvents: Models['WebSocketRequest_type'][] = + resetAndSetEngineEntitySelectionCmds( + codeToIdSelections(selections.codeBasedSelections) + ) selections.codeBasedSelections.forEach(({ range, type }) => { if (range?.[1]) { ranges.push(EditorSelection.cursor(range[1])) - selectionRangeTypeMap[range[1]] = type } }) if (ranges.length) return { - selectionRangeTypeMap, + engineEvents, codeMirrorSelection: EditorSelection.create( ranges, selections.codeBasedSelections.length - 1 ), otherSelections: selections.otherSelections, + updateSceneObjectColors: () => + updateSceneObjectColors(selections.codeBasedSelections), } return { - selectionRangeTypeMap, + codeMirrorSelection: EditorSelection.create( + [EditorSelection.cursor(kclManager.code.length)], + 0 + ), + engineEvents, 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 } export function processCodeMirrorRanges({ codeMirrorRanges, selectionRanges, - selectionRangeTypeMap, isShiftDown, }: { codeMirrorRanges: readonly SelectionRange[] selectionRanges: Selections - selectionRangeTypeMap: SelectionRangeTypeMap isShiftDown: boolean }): null | { modelingEvent: ModelingMachineEvent @@ -349,41 +226,13 @@ export function processCodeMirrorRanges({ if (!isChange) return null const codeBasedSelections: Selections['codeBasedSelections'] = codeMirrorRanges.map(({ from, to }) => { - if (selectionRangeTypeMap[to]) { - return { - type: selectionRangeTypeMap[to], - range: [from, to], - } - } return { type: 'default', range: [from, to], } }) - const idBasedSelections: SelectionToEngine[] = codeBasedSelections - .flatMap(({ type, range }): null | SelectionToEngine[] => { - // 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 + const idBasedSelections: SelectionToEngine[] = + codeToIdSelections(codeBasedSelections) if (!selectionRanges) return null updateSceneObjectColors(codeBasedSelections) @@ -486,24 +335,21 @@ export type CommonASTNode = { ast: Program } -export function buildCommonNodeFromSelection( - selectionRanges: Selections, - i: number -) { +function buildCommonNodeFromSelection(selectionRanges: Selections, i: number) { return { selection: selectionRanges.codeBasedSelections[i], ast: kclManager.ast, } } -export function nodeHasExtrude(node: CommonASTNode) { +function nodeHasExtrude(node: CommonASTNode) { return doesPipeHaveCallExp({ calleeName: 'extrude', ...node, }) } -export function nodeHasClose(node: CommonASTNode) { +function nodeHasClose(node: CommonASTNode) { return doesPipeHaveCallExp({ calleeName: 'close', ...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) return ( @@ -547,7 +393,7 @@ export function getSelectionType( return selection.codeBasedSelections .map((s, 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 { 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, + el: HTMLVideoElement, + streamDimensions: { streamWidth: number; streamHeight: number } +) { + const { x, y } = getNormalisedCoordinates({ + clientX: e.clientX, + clientY: e.clientY, + el, + ...streamDimensions, + }) + const result: Promise = 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 +} diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 0b320497b4..a199fec673 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -1,13 +1,6 @@ import { PathToNode, VariableDeclarator } from 'lang/wasm' -import { engineCommandManager } from 'lang/std/engineConnection' -import { - Axis, - Selection, - SelectionRangeTypeMap, - Selections, -} from 'lib/selections' +import { Axis, Selection, Selections } from 'lib/selections' import { assign, createMachine } from 'xstate' -import { isCursorInSketchCommandRange } from 'lang/util' import { getNodePathFromSourceRange } from 'lang/queryAst' import { kclManager } from 'lang/KclSingleton' import { @@ -26,7 +19,6 @@ import { } from 'components/Toolbar/EqualLength' import { addStartProfileAt, extrudeSketch } from 'lang/modifyAst' import { getNodeFromPath } from '../lang/queryAst' -import { CallExpression, PipeExpression } from '../lang/wasm' import { applyConstraintEqualAngle, equalAngleInfo, @@ -45,10 +37,10 @@ import { ModelingCommandSchema } from 'lib/commandBarConfigs/modelingCommandConf import { DefaultPlaneStr, sceneEntitiesManager, - quaternionFromSketchGroup, - sketchGroupFromPathToNode, } from 'clientSideScene/sceneEntities' import { sceneInfra } from 'clientSideScene/sceneInfra' +import { Vector3 } from 'three' +import { quaternionFromUpNForward } from 'clientSideScene/helpers' export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY' @@ -70,6 +62,13 @@ export type SetSelections = selection: Selections } +export interface SketchDetails { + sketchPathToNode: PathToNode + zAxis: [number, number, number] + yAxis: [number, number, number] + origin: [number, number, number] +} + export type ModelingMachineEvent = | { type: 'Enter sketch' @@ -77,9 +76,24 @@ export type ModelingMachineEvent = forceNewSketch?: boolean } } + | { type: 'Sketch On Face' } | { 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: 'Sketch no face' } @@ -109,18 +123,15 @@ export type ModelingMachineEvent = | { type: 'Equip Line tool' } | { type: 'Equip tangential arc to' } | { - type: 'done.invoke.animate-to-face' - data: { - sketchPathToNode: PathToNode - sketchNormalBackUp: [number, number, number] | null - } + type: 'done.invoke.animate-to-face' | 'done.invoke.animate-to-sketch' + data: SketchDetails } export type MoveDesc = { line: number; snippet: string } 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', tsTypes: {} as import('./modelingMachine.typegen').Typegen0, @@ -135,11 +146,14 @@ export const modelingMachine = createMachine( otherSelections: [], codeBasedSelections: [], } as Selections, - selectionRangeTypeMap: {} as SelectionRangeTypeMap, - 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 - sketchEnginePathId: '' as string, + sketchDetails: { + sketchPathToNode: [], + zAxis: [0, 0, 1], + yAxis: [0, 1, 0], + origin: [0, 0, 0], + } as null | SketchDetails, sketchPlaneId: '' as string, - sketchNormalBackUp: null as null | [number, number, number], + sketchEnginePathId: '' as string, moveDescs: [] as MoveDesc[], }, @@ -160,7 +174,6 @@ export const modelingMachine = createMachine( { target: 'animating to existing sketch', cond: 'Selection is on face', - actions: ['set sketch metadata'], }, 'Sketch no face', ], @@ -504,6 +517,11 @@ export const modelingMachine = createMachine( target: 'animating to plane', actions: ['reset sketch metadata'], }, + + 'Set selection': { + target: 'Sketch no face', + internal: true, + }, }, }, @@ -532,15 +550,15 @@ export const modelingMachine = createMachine( { src: 'animate-to-sketch', id: 'animate-to-sketch', - onDone: 'Sketch', + onDone: { + target: 'Sketch', + actions: 'set new sketch metadata', + }, }, ], entry: 'clientToEngine cam sync direction', }, - - 'animating to plane (copy)': {}, - 'animating to plane (copy) (copy)': {}, }, initial: 'idle', @@ -562,13 +580,13 @@ export const modelingMachine = createMachine( }, { guards: { - 'is editing existing sketch': ({ sketchPathToNode }) => { + 'is editing existing sketch': ({ sketchDetails }) => { // should check that the variable declaration is a pipeExpression // and that the pipeExpression contains a "startProfileAt" callExpression - if (!sketchPathToNode) return false + if (!sketchDetails?.sketchPathToNode) return false const variableDeclaration = getNodeFromPath( kclManager.ast, - sketchPathToNode, + sketchDetails.sketchPathToNode, 'VariableDeclarator' ).node if (variableDeclaration.type !== 'VariableDeclarator') return false @@ -621,128 +639,154 @@ export const modelingMachine = createMachine( }, // end guards actions: { - 'set sketchMetadata from pathToNode': assign(({ sketchPathToNode }) => { - if (!sketchPathToNode) return {} - return getSketchMetadataFromPathToNode(sketchPathToNode) + 'set sketchMetadata from pathToNode': assign(({ sketchDetails }) => { + if (!sketchDetails?.sketchPathToNode || !sketchDetails) return {} + return { + sketchDetails: { + ...sketchDetails, + sketchPathToNode: sketchDetails.sketchPathToNode, + }, + } }), 'hide default planes': () => { sceneInfra.removeDefaultPlanes() kclManager.hidePlanes() }, 'reset sketch metadata': assign({ - sketchPathToNode: null, + sketchDetails: null, sketchEnginePathId: '', sketchPlaneId: '', }), - 'set sketch metadata': assign(({ selectionRanges }) => { - const sourceRange = selectionRanges.codeBasedSelections[0].range - const sketchPathToNode = getNodePathFromSourceRange( - kclManager.ast, - sourceRange - ) - return getSketchMetadataFromPathToNode( - sketchPathToNode, - selectionRanges - ) - }), - 'set new sketch metadata': assign((_, { data }) => data), + 'set new sketch metadata': assign((_, { data }) => ({ + sketchDetails: data, + })), // TODO implement source ranges for all of these constraints // need to make the async like the modal constraints - 'Make selection horizontal': ({ selectionRanges, sketchPathToNode }) => { + 'Make selection horizontal': ({ selectionRanges, sketchDetails }) => { const { modifiedAst } = applyConstraintHorzVert( selectionRanges, 'horizontal', kclManager.ast, kclManager.programMemory ) + if (!sketchDetails) return sceneEntitiesManager.updateAstAndRejigSketch( - sketchPathToNode || [], - modifiedAst + sketchDetails.sketchPathToNode, + modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin ) }, - 'Make selection vertical': ({ selectionRanges, sketchPathToNode }) => { + 'Make selection vertical': ({ selectionRanges, sketchDetails }) => { const { modifiedAst } = applyConstraintHorzVert( selectionRanges, 'vertical', kclManager.ast, kclManager.programMemory ) + if (!sketchDetails) return sceneEntitiesManager.updateAstAndRejigSketch( - sketchPathToNode || [], - modifiedAst + sketchDetails.sketchPathToNode || [], + modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin ) }, - 'Constrain horizontally align': ({ - selectionRanges, - sketchPathToNode, - }) => { + 'Constrain horizontally align': ({ selectionRanges, sketchDetails }) => { const { modifiedAst } = applyConstraintHorzVertAlign({ selectionRanges, constraint: 'setVertDistance', }) + if (!sketchDetails) return sceneEntitiesManager.updateAstAndRejigSketch( - sketchPathToNode || [], - modifiedAst + sketchDetails?.sketchPathToNode || [], + modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin ) }, - 'Constrain vertically align': ({ selectionRanges, sketchPathToNode }) => { + 'Constrain vertically align': ({ selectionRanges, sketchDetails }) => { const { modifiedAst } = applyConstraintHorzVertAlign({ selectionRanges, constraint: 'setHorzDistance', }) + if (!sketchDetails) return sceneEntitiesManager.updateAstAndRejigSketch( - sketchPathToNode || [], - modifiedAst + sketchDetails?.sketchPathToNode || [], + modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin ) }, - 'Constrain snap to X': ({ selectionRanges, sketchPathToNode }) => { + 'Constrain snap to X': ({ selectionRanges, sketchDetails }) => { const { modifiedAst } = applyConstraintAxisAlign({ selectionRanges, constraint: 'snapToXAxis', }) + if (!sketchDetails) return sceneEntitiesManager.updateAstAndRejigSketch( - sketchPathToNode || [], - modifiedAst + sketchDetails?.sketchPathToNode || [], + modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin ) }, - 'Constrain snap to Y': ({ selectionRanges, sketchPathToNode }) => { + 'Constrain snap to Y': ({ selectionRanges, sketchDetails }) => { const { modifiedAst } = applyConstraintAxisAlign({ selectionRanges, constraint: 'snapToYAxis', }) + if (!sketchDetails) return sceneEntitiesManager.updateAstAndRejigSketch( - sketchPathToNode || [], - modifiedAst + sketchDetails?.sketchPathToNode || [], + modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin ) }, - 'Constrain equal length': ({ selectionRanges, sketchPathToNode }) => { + 'Constrain equal length': ({ selectionRanges, sketchDetails }) => { const { modifiedAst } = applyConstraintEqualLength({ selectionRanges, }) + if (!sketchDetails) return sceneEntitiesManager.updateAstAndRejigSketch( - sketchPathToNode || [], - modifiedAst + sketchDetails?.sketchPathToNode || [], + modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin ) }, - 'Constrain parallel': ({ selectionRanges, sketchPathToNode }) => { + 'Constrain parallel': ({ selectionRanges, sketchDetails }) => { const { modifiedAst } = applyConstraintEqualAngle({ selectionRanges, }) + if (!sketchDetails) return sceneEntitiesManager.updateAstAndRejigSketch( - sketchPathToNode || [], - modifiedAst + sketchDetails?.sketchPathToNode || [], + modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin ) }, - 'Constrain remove constraints': ({ - selectionRanges, - sketchPathToNode, - }) => { + 'Constrain remove constraints': ({ selectionRanges, sketchDetails }) => { const { modifiedAst } = applyRemoveConstrainingValues({ selectionRanges, }) + if (!sketchDetails) return sceneEntitiesManager.updateAstAndRejigSketch( - sketchPathToNode || [], - modifiedAst + sketchDetails?.sketchPathToNode || [], + modifiedAst, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin ) }, 'AST extrude': (_, event) => { @@ -754,7 +798,6 @@ export const modelingMachine = createMachine( distance.variableName && distance.insertIndex !== undefined ) { - console.log('adding variable!', distance) const newBody = [...ast.body] newBody.splice( distance.insertIndex, @@ -785,18 +828,25 @@ export const modelingMachine = createMachine( 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) { sceneEntitiesManager .tearDownSketch({ removeAxis: false }) .then(() => { sceneEntitiesManager.setupSketch({ - sketchPathToNode: sketchPathToNode || [], + sketchPathToNode: sketchDetails?.sketchPathToNode || [], + forward: sketchDetails.zAxis, + up: sketchDetails.yAxis, + position: sketchDetails.origin, }) }) } else { 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(), - 'set up draft line': ({ sketchPathToNode }) => { - sceneEntitiesManager.setUpDraftLine(sketchPathToNode || []) + 'set up draft line': ({ sketchDetails }) => { + if (!sketchDetails) return + sceneEntitiesManager.setUpDraftLine( + sketchDetails.sketchPathToNode, + sketchDetails.zAxis, + sketchDetails.yAxis + ) }, - 'set up draft arc': ({ sketchPathToNode }) => { - sceneEntitiesManager.setUpDraftArc(sketchPathToNode || []) + 'set up draft arc': ({ sketchDetails }) => { + 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({ - sketchPathToNode: sketchPathToNode || [], + sketchPathToNode: sketchDetails?.sketchPathToNode || [], draftSegment: 'line', + forward: sketchDetails?.zAxis || [0, 1, 0], + up: sketchDetails?.yAxis || [0, 0, 1], + position: sketchDetails?.origin, }), 'show default planes': () => { sceneInfra.showDefaultPlanes() sceneEntitiesManager.setupDefaultPlaneHover() kclManager.showPlanes() }, - 'setup noPoints onClick listener': ({ sketchPathToNode }) => { + 'setup noPoints onClick listener': ({ sketchDetails }) => { + if (!sketchDetails) return sceneEntitiesManager.createIntersectionPlane() - const sketchGroup = sketchGroupFromPathToNode({ - pathToNode: sketchPathToNode || [], - ast: kclManager.ast, - programMemory: kclManager.programMemory, - }) - const quaternion = quaternionFromSketchGroup(sketchGroup) + const quaternion = quaternionFromUpNForward( + new Vector3(...sketchDetails.yAxis), + new Vector3(...sketchDetails.zAxis) + ) sceneEntitiesManager.intersectionPlane && sceneEntitiesManager.intersectionPlane.setRotationFromQuaternion( quaternion ) + sceneEntitiesManager.intersectionPlane && + sceneEntitiesManager.intersectionPlane.position.copy( + new Vector3(...(sketchDetails?.origin || [0, 0, 0])) + ) sceneInfra.setCallbacks({ onClick: async (args) => { if (!args) return if (args.mouseEvent.which !== 1) return const { intersectionPoint } = args - if (!intersectionPoint?.twoD || !sketchPathToNode) return + if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) + return const { modifiedAst } = addStartProfileAt( kclManager.ast, - sketchPathToNode, + sketchDetails.sketchPathToNode, [intersectionPoint.twoD.x, intersectionPoint.twoD.y] ) await kclManager.updateAst(modifiedAst, false) @@ -854,8 +921,15 @@ export const modelingMachine = createMachine( }, }) }, - 'add axis n grid': ({ sketchPathToNode }) => - sceneEntitiesManager.createSketchAxis(sketchPathToNode || []), + 'add axis n grid': ({ sketchDetails }) => { + if (!sketchDetails) return + sceneEntitiesManager.createSketchAxis( + sketchDetails.sketchPathToNode || [], + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin + ) + }, 'reset client scene mouse handlers': () => { // when not in sketch mode we don't need any mouse listeners // (note the orbit controls are always active though) @@ -871,44 +945,3 @@ export const modelingMachine = createMachine( // end actions } ) - -function getSketchMetadataFromPathToNode( - pathToNode: PathToNode, - selectionRanges?: Selections -) { - const pipeExpression = getNodeFromPath( - 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, - } -} diff --git a/src/useStore.ts b/src/useStore.ts index e3ed2d1200..2a6ce03087 100644 --- a/src/useStore.ts +++ b/src/useStore.ts @@ -93,6 +93,8 @@ export interface StoreState { path: string }[] setHomeMenuItems: (items: { name: string; path: string }[]) => void + lastCodeMirrorSelectionUpdatedFromScene: number + setLastCodeMirrorSelectionUpdatedFromScene: (time: number) => void } export const useStore = create()( @@ -156,6 +158,9 @@ export const useStore = create()( setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }), homeMenuItems: [], setHomeMenuItems: (homeMenuItems) => set({ homeMenuItems }), + lastCodeMirrorSelectionUpdatedFromScene: Date.now(), + setLastCodeMirrorSelectionUpdatedFromScene: (time) => + set({ lastCodeMirrorSelectionUpdatedFromScene: time }), } }, { diff --git a/src/wasm-lib/kcl/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs index e0d4c94932..11ddb4c7ba 100644 --- a/src/wasm-lib/kcl/src/executor.rs +++ b/src/wasm-lib/kcl/src/executor.rs @@ -242,6 +242,8 @@ pub struct Face { pub y_axis: Point3d, /// The z-axis (normal). pub z_axis: Point3d, + /// the face id the sketch is on + pub face_id: uuid::Uuid, #[serde(rename = "__meta")] pub meta: Vec, } diff --git a/src/wasm-lib/kcl/src/std/extrude.rs b/src/wasm-lib/kcl/src/std/extrude.rs index d2c19e7035..0817d2d584 100644 --- a/src/wasm-lib/kcl/src/std/extrude.rs +++ b/src/wasm-lib/kcl/src/std/extrude.rs @@ -3,6 +3,7 @@ use anyhow::Result; use derive_docs::stdlib; use schemars::JsonSchema; +use uuid::Uuid; use crate::{ errors::{KclError, KclErrorDetails}, @@ -113,8 +114,9 @@ async fn inner_extrude(length: f64, sketch_group: Box, args: Args) // Create a hashmap for quick id lookup let mut face_id_map = std::collections::HashMap::new(); - let mut start_cap_id = None; - let mut end_cap_id = None; + // creating fake ids for start and end caps is to make extrudes mock-execute safe + let mut start_cap_id = Some(Uuid::new_v4()); + let mut end_cap_id = Some(Uuid::new_v4()); for face_info in face_infos { match face_info.cap { @@ -160,6 +162,18 @@ async fn inner_extrude(length: f64, sketch_group: Box, args: Args) 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(), + }, + })); } } diff --git a/src/wasm-lib/kcl/src/std/sketch.rs b/src/wasm-lib/kcl/src/std/sketch.rs index 52f766db7d..64118dc7d8 100644 --- a/src/wasm-lib/kcl/src/std/sketch.rs +++ b/src/wasm-lib/kcl/src/std/sketch.rs @@ -955,6 +955,7 @@ async fn start_sketch_on_face( y_axis: extrude_group.y_axis, z_axis: extrude_group.z_axis, meta: vec![args.source_range.into()], + face_id: extrude_plane_id, })) }