Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
07eaf93e78 | |||
6a5ca3088a | |||
6501072d80 | |||
726fd02bad | |||
d0f9ae475f |
@ -31,7 +31,6 @@ layout: manual
|
|||||||
* [`fillet`](kcl/fillet)
|
* [`fillet`](kcl/fillet)
|
||||||
* [`floor`](kcl/floor)
|
* [`floor`](kcl/floor)
|
||||||
* [`getEdge`](kcl/getEdge)
|
* [`getEdge`](kcl/getEdge)
|
||||||
* [`getExtrudeWallTransform`](kcl/getExtrudeWallTransform)
|
|
||||||
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
||||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||||
|
@ -28338,839 +28338,6 @@
|
|||||||
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %, 'revolveAxis')\n |> close(%)\n |> extrude(10, %)\n\nconst sketch001 = startSketchOn('XY')\n |> startProfileAt([0, -10], %)\n |> line([0, -10], %)\n |> line([2, 0], %)\n |> line([0, 10], %)\n |> close(%)\n |> revolve({\n axis: getEdge('revolveAxis', box),\n angle: 90\n }, %)"
|
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %, 'revolveAxis')\n |> close(%)\n |> extrude(10, %)\n\nconst sketch001 = startSketchOn('XY')\n |> startProfileAt([0, -10], %)\n |> line([0, -10], %)\n |> line([2, 0], %)\n |> line([0, 10], %)\n |> close(%)\n |> revolve({\n axis: getEdge('revolveAxis', box),\n angle: 90\n }, %)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "getExtrudeWallTransform",
|
|
||||||
"summary": "Returns the extrude wall transform.",
|
|
||||||
"description": "",
|
|
||||||
"tags": [],
|
|
||||||
"args": [
|
|
||||||
{
|
|
||||||
"name": "surface_name",
|
|
||||||
"type": "string",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "extrude_group",
|
|
||||||
"type": "ExtrudeGroup",
|
|
||||||
"schema": {
|
|
||||||
"description": "An extrude group is a collection of extrude surfaces.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"__meta",
|
|
||||||
"height",
|
|
||||||
"id",
|
|
||||||
"position",
|
|
||||||
"rotation",
|
|
||||||
"sketchGroupValues",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
"sketchGroupValues": {
|
|
||||||
"description": "The sketch group paths.",
|
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "An extruded arc.",
|
|
||||||
"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": [
|
|
||||||
"extrudeArc"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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": "ExtrudeTransform",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"__meta",
|
|
||||||
"position",
|
|
||||||
"rotation"
|
|
||||||
],
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 3,
|
|
||||||
"minItems": 3
|
|
||||||
},
|
|
||||||
"rotation": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 4,
|
|
||||||
"minItems": 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"unpublished": false,
|
|
||||||
"deprecated": false,
|
|
||||||
"examples": [
|
|
||||||
"const box = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> line([10, 0], %)\n |> line([0, -10], %, \"surface\")\n |> close(%)\n |> extrude(5, %)\n\nconst transform = getExtrudeWallTransform('surface', box)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "getNextAdjacentEdge",
|
"name": "getNextAdjacentEdge",
|
||||||
"summary": "Get the next adjacent edge to the edge given.",
|
"summary": "Get the next adjacent edge to the edge given.",
|
||||||
|
@ -131,6 +131,7 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
// selected two lines therefore there should be two cursors
|
// selected two lines therefore there should be two cursors
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Constrain' }).click()
|
||||||
await page.getByRole('button', { name: 'Equal Length' }).click()
|
await page.getByRole('button', { name: 'Equal Length' }).click()
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
@ -264,6 +265,88 @@ test('Can moving camera', async ({ page, context }) => {
|
|||||||
}, [1, -94, -94])
|
}, [1, -94, -94])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('if you click the format button it formats your code', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.click('.cm-content')
|
||||||
|
await page.keyboard.type(`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
await page.click('#code-pane button:first-child')
|
||||||
|
await page.click('button:has-text("Format code")')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('if you use the format keyboard binding it formats your code', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
||||||
|
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
||||||
|
// but that doesn't seem to make it to the console for macos/safari :(
|
||||||
|
if (message.text().includes('start kcl lsp')) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await lspStartPromise
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// focus the editor
|
||||||
|
await page.click('.cm-line')
|
||||||
|
|
||||||
|
// Hit alt+shift+f to format the code
|
||||||
|
await page.keyboard.press('Alt+Shift+KeyF')
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-content'))
|
||||||
|
.toHaveText(`const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
})
|
||||||
|
|
||||||
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
@ -529,10 +612,6 @@ test.describe('Can create sketches on all planes and their back sides', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Auto complete works', async ({ page }) => {
|
test('Auto complete works', async ({ page }) => {
|
||||||
test.skip(
|
|
||||||
true,
|
|
||||||
'CORS issue stopping the kcl lsp from working, enable again later'
|
|
||||||
)
|
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
@ -862,11 +941,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
||||||
await topHorzSegmentClick()
|
await topHorzSegmentClick()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
|
const constrainButton = page.getByRole('button', { name: 'Constrain' })
|
||||||
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
const absYButton = page.getByRole('button', { name: 'ABS Y' })
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
@ -876,12 +958,14 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
// same selection but click the axis first
|
// same selection but click the axis first
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await topHorzSegmentClick()
|
await topHorzSegmentClick()
|
||||||
|
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
@ -890,10 +974,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
|||||||
// check the same selection again by putting cursor in code first then selecting axis
|
// check the same selection again by putting cursor in code first then selecting axis
|
||||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absYButton).toBeDisabled()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await xAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
|
await constrainButton.click()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absYButton).not.toBeDisabled()
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
@ -1827,6 +1913,7 @@ test('Can code mod a line length', async ({ page }) => {
|
|||||||
const startXPx = 500
|
const startXPx = 500
|
||||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||||
await page.mouse.click(615, 102)
|
await page.mouse.click(615, 102)
|
||||||
|
await page.getByRole('button', { name: 'Constrain', exact: true }).click()
|
||||||
await page.getByRole('button', { name: 'length', exact: true }).click()
|
await page.getByRole('button', { name: 'length', exact: true }).click()
|
||||||
await page.getByText('Add constraining value').click()
|
await page.getByText('Add constraining value').click()
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 45 KiB |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.21.0",
|
"version": "0.21.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.16.0",
|
"@codemirror/autocomplete": "^6.16.0",
|
||||||
|
@ -74,5 +74,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"version": "0.21.0"
|
"version": "0.21.1"
|
||||||
}
|
}
|
||||||
|
118
src/Toolbar.tsx
@ -11,17 +11,18 @@ import {
|
|||||||
useNetworkStatus,
|
useNetworkStatus,
|
||||||
} from 'components/NetworkHealthIndicator'
|
} from 'components/NetworkHealthIndicator'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
|
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
const { state, send, context } = useModelingContext()
|
const { state, send, context } = useModelingContext()
|
||||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||||
const iconClassName =
|
const iconClassName =
|
||||||
'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-chalkboard-10 group-pressed:!text-chalkboard-10'
|
'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-primary dark:group-enabled:group-hover:!text-inherit group-pressed:!text-chalkboard-10 group-ui-open:!text-chalkboard-10 dark:group-ui-open:!text-chalkboard-10'
|
||||||
const bgClassName =
|
const bgClassName =
|
||||||
'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary group-pressed:bg-primary'
|
'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary/10 dark:group-enabled:group-hover:bg-primary group-pressed:bg-primary group-ui-open:bg-primary'
|
||||||
const buttonClassName =
|
const buttonClassName =
|
||||||
'bg-chalkboard-10 dark:bg-chalkboard-100 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100'
|
'bg-chalkboard-10 dark:bg-chalkboard-100 enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!border-primary ui-open:!border-primary'
|
||||||
const pathId = useMemo(() => {
|
const pathId = useMemo(() => {
|
||||||
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
|
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
|
||||||
return false
|
return false
|
||||||
@ -57,10 +58,7 @@ export const Toolbar = () => {
|
|||||||
{...props}
|
{...props}
|
||||||
ref={toolbarButtonsRef}
|
ref={toolbarButtonsRef}
|
||||||
onWheel={handleToolbarButtonsWheelEvent}
|
onWheel={handleToolbarButtonsWheelEvent}
|
||||||
className={
|
className={'m-0 py-1 rounded-l-sm flex gap-2 items-center ' + className}
|
||||||
'm-0 py-1 rounded-l-sm flex gap-2 items-center overflow-x-auto ' +
|
|
||||||
className
|
|
||||||
}
|
|
||||||
style={{ scrollbarWidth: 'thin' }}
|
style={{ scrollbarWidth: 'thin' }}
|
||||||
>
|
>
|
||||||
{state.nextEvents.includes('Enter sketch') && (
|
{state.nextEvents.includes('Enter sketch') && (
|
||||||
@ -71,7 +69,7 @@ export const Toolbar = () => {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
|
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
|
||||||
}
|
}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'sketch',
|
icon: 'sketch',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -88,7 +86,7 @@ export const Toolbar = () => {
|
|||||||
className={buttonClassName}
|
className={buttonClassName}
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => send({ type: 'Enter sketch' })}
|
onClick={() => send({ type: 'Enter sketch' })}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'sketch',
|
icon: 'sketch',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -105,7 +103,7 @@ export const Toolbar = () => {
|
|||||||
className={buttonClassName}
|
className={buttonClassName}
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => send({ type: 'Cancel' })}
|
onClick={() => send({ type: 'Cancel' })}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'arrowLeft',
|
icon: 'arrowLeft',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -128,7 +126,7 @@ export const Toolbar = () => {
|
|||||||
: send('Equip Line tool')
|
: send('Equip Line tool')
|
||||||
}
|
}
|
||||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'line',
|
icon: 'line',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -148,7 +146,7 @@ export const Toolbar = () => {
|
|||||||
: send('Equip tangential arc to')
|
: send('Equip tangential arc to')
|
||||||
}
|
}
|
||||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'arc',
|
icon: 'arc',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -172,7 +170,7 @@ export const Toolbar = () => {
|
|||||||
: send('Equip rectangle tool')
|
: send('Equip rectangle tool')
|
||||||
}
|
}
|
||||||
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'rectangle',
|
icon: 'rectangle',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -194,52 +192,54 @@ export const Toolbar = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{state.matches('Sketch.SketchIdle') &&
|
{state.matches('Sketch.SketchIdle') &&
|
||||||
state.nextEvents
|
state.nextEvents.filter(
|
||||||
.filter(
|
(eventName) =>
|
||||||
(eventName) =>
|
eventName.includes('Make segment') ||
|
||||||
eventName.includes('Make segment') ||
|
eventName.includes('Constrain')
|
||||||
eventName.includes('Constrain')
|
).length > 0 && (
|
||||||
)
|
<ActionButtonDropdown
|
||||||
.sort((a, b) => {
|
splitMenuItems={state.nextEvents
|
||||||
const aisEnabled = state.nextEvents
|
.filter(
|
||||||
.filter((event) => state.can(event as any))
|
(eventName) =>
|
||||||
.includes(a)
|
eventName.includes('Make segment') ||
|
||||||
const bIsEnabled = state.nextEvents
|
eventName.includes('Constrain')
|
||||||
.filter((event) => state.can(event as any))
|
)
|
||||||
.includes(b)
|
.sort((a, b) => {
|
||||||
if (aisEnabled && !bIsEnabled) {
|
const aisEnabled = state.nextEvents
|
||||||
return -1
|
.filter((event) => state.can(event as any))
|
||||||
}
|
.includes(a)
|
||||||
if (!aisEnabled && bIsEnabled) {
|
const bIsEnabled = state.nextEvents
|
||||||
return 1
|
.filter((event) => state.can(event as any))
|
||||||
}
|
.includes(b)
|
||||||
return 0
|
if (aisEnabled && !bIsEnabled) {
|
||||||
})
|
return -1
|
||||||
.map((eventName) => (
|
}
|
||||||
<li className="contents" key={eventName}>
|
if (!aisEnabled && bIsEnabled) {
|
||||||
<ActionButton
|
return 1
|
||||||
className={buttonClassName}
|
}
|
||||||
Element="button"
|
return 0
|
||||||
key={eventName}
|
})
|
||||||
onClick={() => send(eventName)}
|
.map((eventName) => ({
|
||||||
disabled={
|
label: eventName
|
||||||
|
.replace('Make segment ', '')
|
||||||
|
.replace('Constrain ', ''),
|
||||||
|
onClick: () => send(eventName),
|
||||||
|
disabled:
|
||||||
!state.nextEvents
|
!state.nextEvents
|
||||||
.filter((event) => state.can(event as any))
|
.filter((event) => state.can(event as any))
|
||||||
.includes(eventName) || disableAllButtons
|
.includes(eventName) || disableAllButtons,
|
||||||
}
|
}))}
|
||||||
title={eventName}
|
className={buttonClassName}
|
||||||
icon={{
|
Element="button"
|
||||||
icon: 'line',
|
iconStart={{
|
||||||
iconClassName,
|
icon: 'dimension',
|
||||||
bgClassName,
|
iconClassName,
|
||||||
}}
|
bgClassName,
|
||||||
>
|
}}
|
||||||
{eventName
|
>
|
||||||
.replace('Make segment ', '')
|
Constrain
|
||||||
.replace('Constrain ', '')}
|
</ActionButtonDropdown>
|
||||||
</ActionButton>
|
)}
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
{state.matches('idle') && (
|
{state.matches('idle') && (
|
||||||
<li className="contents">
|
<li className="contents">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
@ -257,7 +257,7 @@ export const Toolbar = () => {
|
|||||||
? 'extrude'
|
? 'extrude'
|
||||||
: 'sketches need to be closed, or not already extruded'
|
: 'sketches need to be closed, or not already extruded'
|
||||||
}
|
}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'extrude',
|
icon: 'extrude',
|
||||||
iconClassName,
|
iconClassName,
|
||||||
bgClassName,
|
bgClassName,
|
||||||
@ -272,7 +272,7 @@ export const Toolbar = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<menu className="max-w-full overflow-hidden whitespace-nowrap rounded px-1.5 py-0.5 backdrop-blur-sm bg-chalkboard-10/80 dark:bg-chalkboard-110/70 relative">
|
<menu className="max-w-full whitespace-nowrap rounded px-1.5 py-0.5 backdrop-blur-sm bg-chalkboard-10/80 dark:bg-chalkboard-110/70 relative">
|
||||||
<ToolbarButtons />
|
<ToolbarButtons />
|
||||||
</menu>
|
</menu>
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
||||||
import React from 'react'
|
import React, { ForwardedRef, forwardRef } from 'react'
|
||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import type { LinkProps } from 'react-router-dom'
|
import type { LinkProps } from 'react-router-dom'
|
||||||
|
|
||||||
interface BaseActionButtonProps {
|
interface BaseActionButtonProps {
|
||||||
icon?: ActionIconProps
|
iconStart?: ActionIconProps
|
||||||
|
iconEnd?: ActionIconProps
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,15 +33,15 @@ type ActionButtonAsElement = BaseActionButtonProps &
|
|||||||
Element: React.ComponentType<React.HTMLAttributes<HTMLButtonElement>>
|
Element: React.ComponentType<React.HTMLAttributes<HTMLButtonElement>>
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionButtonProps =
|
export type ActionButtonProps =
|
||||||
| ActionButtonAsButton
|
| ActionButtonAsButton
|
||||||
| ActionButtonAsLink
|
| ActionButtonAsLink
|
||||||
| ActionButtonAsExternal
|
| ActionButtonAsExternal
|
||||||
| ActionButtonAsElement
|
| ActionButtonAsElement
|
||||||
|
|
||||||
export const ActionButton = (props: ActionButtonProps) => {
|
export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
|
||||||
const classNames = `action-button m-0 group mono text-sm flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 p-[3px] text-chalkboard-100 dark:text-chalkboard-10 ${
|
const classNames = `action-button p-0 m-0 group mono text-xs leading-none flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 enabled:dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 text-chalkboard-100 dark:text-chalkboard-10 ${
|
||||||
props.icon ? 'pr-2' : 'px-2'
|
props.iconStart ? (props.iconEnd ? 'px-0' : 'pr-2') : 'px-2'
|
||||||
} ${props.className ? props.className : ''}`
|
} ${props.className ? props.className : ''}`
|
||||||
|
|
||||||
switch (props.Element) {
|
switch (props.Element) {
|
||||||
@ -48,11 +49,23 @@ export const ActionButton = (props: ActionButtonProps) => {
|
|||||||
// Note we have to destructure 'className' and 'Element' out of props
|
// Note we have to destructure 'className' and 'Element' out of props
|
||||||
// because we don't want to pass them to the button element;
|
// because we don't want to pass them to the button element;
|
||||||
// the same is true for the other cases below.
|
// the same is true for the other cases below.
|
||||||
const { Element, icon, children, className: _className, ...rest } = props
|
const {
|
||||||
|
Element,
|
||||||
|
iconStart,
|
||||||
|
iconEnd,
|
||||||
|
children,
|
||||||
|
className: _className,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
return (
|
return (
|
||||||
<button className={classNames} {...rest}>
|
<button
|
||||||
{props.icon && <ActionIcon {...icon} />}
|
ref={ref as ForwardedRef<HTMLButtonElement>}
|
||||||
|
className={classNames}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{iconStart && <ActionIcon {...iconStart} />}
|
||||||
{children}
|
{children}
|
||||||
|
{iconEnd && <ActionIcon {...iconEnd} />}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -60,15 +73,22 @@ export const ActionButton = (props: ActionButtonProps) => {
|
|||||||
const {
|
const {
|
||||||
Element,
|
Element,
|
||||||
to,
|
to,
|
||||||
icon,
|
iconStart,
|
||||||
|
iconEnd,
|
||||||
children,
|
children,
|
||||||
className: _className,
|
className: _className,
|
||||||
...rest
|
...rest
|
||||||
} = props
|
} = props
|
||||||
return (
|
return (
|
||||||
<Link to={to || paths.INDEX} className={classNames} {...rest}>
|
<Link
|
||||||
{icon && <ActionIcon {...icon} />}
|
ref={ref as ForwardedRef<HTMLAnchorElement>}
|
||||||
|
to={to || paths.INDEX}
|
||||||
|
className={classNames}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{iconStart && <ActionIcon {...iconStart} />}
|
||||||
{children}
|
{children}
|
||||||
|
{iconEnd && <ActionIcon {...iconEnd} />}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -76,33 +96,42 @@ export const ActionButton = (props: ActionButtonProps) => {
|
|||||||
const {
|
const {
|
||||||
Element,
|
Element,
|
||||||
to,
|
to,
|
||||||
icon,
|
iconStart,
|
||||||
|
iconEnd,
|
||||||
children,
|
children,
|
||||||
className: _className,
|
className: _className,
|
||||||
...rest
|
...rest
|
||||||
} = props
|
} = props
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
ref={ref as ForwardedRef<HTMLAnchorElement>}
|
||||||
to={to || paths.INDEX}
|
to={to || paths.INDEX}
|
||||||
className={classNames}
|
className={classNames}
|
||||||
{...rest}
|
{...rest}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{icon && <ActionIcon {...icon} />}
|
{iconStart && <ActionIcon {...iconStart} />}
|
||||||
{children}
|
{children}
|
||||||
|
{iconEnd && <ActionIcon {...iconEnd} />}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
const { Element, icon, children, className: _className, ...rest } = props
|
const {
|
||||||
if (!Element) throw new Error('Element is required')
|
Element,
|
||||||
|
iconStart,
|
||||||
|
children,
|
||||||
|
className: _className,
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Element className={classNames} {...rest}>
|
<Element className={classNames} {...rest}>
|
||||||
{props.icon && <ActionIcon {...props.icon} />}
|
{props.iconStart && <ActionIcon {...props.iconStart} />}
|
||||||
{children}
|
{children}
|
||||||
|
{props.iconEnd && <ActionIcon {...props.iconEnd} />}
|
||||||
</Element>
|
</Element>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
55
src/components/ActionButtonDropdown.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { Popover } from '@headlessui/react'
|
||||||
|
import { ActionButton, ActionButtonProps } from './ActionButton'
|
||||||
|
|
||||||
|
type ActionButtonSplitProps = Omit<ActionButtonProps, 'iconEnd'> & {
|
||||||
|
splitMenuItems: {
|
||||||
|
label: string
|
||||||
|
shortcut?: string
|
||||||
|
onClick: () => void
|
||||||
|
disabled?: boolean
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ActionButtonDropdown({
|
||||||
|
splitMenuItems,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: ActionButtonSplitProps) {
|
||||||
|
return (
|
||||||
|
<Popover className="relative">
|
||||||
|
<Popover.Button
|
||||||
|
as={ActionButton}
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
Element="button"
|
||||||
|
iconEnd={{
|
||||||
|
icon: 'caretDown',
|
||||||
|
className: 'ui-open:rotate-180',
|
||||||
|
bgClassName:
|
||||||
|
'bg-chalkboard-20 dark:bg-chalkboard-80 ui-open:bg-primary ui-open:text-chalkboard-10',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Popover.Panel
|
||||||
|
as="ul"
|
||||||
|
className="absolute z-20 left-1/2 -translate-x-1/2 top-full mt-1 w-fit max-h-[80vh] overflow-y-auto py-2 flex flex-col gap-1 align-stretch text-inherit dark:text-chalkboard-10 bg-chalkboard-10 dark:bg-chalkboard-100 rounded shadow-lg border border-solid border-chalkboard-30 dark:border-chalkboard-80 text-sm m-0 p-0"
|
||||||
|
>
|
||||||
|
{splitMenuItems.map((item) => (
|
||||||
|
<li className="contents" key={item.label}>
|
||||||
|
<button
|
||||||
|
onClick={item.onClick}
|
||||||
|
className="block px-3 py-1 hover:bg-primary/10 dark:hover:bg-chalkboard-80 border-0 m-0 text-sm w-full rounded-none text-left disabled:!bg-transparent dark:disabled:text-chalkboard-60"
|
||||||
|
disabled={item.disabled}
|
||||||
|
>
|
||||||
|
<span className="capitalize">{item.label}</span>
|
||||||
|
{item.shortcut && (
|
||||||
|
<kbd className="bg-primary/10 dark:bg-chalkboard-80 dark:group-hover:bg-primary font-mono rounded-sm dark:text-inherit inline-block px-1 border-primary dark:border-chalkboard-90">
|
||||||
|
{item.shortcut}
|
||||||
|
</kbd>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</Popover.Panel>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
@ -29,10 +29,8 @@ export const ActionIcon = ({
|
|||||||
size = 'md',
|
size = 'md',
|
||||||
children,
|
children,
|
||||||
}: ActionIconProps) => {
|
}: ActionIconProps) => {
|
||||||
// By default, we reverse the icon color and background color in dark mode
|
const computedIconClassName = `h-auto text-inherit dark:text-current !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}`
|
||||||
const computedIconClassName = `h-auto text-primary dark:text-current !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}`
|
const computedBgClassName = `bg-chalkboard-20 dark:bg-chalkboard-80 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
|
||||||
|
|
||||||
const computedBgClassName = `bg-primary/10 dark:bg-chalkboard-90 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -175,7 +175,7 @@ function ReviewingButton() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
form="review-form"
|
form="review-form"
|
||||||
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'checkmark',
|
icon: 'checkmark',
|
||||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||||
iconClassName: '!text-chalkboard-10',
|
iconClassName: '!text-chalkboard-10',
|
||||||
@ -193,7 +193,7 @@ function GatheringArgsButton() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
form="arg-form"
|
form="arg-form"
|
||||||
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'arrowRight',
|
icon: 'arrowRight',
|
||||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||||
iconClassName: '!text-chalkboard-10',
|
iconClassName: '!text-chalkboard-10',
|
||||||
|
@ -71,6 +71,16 @@ const CustomIconMap = {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
|
caretDown: (
|
||||||
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M10 11.2929L6.35346 7.64642L5.64636 8.35354L9.64648 12.3536L10.3536 12.3536L14.3535 8.35353L13.6464 7.64643L10 11.2929Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
clipboardCheckmark: (
|
clipboardCheckmark: (
|
||||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
@ -101,6 +111,16 @@ const CustomIconMap = {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
|
dimension: (
|
||||||
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M15.6455 3.6455L14 2V5.291L5.291 14H2L6 18V14.7052L14.7052 6H18L16.3526 4.35261L16.3546 4.35065L15.6475 3.64354L15.6455 3.6455Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
equal: (
|
equal: (
|
||||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
|
@ -25,7 +25,7 @@ const DownloadAppBanner = () => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => setIsBannerDismissed(true)}
|
onClick={() => setIsBannerDismissed(true)}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'close',
|
icon: 'close',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
bgClassName:
|
bgClassName:
|
||||||
|
@ -29,7 +29,7 @@ export const ErrorPage = () => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="link"
|
Element="link"
|
||||||
to={'/'}
|
to={'/'}
|
||||||
icon={{ icon: faHome }}
|
iconStart={{ icon: faHome }}
|
||||||
data-testid="unexpected-error-home"
|
data-testid="unexpected-error-home"
|
||||||
>
|
>
|
||||||
Go Home
|
Go Home
|
||||||
@ -37,14 +37,14 @@ export const ErrorPage = () => {
|
|||||||
)}
|
)}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{ icon: faRefresh }}
|
iconStart={{ icon: faRefresh }}
|
||||||
onClick={() => window.location.reload()}
|
onClick={() => window.location.reload()}
|
||||||
>
|
>
|
||||||
Reload
|
Reload
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{ icon: faTrash }}
|
iconStart={{ icon: faTrash }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.localStorage.clear()
|
window.localStorage.clear()
|
||||||
}}
|
}}
|
||||||
@ -53,7 +53,7 @@ export const ErrorPage = () => {
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="externalLink"
|
Element="externalLink"
|
||||||
icon={{ icon: faBug }}
|
iconStart={{ icon: faBug }}
|
||||||
to="https://github.com/KittyCAD/modeling-app/issues/new"
|
to="https://github.com/KittyCAD/modeling-app/issues/new"
|
||||||
>
|
>
|
||||||
Report Bug
|
Report Bug
|
||||||
|
@ -109,7 +109,7 @@ function DeleteConfirmationDialog({
|
|||||||
send({ type: 'Delete file', data: fileOrDir })
|
send({ type: 'Delete file', data: fileOrDir })
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
}}
|
}}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faTrashAlt,
|
icon: faTrashAlt,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
iconClassName:
|
iconClassName:
|
||||||
@ -340,7 +340,7 @@ export const FileTreeMenu = () => {
|
|||||||
<>
|
<>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'filePlus',
|
icon: 'filePlus',
|
||||||
iconClassName: '!text-current',
|
iconClassName: '!text-current',
|
||||||
bgClassName: 'bg-transparent',
|
bgClassName: 'bg-transparent',
|
||||||
@ -355,7 +355,7 @@ export const FileTreeMenu = () => {
|
|||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'folderPlus',
|
icon: 'folderPlus',
|
||||||
iconClassName: '!text-current',
|
iconClassName: '!text-current',
|
||||||
bgClassName: 'bg-transparent',
|
bgClassName: 'bg-transparent',
|
||||||
|
@ -85,7 +85,7 @@ function ProjectCard({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
type="submit"
|
type="submit"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faCheck,
|
icon: faCheck,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
@ -99,7 +99,7 @@ function ProjectCard({
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faX,
|
icon: faX,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
iconClassName: 'dark:!text-chalkboard-20',
|
iconClassName: 'dark:!text-chalkboard-20',
|
||||||
@ -141,7 +141,7 @@ function ProjectCard({
|
|||||||
<div className="absolute z-10 bottom-2 right-2 flex gap-1 items-center opacity-0 group-hover:opacity-100 group-focus-within:opacity-100">
|
<div className="absolute z-10 bottom-2 right-2 flex gap-1 items-center opacity-0 group-hover:opacity-100 group-focus-within:opacity-100">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faPenAlt,
|
icon: faPenAlt,
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
iconClassName: 'dark:!text-chalkboard-20',
|
iconClassName: 'dark:!text-chalkboard-20',
|
||||||
@ -161,7 +161,7 @@ function ProjectCard({
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faTrashAlt,
|
icon: faTrashAlt,
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
size: 'xs',
|
size: 'xs',
|
||||||
@ -207,7 +207,7 @@ function ProjectCard({
|
|||||||
await handleDeleteProject(project)
|
await handleDeleteProject(project)
|
||||||
setIsConfirmingDelete(false)
|
setIsConfirmingDelete(false)
|
||||||
}}
|
}}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faTrashAlt,
|
icon: faTrashAlt,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
|
@ -165,7 +165,7 @@ function ProjectMenuPopover({
|
|||||||
<div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
|
<div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{ icon: 'exportFile', className: 'p-1' }}
|
iconStart={{ icon: 'exportFile', className: 'p-1' }}
|
||||||
className="border-transparent dark:border-transparent"
|
className="border-transparent dark:border-transparent"
|
||||||
disabled={!findCommand(exportCommandInfo)}
|
disabled={!findCommand(exportCommandInfo)}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -185,7 +185,7 @@ function ProjectMenuPopover({
|
|||||||
// Clear the scene and end the session.
|
// Clear the scene and end the session.
|
||||||
engineCommandManager.endSession()
|
engineCommandManager.endSession()
|
||||||
}}
|
}}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'arrowLeft',
|
icon: 'arrowLeft',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
}}
|
}}
|
||||||
|
@ -78,7 +78,7 @@ export const SetVarNameModal = ({
|
|||||||
Element="button"
|
Element="button"
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!isNewVariableNameUnique}
|
disabled={!isNewVariableNameUnique}
|
||||||
icon={{ icon: faPlus }}
|
iconStart={{ icon: faPlus }}
|
||||||
>
|
>
|
||||||
Add variable
|
Add variable
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
@ -59,7 +59,7 @@ export const UpdaterModal = ({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => onResolve({ wantUpdate: false })}
|
onClick={() => onResolve({ wantUpdate: false })}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'close',
|
icon: 'close',
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
||||||
@ -72,7 +72,10 @@ export const UpdaterModal = ({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => onResolve({ wantUpdate: true })}
|
onClick={() => onResolve({ wantUpdate: true })}
|
||||||
icon={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
|
iconStart={{
|
||||||
|
icon: 'arrowRight',
|
||||||
|
bgClassName: 'dark:bg-chalkboard-80',
|
||||||
|
}}
|
||||||
className="dark:hover:bg-chalkboard-80/50"
|
className="dark:hover:bg-chalkboard-80/50"
|
||||||
data-testid="update-button-update"
|
data-testid="update-button-update"
|
||||||
>
|
>
|
||||||
|
@ -31,7 +31,7 @@ export const UpdaterRestartModal = ({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => onResolve({ wantRestart: false })}
|
onClick={() => onResolve({ wantRestart: false })}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'close',
|
icon: 'close',
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
||||||
@ -44,7 +44,10 @@ export const UpdaterRestartModal = ({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => onResolve({ wantRestart: true })}
|
onClick={() => onResolve({ wantRestart: true })}
|
||||||
icon={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
|
iconStart={{
|
||||||
|
icon: 'arrowRight',
|
||||||
|
bgClassName: 'dark:bg-chalkboard-80',
|
||||||
|
}}
|
||||||
className="dark:hover:bg-chalkboard-80/50"
|
className="dark:hover:bg-chalkboard-80/50"
|
||||||
data-testid="update-restrart-button-update"
|
data-testid="update-restrart-button-update"
|
||||||
>
|
>
|
||||||
|
@ -55,7 +55,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
) : (
|
) : (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element={Popover.Button}
|
Element={Popover.Button}
|
||||||
icon={{ icon: 'menu' }}
|
iconStart={{ icon: 'menu' }}
|
||||||
className="border-transparent !px-0"
|
className="border-transparent !px-0"
|
||||||
data-testid="user-sidebar-toggle"
|
data-testid="user-sidebar-toggle"
|
||||||
>
|
>
|
||||||
@ -120,7 +120,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<div className="p-4 flex flex-col gap-2">
|
<div className="p-4 flex flex-col gap-2">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
icon={{ icon: 'settings' }}
|
iconStart={{ icon: 'settings' }}
|
||||||
className="border-transparent dark:border-transparent hover:bg-transparent"
|
className="border-transparent dark:border-transparent hover:bg-transparent"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// since /settings is a nested route the sidebar doesn't close
|
// since /settings is a nested route the sidebar doesn't close
|
||||||
@ -138,7 +138,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="externalLink"
|
Element="externalLink"
|
||||||
to="https://github.com/KittyCAD/modeling-app/discussions"
|
to="https://github.com/KittyCAD/modeling-app/discussions"
|
||||||
icon={{ icon: faGithub, className: 'p-1', size: 'sm' }}
|
iconStart={{ icon: faGithub, className: 'p-1', size: 'sm' }}
|
||||||
className="border-transparent dark:border-transparent"
|
className="border-transparent dark:border-transparent"
|
||||||
>
|
>
|
||||||
Request a feature
|
Request a feature
|
||||||
@ -146,7 +146,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="externalLink"
|
Element="externalLink"
|
||||||
to="https://github.com/KittyCAD/modeling-app/issues/new/choose"
|
to="https://github.com/KittyCAD/modeling-app/issues/new/choose"
|
||||||
icon={{ icon: faBug, className: 'p-1', size: 'sm' }}
|
iconStart={{ icon: faBug, className: 'p-1', size: 'sm' }}
|
||||||
className="border-transparent dark:border-transparent"
|
className="border-transparent dark:border-transparent"
|
||||||
>
|
>
|
||||||
Report a bug
|
Report a bug
|
||||||
@ -154,7 +154,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => send('Log out')}
|
onClick={() => send('Log out')}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faSignOutAlt,
|
icon: faSignOutAlt,
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
bgClassName: '!bg-transparent',
|
bgClassName: '!bg-transparent',
|
||||||
|
@ -24,7 +24,7 @@ export function WasmErrBanner() {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => setBannerDismissed(true)}
|
onClick={() => setBannerDismissed(true)}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'close',
|
icon: 'close',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
bgClassName:
|
bgClassName:
|
||||||
|
@ -264,8 +264,12 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
this.sendChange({
|
this.client.textDocumentDidChange({
|
||||||
documentText: this.view.state.doc.toString(),
|
textDocument: {
|
||||||
|
uri: this.documentUri,
|
||||||
|
version: this.documentVersion++,
|
||||||
|
},
|
||||||
|
contentChanges: [{ text: this.view.state.doc.toString() }],
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await this.client.textDocumentFormatting({
|
const result = await this.client.textDocumentFormatting({
|
||||||
|
@ -79,6 +79,7 @@ export default class CodeManager {
|
|||||||
if (this._code !== code) {
|
if (this._code !== code) {
|
||||||
this.code = code
|
this.code = code
|
||||||
this.#updateState(code)
|
this.#updateState(code)
|
||||||
|
this.updateCodeEditor(code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ const Home = () => {
|
|||||||
: '')
|
: '')
|
||||||
}
|
}
|
||||||
onClick={() => setSearchParams(getNextSearchParams(sort, 'name'))}
|
onClick={() => setSearchParams(getNextSearchParams(sort, 'name'))}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: getSortIcon(sort, 'name'),
|
icon: getSortIcon(sort, 'name'),
|
||||||
className: 'p-1.5',
|
className: 'p-1.5',
|
||||||
iconClassName: !sort.includes('name')
|
iconClassName: !sort.includes('name')
|
||||||
@ -232,7 +232,7 @@ const Home = () => {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
setSearchParams(getNextSearchParams(sort, 'modified'))
|
setSearchParams(getNextSearchParams(sort, 'modified'))
|
||||||
}
|
}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: sort ? getSortIcon(sort, 'modified') : faArrowDown,
|
icon: sort ? getSortIcon(sort, 'modified') : faArrowDown,
|
||||||
className: 'p-1.5',
|
className: 'p-1.5',
|
||||||
iconClassName: !isSortByModified ? '!text-chalkboard-40' : '',
|
iconClassName: !isSortByModified ? '!text-chalkboard-40' : '',
|
||||||
@ -278,7 +278,7 @@ const Home = () => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => send('Create project')}
|
onClick={() => send('Create project')}
|
||||||
icon={{ icon: faPlus, iconClassName: 'p-1 w-4' }}
|
iconStart={{ icon: faPlus, iconClassName: 'p-1 w-4' }}
|
||||||
data-testid="home-new-file"
|
data-testid="home-new-file"
|
||||||
>
|
>
|
||||||
New project
|
New project
|
||||||
|
@ -51,7 +51,7 @@ export default function Units() {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={dismiss}
|
onClick={dismiss}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: faXmark,
|
icon: faXmark,
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
iconClassName:
|
iconClassName:
|
||||||
@ -64,7 +64,7 @@ export default function Units() {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={next}
|
onClick={next}
|
||||||
icon={{ icon: faArrowRight }}
|
iconStart={{ icon: faArrowRight }}
|
||||||
>
|
>
|
||||||
Next: Camera
|
Next: Camera
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
@ -144,7 +144,7 @@ export function OnboardingButtons({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={dismiss}
|
onClick={dismiss}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'close',
|
icon: 'close',
|
||||||
bgClassName: 'bg-destroy-80',
|
bgClassName: 'bg-destroy-80',
|
||||||
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
||||||
@ -161,7 +161,7 @@ export function OnboardingButtons({
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={next}
|
onClick={next}
|
||||||
icon={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
|
iconStart={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
|
||||||
className="dark:hover:bg-chalkboard-80/50"
|
className="dark:hover:bg-chalkboard-80/50"
|
||||||
data-testid="onboarding-next"
|
data-testid="onboarding-next"
|
||||||
>
|
>
|
||||||
|
@ -269,7 +269,7 @@ export const Settings = () => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={restartOnboarding}
|
onClick={restartOnboarding}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'refresh',
|
icon: 'refresh',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
@ -300,7 +300,7 @@ export const Settings = () => {
|
|||||||
)
|
)
|
||||||
showInFolder(paths[searchParamTab])
|
showInFolder(paths[searchParamTab])
|
||||||
}}
|
}}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'folder',
|
icon: 'folder',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
className: 'p-1',
|
className: 'p-1',
|
||||||
@ -319,7 +319,7 @@ export const Settings = () => {
|
|||||||
})
|
})
|
||||||
toast.success('Settings restored to default')
|
toast.success('Settings restored to default')
|
||||||
}}
|
}}
|
||||||
icon={{
|
iconStart={{
|
||||||
icon: 'refresh',
|
icon: 'refresh',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
className: 'p-1 text-chalkboard-10',
|
className: 'p-1 text-chalkboard-10',
|
||||||
|
@ -65,7 +65,7 @@ const SignIn = () => {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={signInTauri}
|
onClick={signInTauri}
|
||||||
icon={{ icon: 'arrowRight' }}
|
iconStart={{ icon: 'arrowRight' }}
|
||||||
className="w-fit mt-4"
|
className="w-fit mt-4"
|
||||||
data-testid="sign-in-button"
|
data-testid="sign-in-button"
|
||||||
>
|
>
|
||||||
@ -80,7 +80,7 @@ const SignIn = () => {
|
|||||||
typeof window !== 'undefined' &&
|
typeof window !== 'undefined' &&
|
||||||
window.location.href.replace('signin', '')
|
window.location.href.replace('signin', '')
|
||||||
)}`}
|
)}`}
|
||||||
icon={{ icon: 'arrowRight' }}
|
iconStart={{ icon: 'arrowRight' }}
|
||||||
className="w-fit mt-4"
|
className="w-fit mt-4"
|
||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
|
2
src/wasm-lib/Cargo.lock
generated
@ -1895,7 +1895,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.1.54"
|
version = "0.1.55"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx 0.5.1",
|
"approx 0.5.1",
|
||||||
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.1.54"
|
version = "0.1.55"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
@ -3297,6 +3297,144 @@ fn ghi = (x) => {
|
|||||||
assert_eq!(symbols.len(), 7);
|
assert_eq!(symbols.len(), 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recast_bug_extra_parens() {
|
||||||
|
let some_program_string = r#"// Ball Bearing
|
||||||
|
// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
|
||||||
|
|
||||||
|
// Define constants like ball diameter, inside diameter, overhange length, and thickness
|
||||||
|
const sphereDia = 0.5
|
||||||
|
const insideDia = 1
|
||||||
|
const thickness = 0.25
|
||||||
|
const overHangLength = .4
|
||||||
|
|
||||||
|
// Sketch and revolve the inside bearing piece
|
||||||
|
const insideRevolve = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([insideDia / 2, 0], %)
|
||||||
|
|> line([0, thickness + sphereDia / 2], %)
|
||||||
|
|> line([overHangLength, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([-overHangLength + thickness, 0], %)
|
||||||
|
|> line([0, -sphereDia], %)
|
||||||
|
|> line([overHangLength - thickness, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([-overHangLength, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'y' }, %)
|
||||||
|
|
||||||
|
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
|
||||||
|
const sphere = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([
|
||||||
|
0.05 + insideDia / 2 + thickness,
|
||||||
|
0 - 0.05
|
||||||
|
], %)
|
||||||
|
|> line([sphereDia - 0.1, 0], %)
|
||||||
|
|> arc({
|
||||||
|
angle_start: 0,
|
||||||
|
angle_end: -180,
|
||||||
|
radius: sphereDia / 2 - 0.05
|
||||||
|
}, %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'x' }, %)
|
||||||
|
|> patternCircular3d({
|
||||||
|
axis: [0, 0, 1],
|
||||||
|
center: [0, 0, 0],
|
||||||
|
repetitions: 10,
|
||||||
|
arcDegrees: 360,
|
||||||
|
rotateDuplicates: true
|
||||||
|
}, %)
|
||||||
|
|
||||||
|
// Sketch and revolve the outside bearing
|
||||||
|
const outsideRevolve = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([
|
||||||
|
insideDia / 2 + thickness + sphereDia,
|
||||||
|
0
|
||||||
|
], %)
|
||||||
|
|> line([0, sphereDia / 2], %)
|
||||||
|
|> line([-overHangLength + thickness, 0], %)
|
||||||
|
|> line([0, thickness], %)
|
||||||
|
|> line([overHangLength, 0], %)
|
||||||
|
|> line([0, -2 * thickness - sphereDia], %)
|
||||||
|
|> line([-overHangLength, 0], %)
|
||||||
|
|> line([0, thickness], %)
|
||||||
|
|> line([overHangLength - thickness, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'y' }, %)"#;
|
||||||
|
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast().unwrap();
|
||||||
|
|
||||||
|
println!("{:#?}", program);
|
||||||
|
|
||||||
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
recasted,
|
||||||
|
r#"// Ball Bearing
|
||||||
|
// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
|
||||||
|
|
||||||
|
|
||||||
|
// Define constants like ball diameter, inside diameter, overhange length, and thickness
|
||||||
|
const sphereDia = 0.5
|
||||||
|
const insideDia = 1
|
||||||
|
const thickness = 0.25
|
||||||
|
const overHangLength = .4
|
||||||
|
|
||||||
|
// Sketch and revolve the inside bearing piece
|
||||||
|
const insideRevolve = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([insideDia / 2, 0], %)
|
||||||
|
|> line([0, thickness + sphereDia / 2], %)
|
||||||
|
|> line([overHangLength, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([-overHangLength + thickness, 0], %)
|
||||||
|
|> line([0, -sphereDia], %)
|
||||||
|
|> line([overHangLength - thickness, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([-overHangLength, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'y' }, %)
|
||||||
|
|
||||||
|
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
|
||||||
|
const sphere = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([
|
||||||
|
0.05 + insideDia / 2 + thickness,
|
||||||
|
0 - 0.05
|
||||||
|
], %)
|
||||||
|
|> line([sphereDia - 0.1, 0], %)
|
||||||
|
|> arc({
|
||||||
|
angle_start: 0,
|
||||||
|
angle_end: -180,
|
||||||
|
radius: sphereDia / 2 - 0.05
|
||||||
|
}, %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'x' }, %)
|
||||||
|
|> patternCircular3d({
|
||||||
|
axis: [0, 0, 1],
|
||||||
|
center: [0, 0, 0],
|
||||||
|
repetitions: 10,
|
||||||
|
arcDegrees: 360,
|
||||||
|
rotateDuplicates: true
|
||||||
|
}, %)
|
||||||
|
|
||||||
|
// Sketch and revolve the outside bearing
|
||||||
|
const outsideRevolve = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([
|
||||||
|
insideDia / 2 + thickness + sphereDia,
|
||||||
|
0
|
||||||
|
], %)
|
||||||
|
|> line([0, sphereDia / 2], %)
|
||||||
|
|> line([-overHangLength + thickness, 0], %)
|
||||||
|
|> line([0, thickness], %)
|
||||||
|
|> line([overHangLength, 0], %)
|
||||||
|
|> line([0, -2 * thickness - sphereDia], %)
|
||||||
|
|> line([-overHangLength, 0], %)
|
||||||
|
|> line([0, thickness], %)
|
||||||
|
|> line([overHangLength - thickness, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'y' }, %)
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_empty_file() {
|
fn test_recast_empty_file() {
|
||||||
let some_program_string = r#""#;
|
let some_program_string = r#""#;
|
||||||
|
@ -144,8 +144,6 @@ pub enum MemoryItem {
|
|||||||
},
|
},
|
||||||
ImportedGeometry(ImportedGeometry),
|
ImportedGeometry(ImportedGeometry),
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
ExtrudeTransform(Box<ExtrudeTransform>),
|
|
||||||
#[ts(skip)]
|
|
||||||
Function {
|
Function {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
func: Option<MemoryFunction>,
|
func: Option<MemoryFunction>,
|
||||||
@ -300,16 +298,6 @@ pub struct UserVal {
|
|||||||
pub meta: Vec<Metadata>,
|
pub meta: Vec<Metadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
||||||
#[ts(export)]
|
|
||||||
#[serde(tag = "type", rename_all = "camelCase")]
|
|
||||||
pub struct ExtrudeTransform {
|
|
||||||
pub position: Position,
|
|
||||||
pub rotation: Rotation,
|
|
||||||
#[serde(rename = "__meta")]
|
|
||||||
pub meta: Vec<Metadata>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type MemoryFunction =
|
pub type MemoryFunction =
|
||||||
fn(
|
fn(
|
||||||
s: Vec<MemoryItem>,
|
s: Vec<MemoryItem>,
|
||||||
@ -348,7 +336,6 @@ impl From<MemoryItem> for Vec<SourceRange> {
|
|||||||
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range))
|
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range))
|
||||||
.collect(),
|
.collect(),
|
||||||
MemoryItem::ImportedGeometry(i) => i.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::ImportedGeometry(i) => i.meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
|
||||||
MemoryItem::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::Face(f) => f.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::Face(f) => f.meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
@ -313,6 +313,28 @@ impl Backend {
|
|||||||
let source_range: SourceRange = token.clone().into();
|
let source_range: SourceRange = token.clone().into();
|
||||||
let position = source_range.start_to_lsp_position(¶ms.text);
|
let position = source_range.start_to_lsp_position(¶ms.text);
|
||||||
|
|
||||||
|
// We need to check if we are on the last token of the line.
|
||||||
|
// If we are starting from the end of the last line just add 1 to the line.
|
||||||
|
// Check if we are on the last token of the line.
|
||||||
|
if let Some(line) = params.text.lines().nth(position.line as usize) {
|
||||||
|
if line.len() == position.character as usize {
|
||||||
|
// We are on the last token of the line.
|
||||||
|
// We need to add a new line.
|
||||||
|
let semantic_token = SemanticToken {
|
||||||
|
delta_line: position.line - last_position.line + 1,
|
||||||
|
delta_start: 0,
|
||||||
|
length: token.value.len() as u32,
|
||||||
|
token_type: token_type_index as u32,
|
||||||
|
token_modifiers_bitset: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
semantic_tokens.push(semantic_token);
|
||||||
|
|
||||||
|
last_position = Position::new(position.line + 1, 0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let semantic_token = SemanticToken {
|
let semantic_token = SemanticToken {
|
||||||
delta_line: position.line - last_position.line,
|
delta_line: position.line - last_position.line,
|
||||||
delta_start: if position.line != last_position.line {
|
delta_start: if position.line != last_position.line {
|
||||||
@ -1011,7 +1033,7 @@ impl LanguageServer for Backend {
|
|||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
let source_range = SourceRange([0, current_code.len() - 1]);
|
let source_range = SourceRange([0, current_code.len()]);
|
||||||
let range = source_range.to_lsp_range(current_code);
|
let range = source_range.to_lsp_range(current_code);
|
||||||
Ok(Some(vec![TextEdit {
|
Ok(Some(vec![TextEdit {
|
||||||
new_text: recast,
|
new_text: recast,
|
||||||
|
@ -1014,15 +1014,86 @@ async fn test_kcl_lsp_semantic_tokens() {
|
|||||||
if let tower_lsp::lsp_types::SemanticTokensResult::Tokens(semantic_tokens) = semantic_tokens {
|
if let tower_lsp::lsp_types::SemanticTokensResult::Tokens(semantic_tokens) = semantic_tokens {
|
||||||
assert_eq!(semantic_tokens.data.len(), 2);
|
assert_eq!(semantic_tokens.data.len(), 2);
|
||||||
assert_eq!(semantic_tokens.data[0].length, 13);
|
assert_eq!(semantic_tokens.data[0].length, 13);
|
||||||
|
assert_eq!(semantic_tokens.data[0].delta_start, 0);
|
||||||
|
assert_eq!(semantic_tokens.data[0].delta_line, 0);
|
||||||
assert_eq!(semantic_tokens.data[0].token_type, 8);
|
assert_eq!(semantic_tokens.data[0].token_type, 8);
|
||||||
assert_eq!(semantic_tokens.data[1].length, 4);
|
assert_eq!(semantic_tokens.data[1].length, 4);
|
||||||
assert_eq!(semantic_tokens.data[1].delta_start, 14);
|
assert_eq!(semantic_tokens.data[1].delta_start, 14);
|
||||||
|
assert_eq!(semantic_tokens.data[1].delta_line, 0);
|
||||||
assert_eq!(semantic_tokens.data[1].token_type, 3);
|
assert_eq!(semantic_tokens.data[1].token_type, 3);
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected semantic tokens");
|
panic!("Expected semantic tokens");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_kcl_lsp_semantic_tokens_multiple_comments() {
|
||||||
|
let server = kcl_lsp_server(false).await.unwrap();
|
||||||
|
|
||||||
|
// Send open file.
|
||||||
|
server
|
||||||
|
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
|
||||||
|
text_document: tower_lsp::lsp_types::TextDocumentItem {
|
||||||
|
uri: "file:///test.kcl".try_into().unwrap(),
|
||||||
|
language_id: "kcl".to_string(),
|
||||||
|
version: 1,
|
||||||
|
text: r#"// Ball Bearing
|
||||||
|
// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
|
||||||
|
|
||||||
|
// Define constants like ball diameter, inside diameter, overhange length, and thickness
|
||||||
|
const sphereDia = 0.5"#.to_string(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
server.wait_on_handle().await;
|
||||||
|
|
||||||
|
// Send semantic tokens request.
|
||||||
|
let semantic_tokens = server
|
||||||
|
.semantic_tokens_full(tower_lsp::lsp_types::SemanticTokensParams {
|
||||||
|
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||||
|
uri: "file:///test.kcl".try_into().unwrap(),
|
||||||
|
},
|
||||||
|
partial_result_params: Default::default(),
|
||||||
|
work_done_progress_params: Default::default(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Check the semantic tokens.
|
||||||
|
if let tower_lsp::lsp_types::SemanticTokensResult::Tokens(semantic_tokens) = semantic_tokens {
|
||||||
|
assert_eq!(semantic_tokens.data.len(), 7);
|
||||||
|
assert_eq!(semantic_tokens.data[0].length, 15);
|
||||||
|
assert_eq!(semantic_tokens.data[0].delta_start, 0);
|
||||||
|
assert_eq!(semantic_tokens.data[0].delta_line, 0);
|
||||||
|
assert_eq!(semantic_tokens.data[0].token_type, 6);
|
||||||
|
assert_eq!(semantic_tokens.data[1].length, 232);
|
||||||
|
assert_eq!(semantic_tokens.data[1].delta_start, 0);
|
||||||
|
assert_eq!(semantic_tokens.data[1].delta_line, 1);
|
||||||
|
assert_eq!(semantic_tokens.data[1].token_type, 6);
|
||||||
|
assert_eq!(semantic_tokens.data[2].length, 88);
|
||||||
|
assert_eq!(semantic_tokens.data[2].delta_start, 0);
|
||||||
|
assert_eq!(semantic_tokens.data[2].delta_line, 2);
|
||||||
|
assert_eq!(semantic_tokens.data[2].token_type, 6);
|
||||||
|
assert_eq!(semantic_tokens.data[3].length, 5);
|
||||||
|
assert_eq!(semantic_tokens.data[3].delta_start, 0);
|
||||||
|
assert_eq!(semantic_tokens.data[3].delta_line, 1);
|
||||||
|
assert_eq!(semantic_tokens.data[3].token_type, 4);
|
||||||
|
assert_eq!(semantic_tokens.data[4].length, 9);
|
||||||
|
assert_eq!(semantic_tokens.data[4].delta_start, 6);
|
||||||
|
assert_eq!(semantic_tokens.data[4].delta_line, 0);
|
||||||
|
assert_eq!(semantic_tokens.data[4].token_type, 1);
|
||||||
|
assert_eq!(semantic_tokens.data[5].length, 1);
|
||||||
|
assert_eq!(semantic_tokens.data[5].delta_start, 10);
|
||||||
|
assert_eq!(semantic_tokens.data[5].token_type, 2);
|
||||||
|
assert_eq!(semantic_tokens.data[6].length, 3);
|
||||||
|
assert_eq!(semantic_tokens.data[6].delta_start, 2);
|
||||||
|
assert_eq!(semantic_tokens.data[6].token_type, 0);
|
||||||
|
} else {
|
||||||
|
panic!("Expected semantic tokens");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_kcl_lsp_document_symbol() {
|
async fn test_kcl_lsp_document_symbol() {
|
||||||
let server = kcl_lsp_server(false).await.unwrap();
|
let server = kcl_lsp_server(false).await.unwrap();
|
||||||
@ -1112,6 +1183,183 @@ async fn test_kcl_lsp_formatting() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_kcl_lsp_formatting_extra_parens() {
|
||||||
|
let server = kcl_lsp_server(false).await.unwrap();
|
||||||
|
|
||||||
|
// Send open file.
|
||||||
|
server
|
||||||
|
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
|
||||||
|
text_document: tower_lsp::lsp_types::TextDocumentItem {
|
||||||
|
uri: "file:///test.kcl".try_into().unwrap(),
|
||||||
|
language_id: "kcl".to_string(),
|
||||||
|
version: 1,
|
||||||
|
text: r#"// Ball Bearing
|
||||||
|
// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
|
||||||
|
|
||||||
|
// Define constants like ball diameter, inside diameter, overhange length, and thickness
|
||||||
|
const sphereDia = 0.5
|
||||||
|
const insideDia = 1
|
||||||
|
const thickness = 0.25
|
||||||
|
const overHangLength = .4
|
||||||
|
|
||||||
|
// Sketch and revolve the inside bearing piece
|
||||||
|
const insideRevolve = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([insideDia / 2, 0], %)
|
||||||
|
|> line([0, thickness + sphereDia / 2], %)
|
||||||
|
|> line([overHangLength, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([-overHangLength + thickness, 0], %)
|
||||||
|
|> line([0, -sphereDia], %)
|
||||||
|
|> line([overHangLength - thickness, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([-overHangLength, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'y' }, %)
|
||||||
|
|
||||||
|
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
|
||||||
|
const sphere = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([
|
||||||
|
0.05 + insideDia / 2 + thickness,
|
||||||
|
0 - 0.05
|
||||||
|
], %)
|
||||||
|
|> line([sphereDia - 0.1, 0], %)
|
||||||
|
|> arc({
|
||||||
|
angle_start: 0,
|
||||||
|
angle_end: -180,
|
||||||
|
radius: sphereDia / 2 - 0.05
|
||||||
|
}, %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'x' }, %)
|
||||||
|
|> patternCircular3d({
|
||||||
|
axis: [0, 0, 1],
|
||||||
|
center: [0, 0, 0],
|
||||||
|
repetitions: 10,
|
||||||
|
arcDegrees: 360,
|
||||||
|
rotateDuplicates: true
|
||||||
|
}, %)
|
||||||
|
|
||||||
|
// Sketch and revolve the outside bearing
|
||||||
|
const outsideRevolve = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([
|
||||||
|
insideDia / 2 + thickness + sphereDia,
|
||||||
|
0
|
||||||
|
], %)
|
||||||
|
|> line([0, sphereDia / 2], %)
|
||||||
|
|> line([-overHangLength + thickness, 0], %)
|
||||||
|
|> line([0, thickness], %)
|
||||||
|
|> line([overHangLength, 0], %)
|
||||||
|
|> line([0, -2 * thickness - sphereDia], %)
|
||||||
|
|> line([-overHangLength, 0], %)
|
||||||
|
|> line([0, thickness], %)
|
||||||
|
|> line([overHangLength - thickness, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'y' }, %)"#
|
||||||
|
.to_string(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
server.wait_on_handle().await;
|
||||||
|
|
||||||
|
// Send formatting request.
|
||||||
|
let formatting = server
|
||||||
|
.formatting(tower_lsp::lsp_types::DocumentFormattingParams {
|
||||||
|
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||||
|
uri: "file:///test.kcl".try_into().unwrap(),
|
||||||
|
},
|
||||||
|
options: tower_lsp::lsp_types::FormattingOptions {
|
||||||
|
tab_size: 2,
|
||||||
|
insert_spaces: true,
|
||||||
|
properties: Default::default(),
|
||||||
|
trim_trailing_whitespace: None,
|
||||||
|
insert_final_newline: None,
|
||||||
|
trim_final_newlines: None,
|
||||||
|
},
|
||||||
|
work_done_progress_params: Default::default(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Check the formatting.
|
||||||
|
assert_eq!(formatting.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
formatting[0].range,
|
||||||
|
tower_lsp::lsp_types::Range {
|
||||||
|
start: tower_lsp::lsp_types::Position { line: 0, character: 0 },
|
||||||
|
end: tower_lsp::lsp_types::Position {
|
||||||
|
line: 60,
|
||||||
|
character: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
formatting[0].new_text,
|
||||||
|
r#"// Ball Bearing
|
||||||
|
// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
|
||||||
|
|
||||||
|
|
||||||
|
// Define constants like ball diameter, inside diameter, overhange length, and thickness
|
||||||
|
const sphereDia = 0.5
|
||||||
|
const insideDia = 1
|
||||||
|
const thickness = 0.25
|
||||||
|
const overHangLength = .4
|
||||||
|
|
||||||
|
// Sketch and revolve the inside bearing piece
|
||||||
|
const insideRevolve = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([insideDia / 2, 0], %)
|
||||||
|
|> line([0, thickness + sphereDia / 2], %)
|
||||||
|
|> line([overHangLength, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([-overHangLength + thickness, 0], %)
|
||||||
|
|> line([0, -sphereDia], %)
|
||||||
|
|> line([overHangLength - thickness, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([-overHangLength, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'y' }, %)
|
||||||
|
|
||||||
|
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
|
||||||
|
const sphere = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([
|
||||||
|
0.05 + insideDia / 2 + thickness,
|
||||||
|
0 - 0.05
|
||||||
|
], %)
|
||||||
|
|> line([sphereDia - 0.1, 0], %)
|
||||||
|
|> arc({
|
||||||
|
angle_start: 0,
|
||||||
|
angle_end: -180,
|
||||||
|
radius: sphereDia / 2 - 0.05
|
||||||
|
}, %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'x' }, %)
|
||||||
|
|> patternCircular3d({
|
||||||
|
axis: [0, 0, 1],
|
||||||
|
center: [0, 0, 0],
|
||||||
|
repetitions: 10,
|
||||||
|
arcDegrees: 360,
|
||||||
|
rotateDuplicates: true
|
||||||
|
}, %)
|
||||||
|
|
||||||
|
// Sketch and revolve the outside bearing
|
||||||
|
const outsideRevolve = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([
|
||||||
|
insideDia / 2 + thickness + sphereDia,
|
||||||
|
0
|
||||||
|
], %)
|
||||||
|
|> line([0, sphereDia / 2], %)
|
||||||
|
|> line([-overHangLength + thickness, 0], %)
|
||||||
|
|> line([0, thickness], %)
|
||||||
|
|> line([overHangLength, 0], %)
|
||||||
|
|> line([0, -2 * thickness - sphereDia], %)
|
||||||
|
|> line([-overHangLength, 0], %)
|
||||||
|
|> line([0, thickness], %)
|
||||||
|
|> line([overHangLength - thickness, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
|> revolve({ axis: 'y' }, %)"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_kcl_lsp_rename() {
|
async fn test_kcl_lsp_rename() {
|
||||||
let server = kcl_lsp_server(false).await.unwrap();
|
let server = kcl_lsp_server(false).await.unwrap();
|
||||||
|
@ -8,8 +8,8 @@ use uuid::Uuid;
|
|||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{
|
executor::{
|
||||||
ExtrudeGroup, ExtrudeGroupSet, ExtrudeSurface, ExtrudeTransform, GeoMeta, MemoryItem, Path, SketchGroup,
|
ExtrudeGroup, ExtrudeGroupSet, ExtrudeSurface, GeoMeta, MemoryItem, Path, SketchGroup, SketchGroupSet,
|
||||||
SketchGroupSet, SketchSurface,
|
SketchSurface,
|
||||||
},
|
},
|
||||||
std::Args,
|
std::Args,
|
||||||
};
|
};
|
||||||
@ -224,48 +224,3 @@ pub(crate) async fn do_post_extrude(
|
|||||||
meta: sketch_group.meta,
|
meta: sketch_group.meta,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the extrude wall transform.
|
|
||||||
pub async fn get_extrude_wall_transform(args: Args) -> Result<MemoryItem, KclError> {
|
|
||||||
let (surface_name, extrude_group) = args.get_path_name_extrude_group()?;
|
|
||||||
let result = inner_get_extrude_wall_transform(&surface_name, *extrude_group, args)?;
|
|
||||||
Ok(MemoryItem::ExtrudeTransform(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the extrude wall transform.
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// const box = startSketchOn('XY')
|
|
||||||
/// |> startProfileAt([0, 0], %)
|
|
||||||
/// |> line([0, 10], %)
|
|
||||||
/// |> line([10, 0], %)
|
|
||||||
/// |> line([0, -10], %, "surface")
|
|
||||||
/// |> close(%)
|
|
||||||
/// |> extrude(5, %)
|
|
||||||
///
|
|
||||||
/// const transform = getExtrudeWallTransform('surface', box)
|
|
||||||
/// ```
|
|
||||||
#[stdlib {
|
|
||||||
name = "getExtrudeWallTransform"
|
|
||||||
}]
|
|
||||||
fn inner_get_extrude_wall_transform(
|
|
||||||
surface_name: &str,
|
|
||||||
extrude_group: ExtrudeGroup,
|
|
||||||
args: Args,
|
|
||||||
) -> Result<Box<ExtrudeTransform>, KclError> {
|
|
||||||
let surface = extrude_group.get_path_by_name(surface_name).ok_or_else(|| {
|
|
||||||
KclError::Type(KclErrorDetails {
|
|
||||||
message: format!(
|
|
||||||
"Expected a surface name that exists in the given ExtrudeGroup, found `{}`",
|
|
||||||
surface_name
|
|
||||||
),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Box::new(ExtrudeTransform {
|
|
||||||
position: surface.get_position(),
|
|
||||||
rotation: surface.get_rotation(),
|
|
||||||
meta: extrude_group.meta,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
@ -44,7 +44,6 @@ lazy_static! {
|
|||||||
Box::new(LegAngX),
|
Box::new(LegAngX),
|
||||||
Box::new(LegAngY),
|
Box::new(LegAngY),
|
||||||
Box::new(crate::std::extrude::Extrude),
|
Box::new(crate::std::extrude::Extrude),
|
||||||
Box::new(crate::std::extrude::GetExtrudeWallTransform),
|
|
||||||
Box::new(crate::std::segment::SegEndX),
|
Box::new(crate::std::segment::SegEndX),
|
||||||
Box::new(crate::std::segment::SegEndY),
|
Box::new(crate::std::segment::SegEndY),
|
||||||
Box::new(crate::std::segment::LastSegX),
|
Box::new(crate::std::segment::LastSegX),
|
||||||
@ -906,54 +905,6 @@ impl Args {
|
|||||||
|
|
||||||
Ok((number, sketch_set))
|
Ok((number, sketch_set))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_path_name_extrude_group(&self) -> Result<(String, Box<ExtrudeGroup>), KclError> {
|
|
||||||
// Iterate over our args, the first argument should be a UserVal with a string value.
|
|
||||||
// The second argument should be a ExtrudeGroup.
|
|
||||||
let first_value = self
|
|
||||||
.args
|
|
||||||
.first()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
KclError::Type(KclErrorDetails {
|
|
||||||
message: format!("Expected a string as the first argument, found `{:?}`", self.args),
|
|
||||||
source_ranges: vec![self.source_range],
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
.get_json_value()?;
|
|
||||||
|
|
||||||
let path_name = if let serde_json::Value::String(s) = first_value {
|
|
||||||
s.to_string()
|
|
||||||
} else {
|
|
||||||
return Err(KclError::Type(KclErrorDetails {
|
|
||||||
message: format!("Expected a string 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 ExtrudeGroup as the second argument, found `{:?}`",
|
|
||||||
self.args
|
|
||||||
),
|
|
||||||
source_ranges: vec![self.source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let extrude_group = if let MemoryItem::ExtrudeGroup(sg) = second_value {
|
|
||||||
sg.clone()
|
|
||||||
} else {
|
|
||||||
return Err(KclError::Type(KclErrorDetails {
|
|
||||||
message: format!(
|
|
||||||
"Expected a ExtrudeGroup as the second argument, found `{:?}`",
|
|
||||||
self.args
|
|
||||||
),
|
|
||||||
source_ranges: vec![self.source_range],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((path_name, extrude_group))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the length of the given leg.
|
/// Returns the length of the given leg.
|
||||||
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |