From c571b153183dbaafd73c4dfb036f30e50397af3a Mon Sep 17 00:00:00 2001 From: Jess Frazelle Date: Wed, 13 Mar 2024 17:16:57 -0700 Subject: [PATCH] Fix circle (#1715) * start of circle Signed-off-by: Jess Frazelle * fixews Signed-off-by: Jess Frazelle * fix all samples Signed-off-by: Jess Frazelle * docs Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * fix tests Signed-off-by: Jess Frazelle * bump version; Signed-off-by: Jess Frazelle * fixes Signed-off-by: Jess Frazelle * fixes Signed-off-by: Jess Frazelle --------- Signed-off-by: Jess Frazelle --- docs/kcl/circle.md | 193 +++- docs/kcl/hole.md | 4 +- docs/kcl/std.json | 1312 +++++++++++++++++++++++---- src/wasm-lib/Cargo.lock | 2 +- src/wasm-lib/kcl/Cargo.toml | 2 +- src/wasm-lib/kcl/src/std/mod.rs | 100 +- src/wasm-lib/kcl/src/std/shapes.rs | 216 ++--- src/wasm-lib/kcl/src/std/sketch.rs | 14 +- src/wasm-lib/tests/executor/main.rs | 20 +- 9 files changed, 1519 insertions(+), 344 deletions(-) diff --git a/docs/kcl/circle.md b/docs/kcl/circle.md index 4537458c0..88b5d907d 100644 --- a/docs/kcl/circle.md +++ b/docs/kcl/circle.md @@ -1,22 +1,43 @@ --- title: "circle" -excerpt: "Sketch a circle on the given plane" +excerpt: "Sketch a circle." layout: manual --- -Sketch a circle on the given plane +Sketch a circle. ```js -circle(center: [number, number], radius: number, surface: SketchSurface, tag?: String) -> SketchGroup +circle(center: [number], radius: number, tag?: String, sketch_surface_or_group: SketchSurfaceOrGroup) -> SketchGroup +``` + +### Examples + +```js +const circles = startSketchOn('XY') + |> circle([5, 5], 1, %) + |> patternLinear2d({ + axis: [1, 1], + repetitions: 12, + distance: 3 + }, %) + +const rectangle = startSketchOn('XY') + |> startProfileAt([0, 0], %) + |> line([0, 50], %) + |> line([50, 0], %) + |> line([0, -50], %) + |> close(%) + |> hole(circles, %) ``` ### Arguments -* `center`: `[number, number]` (REQUIRED) +* `center`: `[number]` (REQUIRED) * `radius`: `number` (REQUIRED) -* `surface`: `SketchSurface` - A sketch group type. (REQUIRED) +* `tag`: `String` (OPTIONAL) +* `sketch_surface_or_group`: `SketchSurfaceOrGroup` - A sketch surface or a sketch group. (REQUIRED) ```js { // The id of the plane. @@ -75,9 +96,169 @@ circle(center: [number, number], radius: number, surface: SketchSurface, tag?: S y: number, z: number, }, +} | +{ + // The plane id or face id of the sketch group. + entityId: uuid, + // The id of the sketch group. + id: uuid, + // What the sketch is on (can be a plane or a face). + on: { + // The id of the plane. + id: uuid, + // Origin of the plane. + origin: { + x: number, + y: number, + z: number, +}, + type: "plane", + // Type for a plane. + value: "XY" | "XZ" | "YZ" | "Custom", + // What should the plane’s X axis be? + xAxis: { + x: number, + y: number, + z: number, +}, + // What should the plane’s Y axis be? + yAxis: { + x: number, + y: number, + z: number, +}, + // The z-axis (normal). + zAxis: { + x: number, + y: number, + z: number, +}, +} | +{ + // The id of the face. + id: uuid, + // The original sketch group id of the object we are sketching on. + sketchGroupId: uuid, + type: "face", + // The tag of the face. + value: string, + // What should the face’s X axis be? + xAxis: { + x: number, + y: number, + z: number, +}, + // What should the face’s Y axis be? + yAxis: { + x: number, + y: number, + z: number, +}, + // The z-axis (normal). + zAxis: { + x: number, + y: number, + z: number, +}, +}, + // 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], +}, + // 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: "TangentialArc", +} | +{ + // 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, +}, } ``` -* `tag`: `String` (OPTIONAL) ### Returns diff --git a/docs/kcl/hole.md b/docs/kcl/hole.md index 96cca4bbb..1d043d8f0 100644 --- a/docs/kcl/hole.md +++ b/docs/kcl/hole.md @@ -21,8 +21,8 @@ const square = startSketchOn('XY') |> line([10, 0], %) |> line([0, -10], %) |> close(%) - |> hole(circle([2, 2], .5, startSketchOn('XY')), %) - |> hole(circle([2, 8], .5, startSketchOn('XY')), %) + |> hole(circle([2, 2], .5, %), %) + |> hole(circle([2, 8], .5, %), %) |> extrude(2, %) ``` diff --git a/docs/kcl/std.json b/docs/kcl/std.json index 8bd1e7dcb..f14159488 100644 --- a/docs/kcl/std.json +++ b/docs/kcl/std.json @@ -18031,13 +18031,13 @@ }, { "name": "circle", - "summary": "Sketch a circle on the given plane", + "summary": "Sketch a circle.", "description": "", "tags": [], "args": [ { "name": "center", - "type": "[number, number]", + "type": "[number]", "schema": { "type": "array", "items": { @@ -18059,181 +18059,325 @@ "required": true }, { - "name": "surface", - "type": "SketchSurface", + "name": "tag", + "type": "String", "schema": { - "description": "A sketch group type.", - "oneOf": [ + "type": "string", + "nullable": true + }, + "required": false + }, + { + "name": "sketch_surface_or_group", + "type": "SketchSurfaceOrGroup", + "schema": { + "description": "A sketch surface or a sketch group.", + "anyOf": [ { - "description": "A plane.", - "type": "object", - "required": [ - "__meta", - "id", - "origin", - "type", - "value", - "xAxis", - "yAxis", - "zAxis" - ], - "properties": { - "__meta": { - "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 + "description": "A sketch group type.", + "oneOf": [ + { + "description": "A plane.", + "type": "object", + "required": [ + "__meta", + "id", + "origin", + "type", + "value", + "xAxis", + "yAxis", + "zAxis" + ], + "properties": { + "__meta": { + "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 + } + } + } + }, + "id": { + "description": "The id of the plane.", + "type": "string", + "format": "uuid" + }, + "origin": { + "description": "Origin of the plane.", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" }, - "maxItems": 2, - "minItems": 2 + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "type": { + "type": "string", + "enum": [ + "plane" + ] + }, + "value": { + "description": "Type for a plane.", + "oneOf": [ + { + "type": "string", + "enum": [ + "XY", + "XZ", + "YZ" + ] + }, + { + "description": "A custom plane.", + "type": "string", + "enum": [ + "Custom" + ] + } + ] + }, + "xAxis": { + "description": "What should the plane’s X axis be?", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "yAxis": { + "description": "What should the plane’s Y axis be?", + "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 (normal).", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } } } } }, - "id": { - "description": "The id of the plane.", - "type": "string", - "format": "uuid" - }, - "origin": { - "description": "Origin of the plane.", + { + "description": "A face.", "type": "object", "required": [ - "x", - "y", - "z" + "__meta", + "id", + "sketchGroupId", + "type", + "value", + "xAxis", + "yAxis", + "zAxis" ], "properties": { - "x": { - "type": "number", - "format": "double" + "__meta": { + "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 + } + } + } }, - "y": { - "type": "number", - "format": "double" + "id": { + "description": "The id of the face.", + "type": "string", + "format": "uuid" }, - "z": { - "type": "number", - "format": "double" - } - } - }, - "type": { - "type": "string", - "enum": [ - "plane" - ] - }, - "value": { - "description": "Type for a plane.", - "oneOf": [ - { + "sketchGroupId": { + "description": "The original sketch group id of the object we are sketching on.", + "type": "string", + "format": "uuid" + }, + "type": { "type": "string", "enum": [ - "XY", - "XZ", - "YZ" + "face" ] }, - { - "description": "A custom plane.", - "type": "string", - "enum": [ - "Custom" - ] - } - ] - }, - "xAxis": { - "description": "What should the plane’s X axis be?", - "type": "object", - "required": [ - "x", - "y", - "z" - ], - "properties": { - "x": { - "type": "number", - "format": "double" + "value": { + "description": "The tag of the face.", + "type": "string" }, - "y": { - "type": "number", - "format": "double" + "xAxis": { + "description": "What should the face’s X axis be?", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } }, - "z": { - "type": "number", - "format": "double" - } - } - }, - "yAxis": { - "description": "What should the plane’s Y axis be?", - "type": "object", - "required": [ - "x", - "y", - "z" - ], - "properties": { - "x": { - "type": "number", - "format": "double" + "yAxis": { + "description": "What should the face’s Y axis be?", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } }, - "y": { - "type": "number", - "format": "double" - }, - "z": { - "type": "number", - "format": "double" - } - } - }, - "zAxis": { - "description": "The z-axis (normal).", - "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 (normal).", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } } } } - } + ] }, { - "description": "A face.", + "description": "A sketch group is a collection of paths.", "type": "object", "required": [ "__meta", "id", - "sketchGroupId", - "type", + "on", + "position", + "rotation", + "start", "value", "xAxis", "yAxis", @@ -18241,6 +18385,7 @@ ], "properties": { "__meta": { + "description": "Metadata.", "type": "array", "items": { "description": "Metadata.", @@ -18263,28 +18408,853 @@ } } }, + "entityId": { + "description": "The plane id or face id of the sketch group.", + "type": "string", + "format": "uuid", + "nullable": true + }, "id": { - "description": "The id of the face.", + "description": "The id of the sketch group.", "type": "string", "format": "uuid" }, - "sketchGroupId": { - "description": "The original sketch group id of the object we are sketching on.", - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "face" + "on": { + "description": "What the sketch is on (can be a plane or a face).", + "oneOf": [ + { + "description": "A plane.", + "type": "object", + "required": [ + "__meta", + "id", + "origin", + "type", + "value", + "xAxis", + "yAxis", + "zAxis" + ], + "properties": { + "__meta": { + "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 + } + } + } + }, + "id": { + "description": "The id of the plane.", + "type": "string", + "format": "uuid" + }, + "origin": { + "description": "Origin of the plane.", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "type": { + "type": "string", + "enum": [ + "plane" + ] + }, + "value": { + "description": "Type for a plane.", + "oneOf": [ + { + "type": "string", + "enum": [ + "XY", + "XZ", + "YZ" + ] + }, + { + "description": "A custom plane.", + "type": "string", + "enum": [ + "Custom" + ] + } + ] + }, + "xAxis": { + "description": "What should the plane’s X axis be?", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "yAxis": { + "description": "What should the plane’s Y axis be?", + "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 (normal).", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + } + } + }, + { + "description": "A face.", + "type": "object", + "required": [ + "__meta", + "id", + "sketchGroupId", + "type", + "value", + "xAxis", + "yAxis", + "zAxis" + ], + "properties": { + "__meta": { + "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 + } + } + } + }, + "id": { + "description": "The id of the face.", + "type": "string", + "format": "uuid" + }, + "sketchGroupId": { + "description": "The original sketch group id of the object we are sketching on.", + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "face" + ] + }, + "value": { + "description": "The tag of the face.", + "type": "string" + }, + "xAxis": { + "description": "What should the face’s X axis be?", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + }, + "yAxis": { + "description": "What should the face’s Y axis be?", + "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 (normal).", + "type": "object", + "required": [ + "x", + "y", + "z" + ], + "properties": { + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + }, + "z": { + "type": "number", + "format": "double" + } + } + } + } + } ] }, + "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 tag of the face.", - "type": "string" + "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 arc that is tangential to the last path segment", + "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": [ + "TangentialArc" + ] + } + } + }, + { + "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": "What should the face’s X axis be?", + "description": "The x-axis of the sketch group base plane in the 3D space", "type": "object", "required": [ "x", @@ -18307,7 +19277,7 @@ } }, "yAxis": { - "description": "What should the face’s Y axis be?", + "description": "The y-axis of the sketch group base plane in the 3D space", "type": "object", "required": [ "x", @@ -18330,7 +19300,7 @@ } }, "zAxis": { - "description": "The z-axis (normal).", + "description": "The z-axis of the sketch group base plane in the 3D space", "type": "object", "required": [ "x", @@ -18357,18 +19327,10 @@ ] }, "required": true - }, - { - "name": "tag", - "type": "String", - "schema": { - "type": "string" - }, - "required": false } ], "returnValue": { - "name": "SketchGroup", + "name": "", "type": "SketchGroup", "schema": { "description": "A sketch group is a collection of paths.", @@ -19330,7 +20292,9 @@ }, "unpublished": false, "deprecated": false, - "examples": [] + "examples": [ + "const circles = startSketchOn('XY')\n |> circle([5, 5], 1, %)\n |> patternLinear2d({\n axis: [1, 1],\n repetitions: 12,\n distance: 3\n }, %)\n\nconst rectangle = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 50], %)\n |> line([50, 0], %)\n |> line([0, -50], %)\n |> close(%)\n |> hole(circles, %)" + ] }, { "name": "close", @@ -31731,7 +32695,7 @@ "unpublished": false, "deprecated": false, "examples": [ - "const square = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %)\n |> close(%)\n |> hole(circle([2, 2], .5, startSketchOn('XY')), %)\n |> hole(circle([2, 8], .5, startSketchOn('XY')), %)\n |> extrude(2, %)" + "const square = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %)\n |> close(%)\n |> hole(circle([2, 2], .5, %), %)\n |> hole(circle([2, 8], .5, %), %)\n |> extrude(2, %)" ] }, { diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock index 01f1c56cc..bb8b92467 100644 --- a/src/wasm-lib/Cargo.lock +++ b/src/wasm-lib/Cargo.lock @@ -1909,7 +1909,7 @@ dependencies = [ [[package]] name = "kcl-lib" -version = "0.1.45" +version = "0.1.46" dependencies = [ "anyhow", "approx 0.5.1", diff --git a/src/wasm-lib/kcl/Cargo.toml b/src/wasm-lib/kcl/Cargo.toml index 7b0f56f22..d22e1a2da 100644 --- a/src/wasm-lib/kcl/Cargo.toml +++ b/src/wasm-lib/kcl/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kcl-lib" description = "KittyCAD Language implementation and tools" -version = "0.1.45" +version = "0.1.46" edition = "2021" license = "MIT" repository = "https://github.com/KittyCAD/modeling-app" diff --git a/src/wasm-lib/kcl/src/std/mod.rs b/src/wasm-lib/kcl/src/std/mod.rs index 96db06f25..9759f484a 100644 --- a/src/wasm-lib/kcl/src/std/mod.rs +++ b/src/wasm-lib/kcl/src/std/mod.rs @@ -49,6 +49,7 @@ lazy_static! { Box::new(crate::std::segment::SegAng), Box::new(crate::std::segment::AngleToMatchLengthX), Box::new(crate::std::segment::AngleToMatchLengthY), + Box::new(crate::std::shapes::Circle), Box::new(crate::std::sketch::LineTo), Box::new(crate::std::sketch::Line), Box::new(crate::std::sketch::XLineTo), @@ -130,7 +131,7 @@ impl StdLib { .map(|internal_fn| (internal_fn.name(), internal_fn)) .collect(); - let kcl_internal_fns: [Box; 1] = [Box::::default()]; + let kcl_internal_fns: [Box; 0] = []; let kcl_fns = kcl_internal_fns .into_iter() .map(|internal_fn| (internal_fn.name(), internal_fn)) @@ -265,6 +266,103 @@ impl Args { Ok((numbers[0], numbers[1])) } + fn get_circle_args( + &self, + ) -> Result<([f64; 2], f64, crate::std::shapes::SketchSurfaceOrGroup, Option), KclError> { + let first_value = self + .args + .first() + .ok_or_else(|| { + KclError::Type(KclErrorDetails { + message: format!( + "Expected a [number, number] as the first argument, found `{:?}`", + self.args + ), + source_ranges: vec![self.source_range], + }) + })? + .get_json_value()?; + + let center: [f64; 2] = if let serde_json::Value::Array(arr) = first_value { + if arr.len() != 2 { + return Err(KclError::Type(KclErrorDetails { + message: format!( + "Expected a [number, number] as the first argument, found `{:?}`", + self.args + ), + source_ranges: vec![self.source_range], + })); + } + let x = parse_json_number_as_f64(&arr[0], self.source_range)?; + let y = parse_json_number_as_f64(&arr[1], self.source_range)?; + [x, y] + } else { + return Err(KclError::Type(KclErrorDetails { + message: format!( + "Expected a [number, number] as the first argument, found `{:?}`", + self.args + ), + source_ranges: vec![self.source_range], + })); + }; + + let second_value = self + .args + .get(1) + .ok_or_else(|| { + KclError::Type(KclErrorDetails { + message: format!("Expected a number as the second argument, found `{:?}`", self.args), + source_ranges: vec![self.source_range], + }) + })? + .get_json_value()?; + + let radius: f64 = serde_json::from_value(second_value).map_err(|e| { + KclError::Type(KclErrorDetails { + message: format!("Failed to deserialize number from JSON: {}", e), + source_ranges: vec![self.source_range], + }) + })?; + + let third_value = self.args.get(2).ok_or_else(|| { + KclError::Type(KclErrorDetails { + message: format!( + "Expected a SketchGroup or SketchSurface as the third argument, found `{:?}`", + self.args + ), + source_ranges: vec![self.source_range], + }) + })?; + + let sketch_group_or_surface = if let MemoryItem::SketchGroup(sg) = third_value { + crate::std::shapes::SketchSurfaceOrGroup::SketchGroup(sg.clone()) + } else if let MemoryItem::Plane(sg) = third_value { + crate::std::shapes::SketchSurfaceOrGroup::SketchSurface(SketchSurface::Plane(sg.clone())) + } else if let MemoryItem::Face(sg) = third_value { + crate::std::shapes::SketchSurfaceOrGroup::SketchSurface(SketchSurface::Face(sg.clone())) + } else { + return Err(KclError::Type(KclErrorDetails { + message: format!( + "Expected a SketchGroup or SketchSurface as the third argument, found `{:?}`", + self.args + ), + source_ranges: vec![self.source_range], + })); + }; + + if let Some(fourth_value) = self.args.get(3) { + let tag: String = serde_json::from_value(fourth_value.get_json_value()?).map_err(|e| { + KclError::Type(KclErrorDetails { + message: format!("Failed to deserialize String from JSON: {}", e), + source_ranges: vec![self.source_range], + }) + })?; + Ok((center, radius, sketch_group_or_surface, Some(tag))) + } else { + Ok((center, radius, sketch_group_or_surface, None)) + } + } + fn get_segment_name_sketch_group(&self) -> Result<(String, Box), KclError> { // Iterate over our args, the first argument should be a UserVal with a string value. // The second argument should be a SketchGroup. diff --git a/src/wasm-lib/kcl/src/std/shapes.rs b/src/wasm-lib/kcl/src/std/shapes.rs index 524de0afa..2c1b015c5 100644 --- a/src/wasm-lib/kcl/src/std/shapes.rs +++ b/src/wasm-lib/kcl/src/std/shapes.rs @@ -1,155 +1,83 @@ +//! Standard library shapes. + +use anyhow::Result; +use derive_docs::stdlib; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use super::kcl_stdlib::KclStdLibFn; use crate::{ - ast::types::{FunctionExpression, Program}, - docs::StdLibFn, + errors::KclError, + executor::MemoryItem, + std::{Args, SketchGroup, SketchSurface}, }; -pub const CIRCLE_FN: &str = r#" -(center, radius, surface, tag?) => { -const sg = startProfileAt([center[0] + radius, center[1]], surface) - |> arc({ - angle_end: 360, - angle_start: 0, - radius: radius, - tag: tag - }, %) - |> close(%) - return sg -} - "#; - -#[derive(Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -pub struct Circle { - function: FunctionExpression, - program: Program, +/// A sketch surface or a sketch group. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(untagged)] +pub enum SketchSurfaceOrGroup { + SketchSurface(SketchSurface), + SketchGroup(Box), } -impl Default for Circle { - fn default() -> Self { - // TODO in https://github.com/KittyCAD/modeling-app/issues/1018 - // Don't unwrap here, parse it at compile-time. - let (src, function) = super::kcl_stdlib::extract_function(CIRCLE_FN).unwrap(); - Self { - function: *function, - program: src, - } - } +/// Sketch a circle. +pub async fn circle(args: Args) -> Result { + let (center, radius, sketch_surface_or_group, tag): ([f64; 2], f64, SketchSurfaceOrGroup, Option) = + args.get_circle_args()?; + + let sketch_group = inner_circle(center, radius, tag, sketch_surface_or_group, args).await?; + Ok(MemoryItem::SketchGroup(sketch_group)) } -impl std::fmt::Debug for Circle { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - "circle".fmt(f) - } -} - -/// TODO: Parse the KCL in a macro and generate these -impl StdLibFn for Circle { - fn name(&self) -> String { - "circle".to_owned() - } - - fn summary(&self) -> String { - "Sketch a circle on the given plane".to_owned() - } - - fn description(&self) -> String { - String::new() - } - - fn tags(&self) -> Vec { - Vec::new() - } - - fn args(&self) -> Vec { - let mut settings = schemars::gen::SchemaSettings::openapi3(); - settings.inline_subschemas = true; - let mut generator = schemars::gen::SchemaGenerator::new(settings); - let mut args = Vec::new(); - for parameter in &self.function.params { - match parameter.identifier.name.as_str() { - "center" => { - args.push(crate::docs::StdLibFnArg { - name: parameter.identifier.name.to_owned(), - type_: "[number, number]".to_string(), - schema: <[f64; 2]>::json_schema(&mut generator), - required: true, - }); - } - "radius" => { - args.push(crate::docs::StdLibFnArg { - name: parameter.identifier.name.to_owned(), - type_: "number".to_string(), - schema: ::json_schema(&mut generator), - required: true, - }); - } - "surface" => { - args.push(crate::docs::StdLibFnArg { - name: parameter.identifier.name.to_owned(), - type_: "SketchSurface".to_string(), - schema: ::json_schema(&mut generator), - required: true, - }); - } - "tag" => { - args.push(crate::docs::StdLibFnArg { - name: parameter.identifier.name.to_owned(), - type_: "String".to_string(), - schema: ::json_schema(&mut generator), - required: false, - }); - } - _ => panic!("Unknown parameter: {:?}", parameter.identifier.name), - } - } - args - } - - fn return_value(&self) -> Option { - let mut settings = schemars::gen::SchemaSettings::openapi3(); - settings.inline_subschemas = true; - let mut generator = schemars::gen::SchemaGenerator::new(settings); - Some(crate::docs::StdLibFnArg { - name: "SketchGroup".to_owned(), - type_: "SketchGroup".to_string(), - schema: ::json_schema(&mut generator), - required: true, - }) - } - - fn unpublished(&self) -> bool { - false - } - - fn deprecated(&self) -> bool { - false - } - - fn examples(&self) -> Vec { - vec![] - } - - fn std_lib_fn(&self) -> crate::std::StdFn { - todo!() - } - - fn clone_box(&self) -> Box { - Box::new(self.to_owned()) - } -} - -impl KclStdLibFn for Circle { - fn function(&self) -> &FunctionExpression { - &self.function - } - fn program(&self) -> &Program { - &self.program - } - - fn kcl_clone_box(&self) -> Box { - Box::new(self.clone()) - } +/// Sketch a circle. +/// +/// ```no_run +/// const circles = startSketchOn('XY') +/// |> circle([5, 5], 1, %) +/// |> patternLinear2d({axis: [1,1], repetitions: 12, distance: 3}, %) +/// +/// const rectangle = startSketchOn('XY') +/// |> startProfileAt([0, 0], %) +/// |> line([0, 50], %) +/// |> line([50, 0], %) +/// |> line([0, -50], %) +/// |> close(%) +/// |> hole(circles, %) +/// ``` +#[stdlib { + name = "circle", +}] +async fn inner_circle( + center: [f64; 2], + radius: f64, + tag: Option, + sketch_surface_or_group: SketchSurfaceOrGroup, + args: Args, +) -> Result, KclError> { + let sketch_surface = match sketch_surface_or_group { + SketchSurfaceOrGroup::SketchSurface(surface) => surface, + SketchSurfaceOrGroup::SketchGroup(group) => group.on, + }; + let mut sketch_group = crate::std::sketch::inner_start_profile_at( + crate::std::sketch::LineData::Point([center[0] + radius, center[1]]), + sketch_surface, + args.clone(), + ) + .await?; + + // Call arc. + sketch_group = crate::std::sketch::inner_arc( + crate::std::sketch::ArcData::AnglesAndRadius { + angle_start: 0.0, + angle_end: 360.0, + radius, + tag, + }, + sketch_group, + args.clone(), + ) + .await?; + + // Call close. + crate::std::sketch::inner_close(sketch_group, None, args).await } diff --git a/src/wasm-lib/kcl/src/std/sketch.rs b/src/wasm-lib/kcl/src/std/sketch.rs index 757b129b0..24ff6e5d5 100644 --- a/src/wasm-lib/kcl/src/std/sketch.rs +++ b/src/wasm-lib/kcl/src/std/sketch.rs @@ -1220,7 +1220,7 @@ pub async fn start_profile_at(args: Args) -> Result { #[stdlib { name = "startProfileAt", }] -async fn inner_start_profile_at( +pub(crate) async fn inner_start_profile_at( data: LineData, sketch_surface: SketchSurface, args: Args, @@ -1306,7 +1306,7 @@ pub async fn close(args: Args) -> Result { #[stdlib { name = "close", }] -async fn inner_close( +pub(crate) async fn inner_close( sketch_group: Box, tag: Option, args: Args, @@ -1402,7 +1402,11 @@ pub async fn arc(args: Args) -> Result { #[stdlib { name = "arc", }] -async fn inner_arc(data: ArcData, sketch_group: Box, args: Args) -> Result, KclError> { +pub(crate) async fn inner_arc( + data: ArcData, + sketch_group: Box, + args: Args, +) -> Result, KclError> { let from: Point2d = sketch_group.get_coords_from_paths()?; let (center, angle_start, angle_end, radius, end) = match &data { @@ -1790,8 +1794,8 @@ pub async fn hole(args: Args) -> Result { /// |> line([10, 0], %) /// |> line([0, -10], %) /// |> close(%) -/// |> hole(circle([2, 2], .5, startSketchOn('XY')), %) -/// |> hole(circle([2, 8], .5, startSketchOn('XY')), %) +/// |> hole(circle([2, 2], .5, %), %) +/// |> hole(circle([2, 8], .5, %), %) /// |> extrude(2, %) /// ``` #[stdlib { diff --git a/src/wasm-lib/tests/executor/main.rs b/src/wasm-lib/tests/executor/main.rs index 1fb6bda8d..026c67039 100644 --- a/src/wasm-lib/tests/executor/main.rs +++ b/src/wasm-lib/tests/executor/main.rs @@ -731,8 +731,8 @@ async fn serial_test_holes() { |> line([10, 0], %) |> line([0, -10], %) |> close(%) - |> hole(circle([2, 2], .5, startSketchOn('XY')), %) - |> hole(circle([2, 8], .5, startSketchOn('XY')), %) + |> hole(circle([2, 2], .5, %), %) + |> hole(circle([2, 8], .5, %), %) |> extrude(2, %) "#; @@ -787,10 +787,10 @@ const holeRadius = 1 const holeIndex = 6 const part = roundedRectangle([0, 0], 20, 20, 4) - |> hole(circle([-holeIndex, holeIndex], holeRadius, startSketchOn('XY')), %) - |> hole(circle([holeIndex, holeIndex], holeRadius, startSketchOn('XY')), %) - |> hole(circle([-holeIndex, -holeIndex], holeRadius, startSketchOn('XY')), %) - |> hole(circle([holeIndex, -holeIndex], holeRadius, startSketchOn('XY')), %) + |> hole(circle([-holeIndex, holeIndex], holeRadius, %), %) + |> hole(circle([holeIndex, holeIndex], holeRadius, %), %) + |> hole(circle([-holeIndex, -holeIndex], holeRadius, %), %) + |> hole(circle([holeIndex, -holeIndex], holeRadius, %), %) |> extrude(2, %) "#; @@ -802,7 +802,7 @@ const part = roundedRectangle([0, 0], 20, 20, 4) #[tokio::test(flavor = "multi_thread")] async fn serial_test_top_level_expression() { - let code = r#"circle([0,0], 22, startSketchOn('XY')) |> extrude(14, %)"#; + let code = r#"startSketchOn('XY') |> circle([0,0], 22, %) |> extrude(14, %)"#; let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm) .await @@ -1252,7 +1252,7 @@ async fn serial_test_stdlib_kcl_error_right_code_path() { |> line([0, -10], %) |> close(%) |> hole(circle([2, 2], .5), %) - |> hole(circle([2, 8], .5, startSketchOn('XY')), %) + |> hole(circle([2, 8], .5, %), %) |> extrude(2, %) "#; @@ -1260,7 +1260,7 @@ async fn serial_test_stdlib_kcl_error_right_code_path() { assert!(result.is_err()); assert_eq!( result.err().unwrap().to_string(), - r#"semantic: KclErrorDetails { source_ranges: [SourceRange([157, 175])], message: "this function expected 3 arguments, got 2" }"# + r#"type: KclErrorDetails { source_ranges: [SourceRange([157, 175])], message: "Expected a SketchGroup or SketchSurface as the third argument, found `[UserVal(UserVal { value: Array [Number(2), Number(2)], meta: [Metadata { source_range: SourceRange([164, 170]) }] }), UserVal(UserVal { value: Number(0.5), meta: [Metadata { source_range: SourceRange([172, 174]) }] })]`" }"# ); } @@ -1367,6 +1367,6 @@ const part = rectShape([0, 0], 20, 20) assert!(result.is_err()); assert_eq!( result.err().unwrap().to_string(), - r#"semantic: KclErrorDetails { source_ranges: [SourceRange([987, 1036])], message: "MemberExpression array is not an array: UserVal(UserVal { value: String(\"XY\"), meta: [Metadata { source_range: SourceRange([994, 998]) }] })" }"# + r#"type: KclErrorDetails { source_ranges: [SourceRange([987, 1036])], message: "Expected a [number, number] as the first argument, found `[UserVal(UserVal { value: String(\"XY\"), meta: [Metadata { source_range: SourceRange([994, 998]) }] }), UserVal(UserVal { value: Array [Number(-6.0), Number(6)], meta: [Metadata { source_range: SourceRange([1000, 1023]) }] }), UserVal(UserVal { value: Number(1), meta: [Metadata { source_range: SourceRange([856, 857]) }] })]`" }"# ); }