diff --git a/.codespellrc b/.codespellrc index 994f7e323..c608d03bc 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,3 +1,3 @@ [codespell] -ignore-words-list: crate,everytime,inout,co-ordinate +ignore-words-list: crate,everytime,inout,co-ordinate,ot skip: **/target,node_modules,build,**/Cargo.lock,./src-tauri/gen/schemas diff --git a/docs/kcl/KNOWN-ISSUES.md b/docs/kcl/KNOWN-ISSUES.md new file mode 100644 index 000000000..0d2aec5b0 --- /dev/null +++ b/docs/kcl/KNOWN-ISSUES.md @@ -0,0 +1,16 @@ +# Known Issues + +The following are bugs that are not in modeling-app or kcl itself. These bugs +once fixed in engine will just start working here with no language changes. + +- **Sketch on Face**: If your sketch is outside the edges of the face (on which you + are sketching) you will get multiple models returned instead of one single + model for that sketch and its underlying 3D object. + +- **Patterns**: If you try and pass a pattern to `hole` currently only the first + item in the pattern is being subtracted. This is an engine bug that is being + worked on. + +- **Import**: Right now you can import a file, even if that file has brep data + you cannot edit it. You also cannot move or transform the imported objects at + all. In the future, after v1, the engine will account for this. diff --git a/docs/kcl/std.json b/docs/kcl/std.json index a2b13c29d..23f593e1c 100644 --- a/docs/kcl/std.json +++ b/docs/kcl/std.json @@ -21033,6 +21033,1773 @@ "unpublished": false, "deprecated": false }, + { + "name": "patternCircular", + "summary": "A Circular pattern.", + "description": "", + "tags": [], + "args": [ + { + "name": "data", + "type": "CircularPatternData", + "schema": { + "description": "Data for a circular pattern.", + "type": "object", + "required": [ + "arcDegrees", + "axis", + "center", + "repetitions", + "rotateDuplicates" + ], + "properties": { + "arcDegrees": { + "description": "The arc angle (in degrees) to place the repetitions. Must be greater than 0.", + "type": "number", + "format": "double" + }, + "axis": { + "description": "The axis around which to make the pattern. This is a 3D vector.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 3, + "minItems": 3 + }, + "center": { + "description": "The center about which to make th pattern. This is a 3D vector.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 3, + "minItems": 3 + }, + "repetitions": { + "description": "The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once.", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "rotateDuplicates": { + "description": "Whether or not to rotate the duplicates as they are copied.", + "type": "boolean" + } + } + }, + "required": true + }, + { + "name": "geometry", + "type": "Geometry", + "schema": { + "description": "A geometry.", + "oneOf": [ + { + "description": "A sketch group is a collection of paths.", + "type": "object", + "required": [ + "__meta", + "id", + "position", + "rotation", + "start", + "type", + "value", + "xAxis", + "yAxis", + "zAxis" + ], + "properties": { + "__meta": { + "description": "Metadata.", + "type": "array", + "items": { + "description": "Metadata.", + "type": "object", + "required": [ + "sourceRange" + ], + "properties": { + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + } + }, + "entityId": { + "description": "The plane id or face id of the sketch group.", + "type": "string", + "format": "uuid", + "nullable": true + }, + "id": { + "description": "The id of the sketch group.", + "type": "string", + "format": "uuid" + }, + "position": { + "description": "The position of the sketch group.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 3, + "minItems": 3 + }, + "rotation": { + "description": "The rotation of the sketch group base plane.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 4, + "minItems": 4 + }, + "start": { + "description": "The starting path.", + "type": "object", + "required": [ + "__geoMeta", + "from", + "name", + "to" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "type": { + "type": "string", + "enum": [ + "SketchGroup" + ] + }, + "value": { + "description": "The paths in the sketch group.", + "type": "array", + "items": { + "description": "A path.", + "oneOf": [ + { + "description": "A path that goes to a point.", + "type": "object", + "required": [ + "__geoMeta", + "from", + "name", + "to", + "type" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "ToPoint" + ] + } + } + }, + { + "description": "A arc that is tangential to the last path segment that goes to a point", + "type": "object", + "required": [ + "__geoMeta", + "ccw", + "center", + "from", + "name", + "to", + "type" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "ccw": { + "description": "arc's direction", + "type": "boolean" + }, + "center": { + "description": "the arc's center", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "TangentialArcTo" + ] + } + } + }, + { + "description": "A path that is horizontal.", + "type": "object", + "required": [ + "__geoMeta", + "from", + "name", + "to", + "type", + "x" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "Horizontal" + ] + }, + "x": { + "description": "The x coordinate.", + "type": "number", + "format": "double" + } + } + }, + { + "description": "An angled line to.", + "type": "object", + "required": [ + "__geoMeta", + "from", + "name", + "to", + "type" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "AngledLineTo" + ] + }, + "x": { + "description": "The x coordinate.", + "type": "number", + "format": "double", + "nullable": true + }, + "y": { + "description": "The y coordinate.", + "type": "number", + "format": "double", + "nullable": true + } + } + }, + { + "description": "A base path.", + "type": "object", + "required": [ + "__geoMeta", + "from", + "name", + "to", + "type" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "Base" + ] + } + } + } + ] + } + }, + "xAxis": { + "description": "The x-axis of the sketch group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "yAxis": { + "description": "The y-axis of the sketch group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "zAxis": { + "description": "The z-axis of the sketch group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + } + } + }, + { + "description": "An extrude group is a collection of extrude surfaces.", + "type": "object", + "required": [ + "__meta", + "height", + "id", + "position", + "rotation", + "type", + "value", + "xAxis", + "yAxis", + "zAxis" + ], + "properties": { + "__meta": { + "description": "Metadata.", + "type": "array", + "items": { + "description": "Metadata.", + "type": "object", + "required": [ + "sourceRange" + ], + "properties": { + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + } + }, + "endCapId": { + "description": "The id of the extrusion end cap", + "type": "string", + "format": "uuid", + "nullable": true + }, + "height": { + "description": "The height of the extrude group.", + "type": "number", + "format": "double" + }, + "id": { + "description": "The id of the extrude group.", + "type": "string", + "format": "uuid" + }, + "position": { + "description": "The position of the extrude group.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 3, + "minItems": 3 + }, + "rotation": { + "description": "The rotation of the extrude group.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 4, + "minItems": 4 + }, + "startCapId": { + "description": "The id of the extrusion start cap", + "type": "string", + "format": "uuid", + "nullable": true + }, + "type": { + "type": "string", + "enum": [ + "ExtrudeGroup" + ] + }, + "value": { + "description": "The extrude surfaces.", + "type": "array", + "items": { + "description": "An extrude surface.", + "oneOf": [ + { + "description": "An extrude plane.", + "type": "object", + "required": [ + "faceId", + "id", + "name", + "position", + "rotation", + "sourceRange", + "type" + ], + "properties": { + "faceId": { + "description": "The face id for the extrude plane.", + "type": "string", + "format": "uuid" + }, + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "name": { + "description": "The name.", + "type": "string" + }, + "position": { + "description": "The position.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 3, + "minItems": 3 + }, + "rotation": { + "description": "The rotation.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 4, + "minItems": 4 + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "extrudePlane" + ] + } + } + } + ] + } + }, + "xAxis": { + "description": "The x-axis of the extrude group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "yAxis": { + "description": "The y-axis of the extrude group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "zAxis": { + "description": "The z-axis of the extrude group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + } + } + } + ] + }, + "required": true + } + ], + "returnValue": { + "name": "", + "type": "Geometries", + "schema": { + "description": "A set of geometry.", + "oneOf": [ + { + "type": [ + "object", + "array" + ], + "items": { + "description": "A sketch group is a collection of paths.", + "type": "object", + "required": [ + "__meta", + "id", + "position", + "rotation", + "start", + "value", + "xAxis", + "yAxis", + "zAxis" + ], + "properties": { + "__meta": { + "description": "Metadata.", + "type": "array", + "items": { + "description": "Metadata.", + "type": "object", + "required": [ + "sourceRange" + ], + "properties": { + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + } + }, + "entityId": { + "description": "The plane id or face id of the sketch group.", + "type": "string", + "format": "uuid", + "nullable": true + }, + "id": { + "description": "The id of the sketch group.", + "type": "string", + "format": "uuid" + }, + "position": { + "description": "The position of the sketch group.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 3, + "minItems": 3 + }, + "rotation": { + "description": "The rotation of the sketch group base plane.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 4, + "minItems": 4 + }, + "start": { + "description": "The starting path.", + "type": "object", + "required": [ + "__geoMeta", + "from", + "name", + "to" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "value": { + "description": "The paths in the sketch group.", + "type": "array", + "items": { + "description": "A path.", + "oneOf": [ + { + "description": "A path that goes to a point.", + "type": "object", + "required": [ + "__geoMeta", + "from", + "name", + "to", + "type" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "ToPoint" + ] + } + } + }, + { + "description": "A arc that is tangential to the last path segment that goes to a point", + "type": "object", + "required": [ + "__geoMeta", + "ccw", + "center", + "from", + "name", + "to", + "type" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "ccw": { + "description": "arc's direction", + "type": "boolean" + }, + "center": { + "description": "the arc's center", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "TangentialArcTo" + ] + } + } + }, + { + "description": "A path that is horizontal.", + "type": "object", + "required": [ + "__geoMeta", + "from", + "name", + "to", + "type", + "x" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "Horizontal" + ] + }, + "x": { + "description": "The x coordinate.", + "type": "number", + "format": "double" + } + } + }, + { + "description": "An angled line to.", + "type": "object", + "required": [ + "__geoMeta", + "from", + "name", + "to", + "type" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "AngledLineTo" + ] + }, + "x": { + "description": "The x coordinate.", + "type": "number", + "format": "double", + "nullable": true + }, + "y": { + "description": "The y coordinate.", + "type": "number", + "format": "double", + "nullable": true + } + } + }, + { + "description": "A base path.", + "type": "object", + "required": [ + "__geoMeta", + "from", + "name", + "to", + "type" + ], + "properties": { + "__geoMeta": { + "description": "Metadata.", + "type": "object", + "required": [ + "id", + "sourceRange" + ], + "properties": { + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + }, + "from": { + "description": "The from point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "name": { + "description": "The name of the path.", + "type": "string" + }, + "to": { + "description": "The to point.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "Base" + ] + } + } + } + ] + } + }, + "xAxis": { + "description": "The x-axis of the sketch group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "yAxis": { + "description": "The y-axis of the sketch group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "zAxis": { + "description": "The z-axis of the sketch group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + } + } + }, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "SketchGroups" + ] + } + } + }, + { + "type": [ + "object", + "array" + ], + "items": { + "description": "An extrude group is a collection of extrude surfaces.", + "type": "object", + "required": [ + "__meta", + "height", + "id", + "position", + "rotation", + "value", + "xAxis", + "yAxis", + "zAxis" + ], + "properties": { + "__meta": { + "description": "Metadata.", + "type": "array", + "items": { + "description": "Metadata.", + "type": "object", + "required": [ + "sourceRange" + ], + "properties": { + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + } + } + } + }, + "endCapId": { + "description": "The id of the extrusion end cap", + "type": "string", + "format": "uuid", + "nullable": true + }, + "height": { + "description": "The height of the extrude group.", + "type": "number", + "format": "double" + }, + "id": { + "description": "The id of the extrude group.", + "type": "string", + "format": "uuid" + }, + "position": { + "description": "The position of the extrude group.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 3, + "minItems": 3 + }, + "rotation": { + "description": "The rotation of the extrude group.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 4, + "minItems": 4 + }, + "startCapId": { + "description": "The id of the extrusion start cap", + "type": "string", + "format": "uuid", + "nullable": true + }, + "value": { + "description": "The extrude surfaces.", + "type": "array", + "items": { + "description": "An extrude surface.", + "oneOf": [ + { + "description": "An extrude plane.", + "type": "object", + "required": [ + "faceId", + "id", + "name", + "position", + "rotation", + "sourceRange", + "type" + ], + "properties": { + "faceId": { + "description": "The face id for the extrude plane.", + "type": "string", + "format": "uuid" + }, + "id": { + "description": "The id of the geometry.", + "type": "string", + "format": "uuid" + }, + "name": { + "description": "The name.", + "type": "string" + }, + "position": { + "description": "The position.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 3, + "minItems": 3 + }, + "rotation": { + "description": "The rotation.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "maxItems": 4, + "minItems": 4 + }, + "sourceRange": { + "description": "The source range.", + "type": "array", + "items": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "maxItems": 2, + "minItems": 2 + }, + "type": { + "type": "string", + "enum": [ + "extrudePlane" + ] + } + } + } + ] + } + }, + "xAxis": { + "description": "The x-axis of the extrude group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "yAxis": { + "description": "The y-axis of the extrude group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "zAxis": { + "description": "The z-axis of the extrude group base plane in the 3D space", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + } + } + }, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "ExtrudeGroups" + ] + } + } + } + ] + }, + "required": true + }, + "unpublished": false, + "deprecated": false + }, { "name": "patternLinear", "summary": "A linear pattern.", @@ -27979,9 +29746,33 @@ }, { "name": "tag", - "type": "String", + "type": "SketchOnFaceTag", "schema": { - "type": "string", + "description": "A tag for sketch on face.", + "anyOf": [ + { + "oneOf": [ + { + "description": "The start face as in before you extruded. This could also be known as the bottom face. But we do not call it bottom because it would be the top face if you extruded it in the opposite direction or flipped the camera.", + "type": "string", + "enum": [ + "start" + ] + }, + { + "description": "The end face after you extruded. This could also be known as the top face. But we do not call it top because it would be the bottom face if you extruded it in the opposite direction or flipped the camera.", + "type": "string", + "enum": [ + "end" + ] + } + ] + }, + { + "description": "A string tag for the face you want to sketch on.", + "type": "string" + } + ], "nullable": true }, "required": true diff --git a/docs/kcl/std.md b/docs/kcl/std.md index 07175263d..02e4f24f1 100644 --- a/docs/kcl/std.md +++ b/docs/kcl/std.md @@ -41,6 +41,7 @@ * [`log2`](#log2) * [`max`](#max) * [`min`](#min) + * [`patternCircular`](#patternCircular) * [`patternLinear`](#patternLinear) * [`pi`](#pi) * [`pow`](#pow) @@ -3998,6 +3999,194 @@ min(args: [number]) -> number +### patternCircular + +A Circular pattern. + + + +``` +patternCircular(data: CircularPatternData, geometry: Geometry) -> Geometries +``` + +#### Arguments + +* `data`: `CircularPatternData` - Data for a circular pattern. +``` +{ + // The arc angle (in degrees) to place the repetitions. Must be greater than 0. + arcDegrees: number, + // The axis around which to make the pattern. This is a 3D vector. + axis: [number, number, number], + // The center about which to make th pattern. This is a 3D vector. + center: [number, number, number], + // The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once. + repetitions: number, + // Whether or not to rotate the duplicates as they are copied. + rotateDuplicates: string, +} +``` +* `geometry`: `Geometry` - A geometry. +``` +{ + // The plane id or face id of the sketch group. + entityId: uuid, + // The id of the sketch group. + id: uuid, + // The position of the sketch group. + position: [number, number, number], + // The rotation of the sketch group base plane. + rotation: [number, number, number, number], + // The starting path. + start: { + // The from point. + from: [number, number], + // The name of the path. + name: string, + // The to point. + to: [number, number], +}, + type: "SketchGroup", + // The paths in the sketch group. + value: [{ + // The from point. + from: [number, number], + // The name of the path. + name: string, + // The to point. + to: [number, number], + type: "ToPoint", +} | +{ + // arc's direction + ccw: string, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // The name of the path. + name: string, + // The to point. + to: [number, number], + type: "TangentialArcTo", +} | +{ + // The from point. + from: [number, number], + // The name of the path. + name: string, + // The to point. + to: [number, number], + type: "Horizontal", + // The x coordinate. + x: number, +} | +{ + // The from point. + from: [number, number], + // The name of the path. + name: string, + // The to point. + to: [number, number], + type: "AngledLineTo", + // The x coordinate. + x: number, + // The y coordinate. + y: number, +} | +{ + // The from point. + from: [number, number], + // The name of the path. + name: string, + // The to point. + to: [number, number], + type: "Base", +}], + // The x-axis of the sketch group base plane in the 3D space + xAxis: { + x: number, + y: number, + z: number, +}, + // The y-axis of the sketch group base plane in the 3D space + yAxis: { + x: number, + y: number, + z: number, +}, + // The z-axis of the sketch group base plane in the 3D space + zAxis: { + x: number, + y: number, + z: number, +}, +} | +{ + // The id of the extrusion end cap + endCapId: uuid, + // The height of the extrude group. + height: number, + // The id of the extrude group. + id: uuid, + // The position of the extrude group. + position: [number, number, number], + // The rotation of the extrude group. + rotation: [number, number, number, number], + // The id of the extrusion start cap + startCapId: uuid, + type: "ExtrudeGroup", + // The extrude surfaces. + value: [{ + // The face id for the extrude plane. + faceId: uuid, + // The id of the geometry. + id: uuid, + // The name. + name: string, + // The position. + position: [number, number, number], + // The rotation. + rotation: [number, number, number, number], + // The source range. + sourceRange: [number, number], + type: "extrudePlane", +}], + // The x-axis of the extrude group base plane in the 3D space + xAxis: { + x: number, + y: number, + z: number, +}, + // The y-axis of the extrude group base plane in the 3D space + yAxis: { + x: number, + y: number, + z: number, +}, + // The z-axis of the extrude group base plane in the 3D space + zAxis: { + x: number, + y: number, + z: number, +}, +} +``` + +#### Returns + +* `Geometries` - A set of geometry. +``` +{ + type: "SketchGroups", +} | +{ + type: "ExtrudeGroups", +} +``` + + + ### patternLinear A linear pattern. @@ -5149,7 +5338,7 @@ Start a sketch on a specific plane or face. ``` -startSketchOn(data: SketchData, tag: String) -> SketchSurface +startSketchOn(data: SketchData, tag: SketchOnFaceTag) -> SketchSurface ``` #### Arguments @@ -5239,7 +5428,11 @@ startSketchOn(data: SketchData, tag: String) -> SketchSurface }, } ``` -* `tag`: `String` +* `tag`: `SketchOnFaceTag` - A tag for sketch on face. +``` +"start" | "end" | +string +``` #### Returns diff --git a/src/App.tsx b/src/App.tsx index c699f5b7e..344f1edc4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,8 +24,6 @@ import { type IndexLoaderData } from 'lib/types' import { paths } from 'lib/paths' import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { onboardingPaths } from 'routes/Onboarding/paths' -import { cameraMouseDragGuards } from 'lib/cameraControls' -import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models' import { CodeMenu } from 'components/CodeMenu' import { TextEditor } from 'components/TextEditor' import { Themes, getSystemTheme } from 'lib/theme' @@ -56,8 +54,7 @@ export function App() { })) const { settings } = useGlobalStateContext() - const { showDebugPanel, onboardingStatus, cameraControls, theme } = - settings?.context || {} + const { showDebugPanel, onboardingStatus, theme } = settings?.context || {} const { state, send } = useModelingContext() const editorTheme = theme === Themes.System ? getSystemTheme() : theme @@ -119,31 +116,6 @@ export function App() { }, cmd_id: newCmdId, }) - } else { - const interactionGuards = cameraMouseDragGuards[cameraControls] - let interaction: CameraDragInteractionType_type - - const eWithButton = { ...e, button: buttonDownInStream } - - if (interactionGuards.pan.callback(eWithButton)) { - interaction = 'pan' - } else if (interactionGuards.rotate.callback(eWithButton)) { - interaction = 'rotate' - } else if (interactionGuards.zoom.dragCallback(eWithButton)) { - interaction = 'zoom' - } else { - return - } - - debounceSocketSend({ - type: 'modeling_cmd_req', - cmd: { - type: 'camera_drag_move', - interaction, - window: { x, y }, - }, - cmd_id: newCmdId, - }) } } diff --git a/src/clientSideScene/ClientSideSceneComp.tsx b/src/clientSideScene/ClientSideSceneComp.tsx new file mode 100644 index 000000000..7325336aa --- /dev/null +++ b/src/clientSideScene/ClientSideSceneComp.tsx @@ -0,0 +1,252 @@ +import { useRef, useEffect, useState } from 'react' +import { useModelingContext } from 'hooks/useModelingContext' + +import { cameraMouseDragGuards } from 'lib/cameraControls' +import { useGlobalStateContext } from 'hooks/useGlobalStateContext' +import { useStore } from 'useStore' +import { + DEBUG_SHOW_BOTH_SCENES, + ReactCameraProperties, + sceneInfra, +} from './sceneInfra' +import { throttle } from 'lib/utils' + +function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } { + const [isCamMoving, setIsCamMoving] = useState(false) + const [isTween, setIsTween] = useState(false) + + const { state } = useModelingContext() + + useEffect(() => { + sceneInfra.setIsCamMovingCallback((isMoving, isTween) => { + setIsCamMoving(isMoving) + setIsTween(isTween) + }) + }, []) + + if (DEBUG_SHOW_BOTH_SCENES || !isCamMoving) + return { hideClient: false, hideServer: false } + let hideServer = state.matches('Sketch') || state.matches('Sketch no face') + if (isTween) { + hideServer = false + } + + return { hideClient: !hideServer, hideServer } +} + +export const ClientSideScene = ({ + cameraControls, +}: { + cameraControls: ReturnType< + typeof useGlobalStateContext + >['settings']['context']['cameraControls'] +}) => { + const canvasRef = useRef(null) + const { state, send } = useModelingContext() + const { hideClient, hideServer } = useShouldHideScene() + const { setHighlightRange } = useStore((s) => ({ + setHighlightRange: s.setHighlightRange, + highlightRange: s.highlightRange, + })) + + // Listen for changes to the camera controls setting + // and update the client-side scene's controls accordingly. + useEffect(() => { + sceneInfra.setInteractionGuards(cameraMouseDragGuards[cameraControls]) + }, [cameraControls]) + useEffect(() => { + sceneInfra.updateOtherSelectionColors( + state?.context?.selectionRanges?.otherSelections || [] + ) + }, [state?.context?.selectionRanges?.otherSelections]) + + useEffect(() => { + if (!canvasRef.current) return + const canvas = canvasRef.current + canvas.appendChild(sceneInfra.renderer.domElement) + sceneInfra.animate() + sceneInfra.setHighlightCallback(setHighlightRange) + canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false) + canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false) + canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false) + sceneInfra.setSend(send) + return () => { + canvas?.removeEventListener('mousemove', sceneInfra.onMouseMove) + canvas?.removeEventListener('mousedown', sceneInfra.onMouseDown) + canvas?.removeEventListener('mouseup', sceneInfra.onMouseUp) + } + }, []) + + return ( +
+ ) +} + +const throttled = throttle((a: ReactCameraProperties) => { + if (a.type === 'perspective' && a.fov) { + sceneInfra.dollyZoom(a.fov) + } +}, 1000 / 15) + +export const CamDebugSettings = () => { + const [camSettings, setCamSettings] = useState({ + type: 'perspective', + fov: 12, + position: [0, 0, 0], + quaternion: [0, 0, 0, 1], + }) + const [fov, setFov] = useState(12) + + useEffect(() => { + sceneInfra.setReactCameraPropertiesCallback(setCamSettings) + }, [sceneInfra]) + useEffect(() => { + if (camSettings.type === 'perspective' && camSettings.fov) { + setFov(camSettings.fov) + } + }, [(camSettings as any)?.fov]) + + return ( +
+

cam settings

+ perspective cam + { + if (camSettings.type === 'perspective') { + sceneInfra.useOrthographicCamera() + } else { + sceneInfra.usePerspectiveCamera() + } + }} + /> + {camSettings.type === 'perspective' && ( + { + setFov(parseFloat(e.target.value)) + + throttled({ + ...camSettings, + fov: parseFloat(e.target.value), + }) + }} + className="w-full cursor-pointer pointer-events-auto" + /> + )} + {camSettings.type === 'perspective' && ( +
+ fov + { + sceneInfra.setCam({ + ...camSettings, + fov: parseFloat(e.target.value), + }) + }} + /> +
+ )} + {camSettings.type === 'orthographic' && ( + <> +
+ fov + { + sceneInfra.setCam({ + ...camSettings, + zoom: parseFloat(e.target.value), + }) + }} + /> +
+ + )} +
+ Position +
    +
  • + x: + { + sceneInfra.setCam({ + ...camSettings, + position: [ + parseFloat(e.target.value), + camSettings.position[1], + camSettings.position[2], + ], + }) + }} + /> +
  • +
  • + y: + { + sceneInfra.setCam({ + ...camSettings, + position: [ + camSettings.position[0], + parseFloat(e.target.value), + camSettings.position[2], + ], + }) + }} + /> +
  • +
  • + z: + { + sceneInfra.setCam({ + ...camSettings, + position: [ + camSettings.position[0], + camSettings.position[1], + parseFloat(e.target.value), + ], + }) + }} + /> +
  • +
+
+
+ ) +} diff --git a/src/clientSideScene/clientSideScene.ts b/src/clientSideScene/sceneEntities.ts similarity index 94% rename from src/clientSideScene/clientSideScene.ts rename to src/clientSideScene/sceneEntities.ts index f82189f6c..e971a07f7 100644 --- a/src/clientSideScene/clientSideScene.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -25,14 +25,14 @@ import { INTERSECTION_PLANE_LAYER, isQuaternionVertical, RAYCASTABLE_PLANE, - setupSingleton, + sceneInfra, SKETCH_GROUP_SEGMENTS, SKETCH_LAYER, X_AXIS, XZ_PLANE, Y_AXIS, YZ_PLANE, -} from './setup' +} from './sceneInfra' import { CallExpression, getTangentialArcToInfo, @@ -85,7 +85,10 @@ export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body' export const TANGENTIAL_ARC_TO__SEGMENT_DASH = 'tangential-arc-to-segment-body-dashed' -class ClientSideScene { +// This singleton Class is responsible for all of the things the user sees and interacts with. +// That mostly mean sketch elements. +// Cameras, controls, raycasters, etc are handled by sceneInfra +class SceneEntities { scene: Scene sceneProgramMemory: ProgramMemory = { root: {}, return: null } activeSegments: { [key: string]: Group } = {} @@ -93,18 +96,18 @@ class ClientSideScene { axisGroup: Group | null = null currentSketchQuaternion: Quaternion | null = null constructor() { - this.scene = setupSingleton?.scene - setupSingleton?.setOnCamChange(this.onCamChange) + this.scene = sceneInfra?.scene + sceneInfra?.setOnCamChange(this.onCamChange) } onCamChange = () => { - const orthoFactor = orthoScale(setupSingleton.camera) + const orthoFactor = orthoScale(sceneInfra.camera) Object.values(this.activeSegments).forEach((segment) => { const factor = - setupSingleton.camera instanceof OrthographicCamera + sceneInfra.camera instanceof OrthographicCamera ? orthoFactor - : perspScale(setupSingleton.camera, segment) + : perspScale(sceneInfra.camera, segment) if ( segment.userData.from && segment.userData.to && @@ -135,9 +138,9 @@ class ClientSideScene { }) if (this.axisGroup) { const factor = - setupSingleton.camera instanceof OrthographicCamera + sceneInfra.camera instanceof OrthographicCamera ? orthoFactor - : perspScale(setupSingleton.camera, this.axisGroup) + : perspScale(sceneInfra.camera, this.axisGroup) const x = this.axisGroup.getObjectByName(X_AXIS) x?.scale.set(1, factor, 1) const y = this.axisGroup.getObjectByName(Y_AXIS) @@ -241,12 +244,13 @@ class ClientSideScene { engineCommandManager, programMemoryOverride, }) - this.sceneProgramMemory = programMemory const sketchGroup = sketchGroupFromPathToNode({ pathToNode: sketchPathToNode, ast: kclManager.ast, programMemory, }) + if (!Array.isArray(sketchGroup?.value)) return + this.sceneProgramMemory = programMemory const group = new Group() group.userData = { type: SKETCH_GROUP_SEGMENTS, @@ -258,11 +262,11 @@ class ClientSideScene { sketchGroup.position[1], sketchGroup.position[2] ) - const orthoFactor = orthoScale(setupSingleton.camera) + const orthoFactor = orthoScale(sceneInfra.camera) const factor = - setupSingleton.camera instanceof OrthographicCamera + sceneInfra.camera instanceof OrthographicCamera ? orthoFactor - : perspScale(setupSingleton.camera, dummy) + : perspScale(sceneInfra.camera, dummy) sketchGroup.value.forEach((segment, index) => { let segPathToNode = getNodePathFromSourceRange( draftSegment ? truncatedAst : kclManager.ast, @@ -309,7 +313,7 @@ class ClientSideScene { this.scene.add(group) if (!draftSegment) { - setupSingleton.setCallbacks({ + sceneInfra.setCallbacks({ onDrag: (args) => { this.onDragSegment({ ...args, @@ -319,7 +323,7 @@ class ClientSideScene { onMove: () => {}, onClick: (args) => { if (!args || !args.object) { - setupSingleton.modelingSend({ + sceneInfra.modelingSend({ type: 'Set selection', data: { selectionType: 'singleCodeCursor', @@ -330,7 +334,7 @@ class ClientSideScene { const { object } = args const event = getEventForSegmentSelection(object) if (!event) return - setupSingleton.modelingSend(event) + sceneInfra.modelingSend(event) }, onMouseEnter: ({ object }) => { // TODO change the color of the segment to yellow? @@ -350,15 +354,15 @@ class ClientSideScene { parent.userData.pathToNode, 'CallExpression' ).node - setupSingleton.highlightCallback([node.start, node.end]) + sceneInfra.highlightCallback([node.start, node.end]) const yellow = 0xffff00 colorSegment(object, yellow) return } - setupSingleton.highlightCallback([0, 0]) + sceneInfra.highlightCallback([0, 0]) }, onMouseLeave: ({ object }) => { - setupSingleton.highlightCallback([0, 0]) + sceneInfra.highlightCallback([0, 0]) const parent = getParentGroup(object) const isSelected = parent?.userData?.isSelected colorSegment(object, isSelected ? 0x0000ff : 0xffffff) @@ -371,7 +375,7 @@ class ClientSideScene { }, }) } else { - setupSingleton.setCallbacks({ + sceneInfra.setCallbacks({ onDrag: () => {}, onClick: async (args) => { if (!args) return @@ -426,7 +430,7 @@ class ClientSideScene { }, }) } - setupSingleton.controls.enableRotate = false + sceneInfra.controls.enableRotate = false } updateAstAndRejigSketch = async ( sketchPathToNode: PathToNode, @@ -529,7 +533,7 @@ class ClientSideScene { this.sceneProgramMemory = programMemory const sketchGroup = programMemory.root[variableDeclarationName] .value as Path[] - const orthoFactor = orthoScale(setupSingleton.camera) + const orthoFactor = orthoScale(sceneInfra.camera) sketchGroup.forEach((segment, index) => { const segPathToNode = getNodePathFromSourceRange( modifiedAst, @@ -545,9 +549,9 @@ class ClientSideScene { // const prevSegment = sketchGroup.slice(index - 1)[0] const type = group?.userData?.type const factor = - setupSingleton.camera instanceof OrthographicCamera + sceneInfra.camera instanceof OrthographicCamera ? orthoFactor - : perspScale(setupSingleton.camera, group) + : perspScale(sceneInfra.camera, group) if (type === TANGENTIAL_ARC_TO_SEGMENT) { this.updateTangentialArcToSegment({ prevSegment: sketchGroup[index - 1], @@ -704,9 +708,9 @@ class ClientSideScene { } async animateAfterSketch() { if (isReducedMotion()) { - setupSingleton.usePerspectiveCamera() + sceneInfra.usePerspectiveCamera() } else { - await setupSingleton.animateToPerspective() + await sceneInfra.animateToPerspective() } } removeSketchGrid() { @@ -739,7 +743,7 @@ class ClientSideScene { reject() } } - setupSingleton.controls.enableRotate = true + sceneInfra.controls.enableRotate = true this.activeSegments = {} // maybe should reset onMove etc handlers if (shouldResolve) resolve(true) @@ -758,7 +762,7 @@ class ClientSideScene { }) } setupDefaultPlaneHover() { - setupSingleton.setCallbacks({ + sceneInfra.setCallbacks({ onMouseEnter: ({ object }) => { if (object.parent.userData.type !== DEFAULT_PLANES) return const type: DefaultPlane = object.userData.type @@ -783,7 +787,7 @@ class ClientSideScene { planeString = posNorm ? 'XZ' : '-XZ' normal = posNorm ? [0, 1, 0] : [0, -1, 0] } - setupSingleton.modelingSend({ + sceneInfra.modelingSend({ type: 'Select default plane', data: { plane: planeString, @@ -797,7 +801,7 @@ class ClientSideScene { export type DefaultPlaneStr = 'XY' | 'XZ' | 'YZ' | '-XY' | '-XZ' | '-YZ' -export const clientSideScene = new ClientSideScene() +export const sceneEntitiesManager = new SceneEntities() // calculations/pure-functions/easy to test so no excuse not to diff --git a/src/clientSideScene/setup.test.ts b/src/clientSideScene/sceneInfra.test.ts similarity index 95% rename from src/clientSideScene/setup.test.ts rename to src/clientSideScene/sceneInfra.test.ts index dbd1d98a9..90a710069 100644 --- a/src/clientSideScene/setup.test.ts +++ b/src/clientSideScene/sceneInfra.test.ts @@ -1,5 +1,5 @@ import { Quaternion } from 'three' -import { isQuaternionVertical } from './setup' +import { isQuaternionVertical } from './sceneInfra' describe('isQuaternionVertical', () => { it('should identify vertical quaternions', () => { diff --git a/src/clientSideScene/setup.tsx b/src/clientSideScene/sceneInfra.ts similarity index 80% rename from src/clientSideScene/setup.tsx rename to src/clientSideScene/sceneInfra.ts index 64e95c0f9..3b9410237 100644 --- a/src/clientSideScene/setup.tsx +++ b/src/clientSideScene/sceneInfra.ts @@ -24,7 +24,6 @@ import { Object3DEventMap, } from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' -import { useRef, useEffect, useState } from 'react' import { engineCommandManager } from 'lang/std/engineConnection' import { v4 as uuidv4 } from 'uuid' import { isReducedMotion, roundOff, throttle } from 'lib/utils' @@ -33,9 +32,7 @@ import { useModelingContext } from 'hooks/useModelingContext' import { deg2Rad } from 'lib/utils2d' import * as TWEEN from '@tweenjs/tween.js' import { MouseGuard, cameraMouseDragGuards } from 'lib/cameraControls' -import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { SourceRange } from 'lang/wasm' -import { useStore } from 'useStore' import { Axis } from 'lib/selections' import { createGridHelper } from './helpers' @@ -71,12 +68,14 @@ interface ThreeCamValues { isPerspective: boolean } +const lastCmdDelay = 50 + let lastCmd: any = null let lastCmdTime: number = Date.now() let lastCmdTimeoutId: number | null = null const sendLastReliableChannel = () => { - if (lastCmd && Date.now() - lastCmdTime >= 300) { + if (lastCmd && Date.now() - lastCmdTime >= lastCmdDelay) { engineCommandManager.sendSceneCommand(lastCmd, true) lastCmdTime = Date.now() } @@ -98,16 +97,57 @@ const throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => { if (lastCmdTimeoutId !== null) { clearTimeout(lastCmdTimeoutId) } - lastCmdTimeoutId = setTimeout(sendLastReliableChannel, 300) as any as number + lastCmdTimeoutId = setTimeout( + sendLastReliableChannel, + lastCmdDelay + ) as any as number }, 1000 / 30) +let lastPerspectiveCmd: any = null +let lastPerspectiveCmdTime: number = Date.now() +let lastPerspectiveCmdTimeoutId: number | null = null + +const sendLastPerspectiveReliableChannel = () => { + if ( + lastPerspectiveCmd && + Date.now() - lastPerspectiveCmdTime >= lastCmdDelay + ) { + engineCommandManager.sendSceneCommand(lastPerspectiveCmd, true) + lastPerspectiveCmdTime = Date.now() + } +} + const throttledUpdateEngineFov = throttle( (vals: { position: Vector3 quaternion: Quaternion zoom: number fov: number - }) => updateEngineFov(vals), + }) => { + const cmd = { + type: 'modeling_cmd_req', + cmd_id: uuidv4(), + cmd: { + type: 'default_camera_perspective_settings', + ...convertThreeCamValuesToEngineCam({ + ...vals, + isPerspective: true, + }), + fov_y: vals.fov, + ...calculateNearFarFromFOV(vals.fov), + }, + } as any + engineCommandManager.sendSceneCommand(cmd) + lastPerspectiveCmd = cmd + lastPerspectiveCmdTime = Date.now() + if (lastPerspectiveCmdTimeoutId !== null) { + clearTimeout(lastPerspectiveCmdTimeoutId) + } + lastPerspectiveCmdTimeoutId = setTimeout( + sendLastPerspectiveReliableChannel, + lastCmdDelay + ) as any as number + }, 1000 / 15 ) @@ -138,7 +178,7 @@ interface onMoveCallbackArgs { intersection: Intersection> } -type ReactCameraProperties = +export type ReactCameraProperties = | { type: 'perspective' fov?: number @@ -152,8 +192,11 @@ type ReactCameraProperties = quaternion: [number, number, number, number] } -class SetupSingleton { - static instance: SetupSingleton +// This singleton class is responsible for all of the under the hood setup for the client side scene. +// That is the cameras and switching between them, raycasters for click mouse events and their abstractions (onClick etc), setting up controls. +// Anything that added the the scene for the user to interact with is probably in SceneEntities.ts +class SceneInfra { + static instance: SceneInfra scene: Scene camera: PerspectiveCamera | OrthographicCamera renderer: WebGLRenderer @@ -290,7 +333,7 @@ class SetupSingleton { const light = new AmbientLight(0x505050) // soft white light this.scene.add(light) - SetupSingleton.instance = this + SceneInfra.instance = this } private _isCamMovingCallback: (isMoving: boolean, isTween: boolean) => void = () => {} @@ -481,7 +524,7 @@ class SetupSingleton { const targetFov = 4 const fovAnimationStep = (currentFov - targetFov) / FRAMES_TO_ANIMATE_IN - let frameWaitOnFinish = 5 + let frameWaitOnFinish = 10 const animateFovChange = () => { if (this.camera instanceof PerspectiveCamera) { @@ -670,7 +713,7 @@ class SetupSingleton { } | null => { this.planeRaycaster.setFromCamera( this.currentMouseVector, - setupSingleton.camera + sceneInfra.camera ) const planeIntersects = this.planeRaycaster.intersectObjects( this.scene.children, @@ -933,7 +976,7 @@ class SetupSingleton { if (planesGroup) this.scene.remove(planesGroup) } updateOtherSelectionColors = (otherSelections: Axis[]) => { - const axisGroup = setupSingleton.scene.children.find( + const axisGroup = sceneInfra.scene.children.find( ({ userData }) => userData?.type === AXIS_GROUP ) const axisMap: { [key: string]: Axis } = { @@ -955,247 +998,7 @@ class SetupSingleton { } } -export const setupSingleton = new SetupSingleton() - -function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } { - const [isCamMoving, setIsCamMoving] = useState(false) - const [isTween, setIsTween] = useState(false) - - const { state } = useModelingContext() - - useEffect(() => { - setupSingleton.setIsCamMovingCallback((isMoving, isTween) => { - setIsCamMoving(isMoving) - setIsTween(isTween) - }) - }, []) - - if (DEBUG_SHOW_BOTH_SCENES || !isCamMoving) - return { hideClient: false, hideServer: false } - let hideServer = state.matches('Sketch') || state.matches('Sketch no face') - if (isTween) { - hideServer = false - } - - return { hideClient: !hideServer, hideServer } -} - -export const ClientSideScene = ({ - cameraControls, -}: { - cameraControls: ReturnType< - typeof useGlobalStateContext - >['settings']['context']['cameraControls'] -}) => { - const canvasRef = useRef(null) - const { state, send } = useModelingContext() - const { hideClient, hideServer } = useShouldHideScene() - const { setHighlightRange } = useStore((s) => ({ - setHighlightRange: s.setHighlightRange, - highlightRange: s.highlightRange, - })) - - // Listen for changes to the camera controls setting - // and update the client-side scene's controls accordingly. - useEffect(() => { - setupSingleton.setInteractionGuards(cameraMouseDragGuards[cameraControls]) - }, [cameraControls]) - useEffect(() => { - setupSingleton.updateOtherSelectionColors( - state?.context?.selectionRanges?.otherSelections || [] - ) - }, [state?.context?.selectionRanges?.otherSelections]) - - useEffect(() => { - if (!canvasRef.current) return - const canvas = canvasRef.current - canvas.appendChild(setupSingleton.renderer.domElement) - setupSingleton.animate() - setupSingleton.setHighlightCallback(setHighlightRange) - canvas.addEventListener('mousemove', setupSingleton.onMouseMove, false) - canvas.addEventListener('mousedown', setupSingleton.onMouseDown, false) - canvas.addEventListener('mouseup', setupSingleton.onMouseUp, false) - setupSingleton.setSend(send) - return () => { - canvas?.removeEventListener('mousemove', setupSingleton.onMouseMove) - canvas?.removeEventListener('mousedown', setupSingleton.onMouseDown) - canvas?.removeEventListener('mouseup', setupSingleton.onMouseUp) - } - }, []) - - return ( -
- ) -} - -const throttled = throttle((a: ReactCameraProperties) => { - if (a.type === 'perspective' && a.fov) { - setupSingleton.dollyZoom(a.fov) - } -}, 1000 / 15) - -export const CamDebugSettings = () => { - const [camSettings, setCamSettings] = useState({ - type: 'perspective', - fov: 12, - position: [0, 0, 0], - quaternion: [0, 0, 0, 1], - }) - const [fov, setFov] = useState(12) - - useEffect(() => { - setupSingleton.setReactCameraPropertiesCallback(setCamSettings) - }, [setupSingleton]) - useEffect(() => { - if (camSettings.type === 'perspective' && camSettings.fov) { - setFov(camSettings.fov) - } - }, [(camSettings as any)?.fov]) - - return ( -
-

cam settings

- perspective cam - { - if (camSettings.type === 'perspective') { - setupSingleton.useOrthographicCamera() - } else { - setupSingleton.usePerspectiveCamera() - } - }} - /> - {camSettings.type === 'perspective' && ( - { - setFov(parseFloat(e.target.value)) - - throttled({ - ...camSettings, - fov: parseFloat(e.target.value), - }) - }} - className="w-full cursor-pointer pointer-events-auto" - /> - )} - {camSettings.type === 'perspective' && ( -
- fov - { - setupSingleton.setCam({ - ...camSettings, - fov: parseFloat(e.target.value), - }) - }} - /> -
- )} - {camSettings.type === 'orthographic' && ( - <> -
- fov - { - setupSingleton.setCam({ - ...camSettings, - zoom: parseFloat(e.target.value), - }) - }} - /> -
- - )} -
- Position -
    -
  • - x: - { - setupSingleton.setCam({ - ...camSettings, - position: [ - parseFloat(e.target.value), - camSettings.position[1], - camSettings.position[2], - ], - }) - }} - /> -
  • -
  • - y: - { - setupSingleton.setCam({ - ...camSettings, - position: [ - camSettings.position[0], - parseFloat(e.target.value), - camSettings.position[2], - ], - }) - }} - /> -
  • -
  • - z: - { - setupSingleton.setCam({ - ...camSettings, - position: [ - camSettings.position[0], - camSettings.position[1], - parseFloat(e.target.value), - ], - }) - }} - /> -
  • -
-
-
- ) -} +export const sceneInfra = new SceneInfra() function convertThreeCamValuesToEngineCam({ position, @@ -1242,29 +1045,6 @@ function calculateNearFarFromFOV(fov: number) { return { z_near: 0.1, z_far } } -function updateEngineFov(args: { - position: Vector3 - quaternion: Quaternion - zoom: number - fov: number -}) { - engineCommandManager.sendSceneCommand( - { - type: 'modeling_cmd_req', - cmd_id: uuidv4(), - cmd: { - type: 'default_camera_perspective_settings', - ...convertThreeCamValuesToEngineCam({ - ...args, - isPerspective: true, - }), - fov_y: args.fov, - ...calculateNearFarFromFOV(args.fov), - }, - } as any /* TODO - this command isn't in the spec yet, remove any when it is */ - ) -} - export function isQuaternionVertical(q: Quaternion) { const v = new Vector3(0, 0, 1).applyQuaternion(q) // no x or y components means it's vertical diff --git a/src/clientSideScene/segments.ts b/src/clientSideScene/segments.ts index 1a279ccd8..4623c7076 100644 --- a/src/clientSideScene/segments.ts +++ b/src/clientSideScene/segments.ts @@ -25,9 +25,9 @@ import { TANGENTIAL_ARC_TO_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT_BODY, TANGENTIAL_ARC_TO__SEGMENT_DASH, -} from './clientSideScene' +} from './sceneEntities' import { getTangentPointFromPreviousArc } from 'lib/utils2d' -import { ARROWHEAD } from './setup' +import { ARROWHEAD } from './sceneInfra' export function straightSegment({ from, diff --git a/src/components/CamToggle.tsx b/src/components/CamToggle.tsx index f55cd0c8b..97bc49389 100644 --- a/src/components/CamToggle.tsx +++ b/src/components/CamToggle.tsx @@ -1,10 +1,10 @@ import { useState, useEffect } from 'react' -import { setupSingleton } from '../clientSideScene/setup' +import { sceneInfra } from '../clientSideScene/sceneInfra' import { engineCommandManager } from 'lang/std/engineConnection' import { throttle, isReducedMotion } from 'lib/utils' const updateDollyZoom = throttle( - (newFov: number) => setupSingleton.dollyZoom(newFov), + (newFov: number) => sceneInfra.dollyZoom(newFov), 1000 / 15 ) @@ -15,19 +15,19 @@ export const CamToggle = () => { useEffect(() => { engineCommandManager.waitForReady.then(async () => { - setupSingleton.dollyZoom(fov) + sceneInfra.dollyZoom(fov) }) }, []) const toggleCamera = () => { if (isPerspective) { isReducedMotion() - ? setupSingleton.useOrthographicCamera() - : setupSingleton.animateToOrthographic() + ? sceneInfra.useOrthographicCamera() + : sceneInfra.animateToOrthographic() } else { isReducedMotion() - ? setupSingleton.usePerspectiveCamera() - : setupSingleton.animateToPerspective() + ? sceneInfra.usePerspectiveCamera() + : sceneInfra.animateToPerspective() } setIsPerspective(!isPerspective) } @@ -60,9 +60,9 @@ export const CamToggle = () => {