Merge branch 'franknoirot/adhoc/appearance-tweaks-modeling-view' into franknoirot/adhoc/split-sidebar

This commit is contained in:
Frank Noirot
2025-04-21 13:51:37 -04:00
committed by GitHub
69 changed files with 5693296 additions and 197 deletions

File diff suppressed because one or more lines are too long

View File

@ -109,3 +109,98 @@ Coordinate systems:
- `zoo` (the default), forward: -Y, up: +Z, handedness: right
- `opengl`, forward: +Z, up: +Y, handedness: right
- `vulkan`, forward: +Z, up: -Y, handedness: left
### Performance
Parallelized foreign-file imports now let you overlap file reads, initialization,
and rendering. To maximize throughput, you need to understand the three distinct
stages—reading, initializing (background render start), and invocation (blocking)
—and structure your code to defer blocking operations until the end.
#### Foreign Import Execution Stages
1. **Import (Read) Stage**
```norun
import "tests/inputs/cube.step" as cube
```
- Reads the file from disk and makes its API available.
- **Does _not_** start Engine rendering or block your script.
2. **Initialization (Background Render) Stage**
```norun
import "tests/inputs/cube.step" as cube
myCube = cube // <- This line starts background rendering
```
- Invoking the imported symbol (assignment or plain call) triggers Engine rendering _in the background_.
- This kickstarts the render pipeline but doesnt block—you can continue other work while the Engine processes the model.
3. **Invocation (Blocking) Stage**
```norun
import "tests/inputs/cube.step" as cube
myCube = cube
myCube
|> translate(z=10) // <- This line blocks
```
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
- This is the only point where your script will block.
> **Nuance:** Foreign imports differ from pure KCL modules—calling the same import symbol multiple times (e.g., `screw` twice) starts background rendering twice.
#### Best Practices
##### 1. Defer Blocking Calls
Initialize early but delay all transformations until after your heavy computation:
```norun
import "tests/inputs/cube.step" as cube // 1) Read
myCube = cube // 2) Background render starts
// --- perform other operations and calculations or setup here ---
myCube
|> translate(z=10) // 3) Blocks only here
```
##### 2. Encapsulate Imports in Modules
Keep `main.kcl` free of reads and initialization; wrap them:
```norun
// imports.kcl
import "tests/inputs/cube.step" as cube // Read only
export myCube = cube // Kick off rendering
```
```norun
// main.kcl
import myCube from "imports.kcl" // Import the initialized object
// ... computations ...
myCube
|> translate(z=10) // Blocking call at the end
```
##### 3. Avoid Immediate Method Calls
```norun
import "tests/inputs/cube.step" as cube
cube
|> translate(z=10) // Blocks immediately, negating parallelism
```
Both calling methods right on `cube` immediately or leaving an implicit import without assignment introduce blocking.
#### Future Improvements
Upcoming releases will autoanalyze dependencies and only block when truly necessary. Until then, explicit deferral and modular wrapping give you the best performance.

View File

@ -53,7 +53,7 @@ rotate(
### Returns
[`SolidOrSketchOrImportedGeometry`](/docs/kcl/types/SolidOrSketchOrImportedGeometry) - Data for a solid or an imported geometry.
[`SolidOrSketchOrImportedGeometry`](/docs/kcl/types/SolidOrSketchOrImportedGeometry) - Data for a solid, sketch, or an imported geometry.
### Examples

View File

@ -37,7 +37,7 @@ scale(
### Returns
[`SolidOrSketchOrImportedGeometry`](/docs/kcl/types/SolidOrSketchOrImportedGeometry) - Data for a solid or an imported geometry.
[`SolidOrSketchOrImportedGeometry`](/docs/kcl/types/SolidOrSketchOrImportedGeometry) - Data for a solid, sketch, or an imported geometry.
### Examples

View File

@ -28101,14 +28101,62 @@
"args": [
{
"name": "solids",
"type": "[Solid]",
"type": "SolidOrImportedGeometry",
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "Array_of_Solid",
"type": "array",
"items": {
"$ref": "#/components/schemas/Solid"
},
"title": "SolidOrImportedGeometry",
"description": "Data for a solid or an imported geometry.",
"oneOf": [
{
"description": "Data for an imported geometry.",
"type": "object",
"required": [
"id",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"importedGeometry"
]
},
"id": {
"description": "The ID of the imported geometry.",
"type": "string",
"format": "uuid"
},
"value": {
"description": "The original file paths.",
"type": "array",
"items": {
"type": "string"
}
}
}
},
{
"type": [
"object",
"array"
],
"items": {
"$ref": "#/components/schemas/Solid"
},
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"solidSet"
]
}
}
}
],
"definitions": {
"Solid": {
"type": "object",
@ -34571,14 +34619,62 @@
],
"returnValue": {
"name": "",
"type": "[Solid]",
"type": "SolidOrImportedGeometry",
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "Array_of_Solid",
"type": "array",
"items": {
"$ref": "#/components/schemas/Solid"
},
"title": "SolidOrImportedGeometry",
"description": "Data for a solid or an imported geometry.",
"oneOf": [
{
"description": "Data for an imported geometry.",
"type": "object",
"required": [
"id",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"importedGeometry"
]
},
"id": {
"description": "The ID of the imported geometry.",
"type": "string",
"format": "uuid"
},
"value": {
"description": "The original file paths.",
"type": "array",
"items": {
"type": "string"
}
}
}
},
{
"type": [
"object",
"array"
],
"items": {
"$ref": "#/components/schemas/Solid"
},
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"solidSet"
]
}
}
}
],
"definitions": {
"Solid": {
"type": "object",
@ -36198,7 +36294,8 @@
"// Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.\n// This example shows _before_ the pattern.\nexampleSketch = startSketchOn(XZ)\n |> startProfileAt([0, 0], %)\n |> line(end = [0, 2])\n |> line(end = [3, 1])\n |> line(end = [0, -4])\n |> close()\n\nexample = extrude(exampleSketch, length = 1)\n |> appearance(color = '#ff0000', metalness = 90, roughness = 90)\n |> patternLinear3d(axis = [1, 0, 1], instances = 7, distance = 6)",
"// Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.\n// This example shows _after_ the pattern.\nexampleSketch = startSketchOn(XZ)\n |> startProfileAt([0, 0], %)\n |> line(end = [0, 2])\n |> line(end = [3, 1])\n |> line(end = [0, -4])\n |> close()\n\nexample = extrude(exampleSketch, length = 1)\n |> patternLinear3d(axis = [1, 0, 1], instances = 7, distance = 6)\n |> appearance(color = '#ff0000', metalness = 90, roughness = 90)",
"// Color the result of a 2D pattern that was extruded.\nexampleSketch = startSketchOn(XZ)\n |> startProfileAt([.5, 25], %)\n |> line(end = [0, 5])\n |> line(end = [-1, 0])\n |> line(end = [0, -5])\n |> close()\n |> patternCircular2d(\n center = [0, 0],\n instances = 13,\n arcDegrees = 360,\n rotateDuplicates = true,\n )\n\nexample = extrude(exampleSketch, length = 1)\n |> appearance(color = '#ff0000', metalness = 90, roughness = 90)",
"// Color the result of a sweep.\n\n// Create a path for the sweep.\nsweepPath = startSketchOn(XZ)\n |> startProfileAt([0.05, 0.05], %)\n |> line(end = [0, 7])\n |> tangentialArc(angle = 90, radius = 5)\n |> line(end = [-3, 0])\n |> tangentialArc(angle = -90, radius = 5)\n |> line(end = [0, 7])\n\npipeHole = startSketchOn(XY)\n |> circle(center = [0, 0], radius = 1.5)\n\nsweepSketch = startSketchOn(XY)\n |> circle(center = [0, 0], radius = 2)\n |> hole(pipeHole, %)\n |> sweep(path = sweepPath)\n |> appearance(color = \"#ff0000\", metalness = 50, roughness = 50)"
"// Color the result of a sweep.\n\n// Create a path for the sweep.\nsweepPath = startSketchOn(XZ)\n |> startProfileAt([0.05, 0.05], %)\n |> line(end = [0, 7])\n |> tangentialArc(angle = 90, radius = 5)\n |> line(end = [-3, 0])\n |> tangentialArc(angle = -90, radius = 5)\n |> line(end = [0, 7])\n\npipeHole = startSketchOn(XY)\n |> circle(center = [0, 0], radius = 1.5)\n\nsweepSketch = startSketchOn(XY)\n |> circle(center = [0, 0], radius = 2)\n |> hole(pipeHole, %)\n |> sweep(path = sweepPath)\n |> appearance(color = \"#ff0000\", metalness = 50, roughness = 50)",
"// Change the appearance of an imported model.\n\n\nimport \"tests/inputs/cube.sldprt\" as cube\n\ncube\n// |> appearance(\n// color = \"#ff0000\",\n// metalness = 50,\n// roughness = 50\n// )"
]
},
{
@ -249578,7 +249675,7 @@
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "SolidOrSketchOrImportedGeometry",
"description": "Data for a solid or an imported geometry.",
"description": "Data for a solid, sketch, or an imported geometry.",
"oneOf": [
{
"description": "Data for an imported geometry.",
@ -260975,7 +261072,7 @@
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "SolidOrSketchOrImportedGeometry",
"description": "Data for a solid or an imported geometry.",
"description": "Data for a solid, sketch, or an imported geometry.",
"oneOf": [
{
"description": "Data for an imported geometry.",
@ -262721,7 +262818,7 @@
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "SolidOrSketchOrImportedGeometry",
"description": "Data for a solid or an imported geometry.",
"description": "Data for a solid, sketch, or an imported geometry.",
"oneOf": [
{
"description": "Data for an imported geometry.",
@ -270879,7 +270976,7 @@
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "SolidOrSketchOrImportedGeometry",
"description": "Data for a solid or an imported geometry.",
"description": "Data for a solid, sketch, or an imported geometry.",
"oneOf": [
{
"description": "Data for an imported geometry.",
@ -319774,7 +319871,7 @@
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "SolidOrSketchOrImportedGeometry",
"description": "Data for a solid or an imported geometry.",
"description": "Data for a solid, sketch, or an imported geometry.",
"oneOf": [
{
"description": "Data for an imported geometry.",
@ -327932,7 +328029,7 @@
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "SolidOrSketchOrImportedGeometry",
"description": "Data for a solid or an imported geometry.",
"description": "Data for a solid, sketch, or an imported geometry.",
"oneOf": [
{
"description": "Data for an imported geometry.",

View File

@ -33,7 +33,7 @@ translate(
### Returns
[`SolidOrSketchOrImportedGeometry`](/docs/kcl/types/SolidOrSketchOrImportedGeometry) - Data for a solid or an imported geometry.
[`SolidOrSketchOrImportedGeometry`](/docs/kcl/types/SolidOrSketchOrImportedGeometry) - Data for a solid, sketch, or an imported geometry.
### Examples

View File

@ -1,10 +1,10 @@
---
title: "SolidOrSketchOrImportedGeometry"
excerpt: "Data for a solid or an imported geometry."
excerpt: "Data for a solid, sketch, or an imported geometry."
layout: manual
---
Data for a solid or an imported geometry.
Data for a solid, sketch, or an imported geometry.

View File

@ -405,10 +405,6 @@ test.describe('Point-and-click assemblies tests', () => {
)
await scene.settled(cmdBar)
// TODO: remove this once #5780 is fixed
await page.reload()
await scene.settled(cmdBar)
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await toolbar.closePane('code')
await scene.expectPixelColor(partColor, partPoint, tolerance)
@ -453,10 +449,6 @@ test.describe('Point-and-click assemblies tests', () => {
)
await scene.settled(cmdBar)
// TODO: remove this once #5780 is fixed
await page.reload()
await scene.settled(cmdBar)
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await toolbar.closePane('code')
await scene.expectPixelColor(partColor, partPoint, tolerance)

View File

@ -3105,23 +3105,25 @@ test.describe('manual edits during sketch mode', () => {
await test.step('Edit sketch by dragging handle', async () => {
await page.waitForTimeout(500)
await expect
.poll(async () => {
await editor.expectEditor.toContain('length = 156.54, angle = -28')
await page.mouse.move(handle1Location.x, handle1Location.y)
await page.mouse.down()
await page.mouse.move(
handle1Location.x + 50,
handle1Location.y + 50,
{
steps: 5,
}
)
await page.mouse.up()
await editor.expectEditor.toContain('length = 231.59, angle = -34')
return true
})
.poll(
async () => {
await editor.expectEditor.toContain('length = 156.54, angle = -28')
await page.mouse.move(handle1Location.x, handle1Location.y)
await page.mouse.down()
await page.mouse.move(
handle1Location.x + 50,
handle1Location.y + 50,
{
steps: 5,
}
)
await page.mouse.up()
await editor.expectEditor.toContain('length = 231.59, angle = -34')
return true
},
{ timeout: 10_000 }
)
.toBeTruthy()
// await page.waitForTimeout(1000) // Wait for update
})
await test.step('Delete variables and wait for re-execution', async () => {
@ -3137,16 +3139,19 @@ test.describe('manual edits during sketch mode', () => {
await editor.expectEditor.toContain('length = 231.59, angle = -34')
await page.waitForTimeout(500)
await expect
.poll(async () => {
await page.mouse.move(handle2Location.x, handle2Location.y)
await page.mouse.down()
await page.mouse.move(handle2Location.x, handle2Location.y - 50, {
steps: 5,
})
await page.mouse.up()
await editor.expectEditor.toContain('length = 167.36, angle = -14')
return true
})
.poll(
async () => {
await page.mouse.move(handle2Location.x, handle2Location.y)
await page.mouse.down()
await page.mouse.move(handle2Location.x, handle2Location.y - 50, {
steps: 5,
})
await page.mouse.up()
await editor.expectEditor.toContain('length = 167.36, angle = -14')
return true
},
{ timeout: 10_000 }
)
.toBeTruthy()
})
@ -3165,17 +3170,20 @@ test.describe('manual edits during sketch mode', () => {
await test.step('edit sketch again', async () => {
await page.waitForTimeout(500) // Wait for deferred execution
await expect
.poll(async () => {
await editor.expectEditor.toContain('length = 167.36, angle = -14')
await page.mouse.move(handle3Location.x, handle3Location.y)
await page.mouse.down()
await page.mouse.move(handle3Location.x, handle3Location.y + 110, {
steps: 5,
})
await page.mouse.up()
await editor.expectEditor.toContain('length = 219.2, angle = -56')
return true
})
.poll(
async () => {
await editor.expectEditor.toContain('length = 167.36, angle = -14')
await page.mouse.move(handle3Location.x, handle3Location.y)
await page.mouse.down()
await page.mouse.move(handle3Location.x, handle3Location.y + 110, {
steps: 5,
})
await page.mouse.up()
await editor.expectEditor.toContain('length = 219.2, angle = -56')
return true
},
{ timeout: 10_000 }
)
.toBeTruthy()
})

View File

@ -44,7 +44,9 @@
packages =
(with pkgs; [
rustToolchain
cargo-criterion
cargo-nextest
cargo-sort
just
postgresql.lib
openssl

View File

@ -25,10 +25,14 @@ When you submit a PR to add or modify KCL samples, images and STEP files will be
---
#### [80-20-rail](80-20-rail/main.kcl) ([screenshot](screenshots/80-20-rail.png))
[![80-20-rail](screenshots/80-20-rail.png)](80-20-rail/main.kcl)
#### [axial-fan](axial-fan/main.kcl) ([screenshot](screenshots/axial-fan.png))
[![axial-fan](screenshots/axial-fan.png)](axial-fan/main.kcl)
#### [ball-bearing](ball-bearing/main.kcl) ([screenshot](screenshots/ball-bearing.png))
[![ball-bearing](screenshots/ball-bearing.png)](ball-bearing/main.kcl)
#### [bench](bench/main.kcl) ([screenshot](screenshots/bench.png))
[![bench](screenshots/bench.png)](bench/main.kcl)
#### [bottle](bottle/main.kcl) ([screenshot](screenshots/bottle.png))
[![bottle](screenshots/bottle.png)](bottle/main.kcl)
#### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png))
[![bracket](screenshots/bracket.png)](bracket/main.kcl)
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png))

View File

@ -0,0 +1,153 @@
// Fan Housing
// The plastic housing that contains the fan and the motor
// Set units
@settings(defaultLengthUnit = mm)
// Import parameters
import * from "parameters.kcl"
// Model the housing which holds the motor, the fan, and the mounting provisions
// Bottom mounting face
bottomFaceSketch = startSketchOn(XY)
|> startProfileAt([-fanSize / 2, -fanSize / 2], %)
|> angledLine(angle = 0, length = fanSize, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = fanSize, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|> close()
|> hole(circle(center = [0, 0], radius = 4), %)
|> hole(circle(
center = [
mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
), %)
|> hole(circle(
center = [
-mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
), %)
|> hole(circle(
center = [
mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
), %)
|> hole(circle(
center = [
-mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
), %)
|> extrude(length = 4)
// Add large openings to the bottom face to allow airflow through the fan
airflowPattern = startSketchOn(bottomFaceSketch, face = END)
|> startProfileAt([fanSize * 7 / 25, -fanSize * 9 / 25], %)
|> angledLine(angle = 140, length = fanSize * 12 / 25, tag = $seg01)
|> tangentialArc(radius = fanSize * 1 / 50, angle = 90)
|> angledLine(angle = -130, length = fanSize * 8 / 25)
|> tangentialArc(radius = fanSize * 1 / 50, angle = 90)
|> angledLine(angle = segAng(seg01) + 180, length = fanSize * 2 / 25)
|> tangentialArc(radius = fanSize * 8 / 25, angle = 40)
|> xLine(length = fanSize * 3 / 25)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternCircular2d(
instances = 4,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
|> extrude(length = -4)
// Create the middle segment of the fan housing body
housingMiddleLength = fanSize / 3
housingMiddleRadius = fanSize / 3 - 1
bodyMiddle = startSketchOn(bottomFaceSketch, face = END)
|> startProfileAt([
housingMiddleLength / 2,
-housingMiddleLength / 2 - housingMiddleRadius
], %)
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> yLine(length = housingMiddleLength)
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> xLine(length = -housingMiddleLength)
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> yLine(length = -housingMiddleLength)
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> extrude(length = fanHeight - 4 - 4)
// Cut a hole in the body to accommodate the fan
bodyFanHole = startSketchOn(bodyMiddle, face = END)
|> circle(center = [0, 0], radius = fanSize * 23 / 50)
|> extrude(length = -(fanHeight - 4 - 4))
// Top mounting face. Cut a hole in the face to accommodate the fan
topFaceSketch = startSketchOn(bodyMiddle, face = END)
topHoles = startProfileAt([-fanSize / 2, -fanSize / 2], topFaceSketch)
|> angledLine(angle = 0, length = fanSize, tag = $rectangleSegmentA002)
|> angledLine(angle = segAng(rectangleSegmentA002) + 90, length = fanSize, tag = $rectangleSegmentB002)
|> angledLine(angle = segAng(rectangleSegmentA002), length = -segLen(rectangleSegmentA002), tag = $rectangleSegmentC002)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD002)
|> close()
|> hole(circle(center = [0, 0], radius = fanSize * 23 / 50), %)
|> hole(circle(
center = [
mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
), %)
|> hole(circle(
center = [
-mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
), %)
|> hole(circle(
center = [
mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
), %)
|> hole(circle(
center = [
-mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
), %)
|> extrude(length = 4)
// Create a housing for the electric motor to sit
motorHousing = startSketchOn(bottomFaceSketch, face = END)
|> circle(center = [0, 0], radius = 11.2)
|> extrude(length = 16)
startSketchOn(motorHousing, face = END)
|> circle(center = [0, 0], radius = 10)
|> extrude(length = -16)
|> appearance(color = "#a55e2c")
|> fillet(
radius = abs(fanSize - mountingHoleSpacing) / 2,
tags = [
getNextAdjacentEdge(rectangleSegmentA001),
getNextAdjacentEdge(rectangleSegmentB001),
getNextAdjacentEdge(rectangleSegmentC001),
getNextAdjacentEdge(rectangleSegmentD001),
getNextAdjacentEdge(rectangleSegmentA002),
getNextAdjacentEdge(rectangleSegmentB002),
getNextAdjacentEdge(rectangleSegmentC002),
getNextAdjacentEdge(rectangleSegmentD002)
],
)

View File

@ -0,0 +1,92 @@
// Fan
// Spinning axial fan that moves airflow
// Set units
@settings(defaultLengthUnit = mm)
// Import parameters
import * from "parameters.kcl"
// Model the center of the fan
fanCenter = startSketchOn(XZ)
|> startProfileAt([-0.0001, fanHeight], %)
|> xLine(endAbsolute = -15 + 1.5)
|> tangentialArc(radius = 1.5, angle = 90)
|> yLine(endAbsolute = 4.5)
|> xLine(endAbsolute = -13)
|> yLine(endAbsolute = profileStartY(%) - 5)
|> tangentialArc(radius = 1, angle = -90)
|> xLine(endAbsolute = -1)
|> yLine(length = 2)
|> xLine(length = -0.15)
|> line(endAbsolute = [
profileStartX(%) - 1,
profileStartY(%) - 1.4
])
|> xLine(endAbsolute = profileStartX(%))
|> yLine(endAbsolute = profileStartY(%))
|> close()
|> revolve(axis = {
direction = [0.0, 1.0],
origin = [0.0, 0.0]
})
|> appearance(color = "#f3e2d8")
// Create a function for a lofted fan blade cross section that rotates about the center hub of the fan
fn fanBlade(offsetHeight, startAngle) {
fanBlade = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|> startProfileAt([
15 * cos(toRadians(startAngle)),
15 * sin(toRadians(startAngle))
], %)
|> arc({
angleStart = startAngle,
angleEnd = startAngle + 14,
radius = 15
}, %)
|> arcTo({
end = [
fanSize * 22 / 50 * cos(toRadians(startAngle - 20)),
fanSize * 22 / 50 * sin(toRadians(startAngle - 20))
],
interior = [
fanSize * 11 / 50 * cos(toRadians(startAngle + 3)),
fanSize * 11 / 50 * sin(toRadians(startAngle + 3))
]
}, %)
|> arcTo({
end = [
fanSize * 22 / 50 * cos(toRadians(startAngle - 24)),
fanSize * 22 / 50 * sin(toRadians(startAngle - 24))
],
interior = [
fanSize * 22 / 50 * cos(toRadians(startAngle - 22)),
fanSize * 22 / 50 * sin(toRadians(startAngle - 22))
]
}, %)
|> arcTo({
end = [profileStartX(%), profileStartY(%)],
interior = [
fanSize * 11 / 50 * cos(toRadians(startAngle - 5)),
fanSize * 11 / 50 * sin(toRadians(startAngle - 5))
]
}, %)
|> close()
return fanBlade
}
// Loft the fan blade cross sections into a single blade, then pattern them about the fan center
loft([
fanBlade(4.5, 50),
fanBlade((fanHeight - 2 - 4) / 2, 30),
fanBlade(fanHeight - 2, 0)
])
|> appearance(color = "#f3e2d8")
|> patternCircular3d(
%,
instances = 9,
axis = [0, 0, 1],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)

View File

@ -0,0 +1,15 @@
// PC Fan
// A small axial fan, used to push or draw airflow over components to remove excess heat
// Set units
@settings(defaultLengthUnit = mm)
// Import all parts into assembly file
import "fan-housing.kcl" as fanHousing
import "motor.kcl" as motor
import "fan.kcl" as fan
// Produce the model for each imported part
fanHousing
motor
fan

View File

@ -0,0 +1,22 @@
// Motor
// A small electric motor to power the fan
// Set Units
@settings(defaultLengthUnit = mm)
// Import Parameters
import * from "parameters.kcl"
// Model the motor body and stem
topFacePlane = offsetPlane(XY, offset = 4)
motorBody = startSketchOn(topFacePlane)
|> circle(center = [0, 0], radius = 10, tag = $seg04)
|> extrude(length = 17)
|> appearance(color = "#021b55")
|> fillet(radius = 2, tags = [getOppositeEdge(seg04), seg04])
startSketchOn(offsetPlane(XY, offset = 21))
|> circle(center = [0, 0], radius = 1)
|> extrude(length = 3.8)
|> appearance(color = "#dbc89e")

View File

@ -0,0 +1,10 @@
// Global parameters for the axial fan
// Set units
@settings(defaultLengthUnit = mm)
// Define Parameters
export fanSize = 120
export fanHeight = 25
export mountingHoleSpacing = 105
export mountingHoleSize = 4.5

View File

@ -0,0 +1,35 @@
// Bottle
// A simple bottle with a hollow, watertight interior
// Set Units
@settings(defaultLengthUnit = mm)
// Input dimensions to define the bottle
bottleWidth = 80
bottleLength = 125
bottleHeight = 220
neckDepth = 18
neckDiameter = 45
wallThickness = 4
// Create a rounded body for the bottle
bottleBody = startSketchOn(XY)
|> startProfileAt([-bottleLength / 2, 0], %)
|> yLine(length = bottleWidth / 3)
|> arcTo({
end = [bottleLength / 2, bottleWidth / 3],
interior = [0, bottleWidth / 2]
}, %)
|> yLine(endAbsolute = 0)
|> mirror2d(axis = X)
|> close()
|> extrude(length = bottleHeight - neckDepth)
// Create a neck centered at the top of the bottle
bottleNeck = startSketchOn(bottleBody, face = END)
|> circle(center = [0, 0], radius = neckDiameter / 2)
|> extrude(length = neckDepth)
// Define a shell operation so that the entire body and neck are hollow, with only the top face opened
bottleShell = shell(bottleNeck, faces = [END], thickness = wallThickness)
|> appearance(%, color = "#0078c2")

View File

@ -6,6 +6,13 @@
"title": "80/20 Rail",
"description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position"
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "axial-fan/main.kcl",
"multipleFiles": true,
"title": "PC Fan",
"description": "A small axial fan, used to push or draw airflow over components to remove excess heat"
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "ball-bearing/main.kcl",
@ -20,6 +27,13 @@
"title": "Bench",
"description": "This is a slight remix of Depep1's original 3D Boaty (https://www.printables.com/model/1141963-3d-boaty). This is a tool used for benchmarking 3D FDM printers for bed adhesion, overhangs, bridging and top surface quality. The name of this file is a bit of misnomer, the shape of the object is a typical park bench."
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "bottle/main.kcl",
"multipleFiles": false,
"title": "Bottle",
"description": "A simple bottle with a hollow, watertight interior"
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "bracket/main.kcl",

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

89
rust/Cargo.lock generated
View File

@ -535,7 +535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -758,7 +758,7 @@ dependencies = [
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
"parking_lot_core 0.9.10",
]
[[package]]
@ -772,7 +772,7 @@ dependencies = [
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
"parking_lot_core 0.9.10",
]
[[package]]
@ -847,7 +847,7 @@ dependencies = [
"backtrace",
"lazy_static",
"mintex",
"parking_lot",
"parking_lot 0.12.3",
"rustc-hash 1.1.0",
"serde",
"serde_json",
@ -954,7 +954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1697,6 +1697,18 @@ dependencies = [
"similar",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "ipnet"
version = "2.11.0"
@ -1711,7 +1723,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1885,6 +1897,7 @@ dependencies = [
"image",
"indexmap 2.8.0",
"insta",
"instant",
"itertools 0.13.0",
"js-sys",
"kcl-derive-docs",
@ -1921,6 +1934,7 @@ dependencies = [
"validator",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-timer",
"web-sys",
"web-time",
"winnow 0.6.24",
@ -2460,6 +2474,17 @@ dependencies = [
"unicode-width 0.2.0",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
@ -2467,7 +2492,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
"parking_lot_core 0.9.10",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
]
[[package]]
@ -2478,7 +2517,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"redox_syscall 0.5.10",
"smallvec",
"windows-targets 0.52.6",
]
@ -2883,7 +2922,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -3018,6 +3057,15 @@ dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.10"
@ -3211,7 +3259,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -3809,7 +3857,7 @@ dependencies = [
"getrandom 0.3.1",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -3984,7 +4032,7 @@ dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"parking_lot 0.12.3",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@ -4581,6 +4629,21 @@ dependencies = [
"web-sys",
]
[[package]]
name = "wasm-timer"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f"
dependencies = [
"futures",
"js-sys",
"parking_lot 0.11.2",
"pin-utils",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.77"
@ -4632,7 +4695,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]

View File

@ -89,14 +89,17 @@ winnow = "=0.6.24"
zip = { workspace = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
instant = { version = "0.1.13", features = ["wasm-bindgen", "inaccurate"] }
js-sys = { version = "0.3.72" }
tokio = { workspace = true, features = ["sync", "time"] }
tower-lsp = { workspace = true, features = ["runtime-agnostic"] }
wasm-bindgen = "0.2.99"
wasm-bindgen-futures = "0.4.49"
wasm-timer = "0.2.5"
web-sys = { version = "0.3.76", features = ["console"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
instant = "0.1.13"
tokio = { workspace = true, features = ["full"] }
tokio-tungstenite = { version = "0.24.0", features = [
"rustls-tls-native-roots",

View File

@ -2109,7 +2109,7 @@ async fn kcl_test_better_type_names() {
},
None => todo!(),
};
assert_eq!(err, "This function expected the input argument to be one or more Solids but it's actually of type Sketch. You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`");
assert_eq!(err, "This function expected the input argument to be one or more Solids or imported geometry but it's actually of type Sketch. You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`");
}
#[tokio::test(flavor = "multi_thread")]

View File

@ -133,6 +133,7 @@ impl StdLibFnArg {
|| self.type_ == "[Solid]"
|| self.type_ == "SketchSurface"
|| self.type_ == "SketchOrSurface"
|| self.type_ == "SolidOrImportedGeometry"
|| self.type_ == "SolidOrSketchOrImportedGeometry")
&& (self.required || self.include_in_snippet)
{

View File

@ -45,6 +45,7 @@ pub struct EngineConnection {
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
ids_of_async_commands: Arc<RwLock<IndexMap<Uuid, SourceRange>>>,
/// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
@ -115,6 +116,17 @@ impl Drop for TcpReadHandle {
}
}
struct ResponsesInformation {
/// The responses from the engine.
responses: Arc<RwLock<IndexMap<uuid::Uuid, WebSocketResponse>>>,
}
impl ResponsesInformation {
pub async fn add(&self, id: Uuid, response: WebSocketResponse) {
self.responses.write().await.insert(id, response);
}
}
/// Requests to send to the engine, and a way to await a response.
struct ToEngineReq {
/// The request to send
@ -227,10 +239,13 @@ impl EngineConnection {
let session_data: Arc<RwLock<Option<ModelingSessionData>>> = Arc::new(RwLock::new(None));
let session_data2 = session_data.clone();
let responses: Arc<RwLock<IndexMap<uuid::Uuid, WebSocketResponse>>> = Arc::new(RwLock::new(IndexMap::new()));
let responses_clone = responses.clone();
let ids_of_async_commands: Arc<RwLock<IndexMap<Uuid, SourceRange>>> = Arc::new(RwLock::new(IndexMap::new()));
let socket_health = Arc::new(RwLock::new(SocketHealth::Active));
let pending_errors = Arc::new(RwLock::new(Vec::new()));
let pending_errors_clone = pending_errors.clone();
let responses_information = ResponsesInformation {
responses: responses.clone(),
};
let socket_health_tcp_read = socket_health.clone();
let tcp_read_handle = tokio::spawn(async move {
@ -244,8 +259,7 @@ impl EngineConnection {
WebSocketResponse::Success(SuccessWebSocketResponse {
resp: OkWebSocketResponseData::ModelingBatch { responses },
..
}) =>
{
}) => {
#[expect(
clippy::iter_over_hash_type,
reason = "modeling command uses a HashMap and keys are random, so we don't really have a choice"
@ -254,26 +268,32 @@ impl EngineConnection {
let id: uuid::Uuid = (*resp_id).into();
match batch_response {
BatchResponse::Success { response } => {
responses_clone.write().await.insert(
id,
WebSocketResponse::Success(SuccessWebSocketResponse {
success: true,
request_id: Some(id),
resp: OkWebSocketResponseData::Modeling {
modeling_response: response.clone(),
},
}),
);
// If the id is in our ids of async commands, remove
// it.
responses_information
.add(
id,
WebSocketResponse::Success(SuccessWebSocketResponse {
success: true,
request_id: Some(id),
resp: OkWebSocketResponseData::Modeling {
modeling_response: response.clone(),
},
}),
)
.await;
}
BatchResponse::Failure { errors } => {
responses_clone.write().await.insert(
id,
WebSocketResponse::Failure(FailureWebSocketResponse {
success: false,
request_id: Some(id),
errors: errors.clone(),
}),
);
responses_information
.add(
id,
WebSocketResponse::Failure(FailureWebSocketResponse {
success: false,
request_id: Some(id),
errors: errors.clone(),
}),
)
.await;
}
}
}
@ -291,14 +311,16 @@ impl EngineConnection {
errors,
}) => {
if let Some(id) = request_id {
responses_clone.write().await.insert(
*id,
WebSocketResponse::Failure(FailureWebSocketResponse {
success: false,
request_id: *request_id,
errors: errors.clone(),
}),
);
responses_information
.add(
*id,
WebSocketResponse::Failure(FailureWebSocketResponse {
success: false,
request_id: *request_id,
errors: errors.clone(),
}),
)
.await;
} else {
// Add it to our pending errors.
let mut pe = pending_errors_clone.write().await;
@ -314,7 +336,7 @@ impl EngineConnection {
}
if let Some(id) = id {
responses_clone.write().await.insert(id, ws_resp.clone());
responses_information.add(id, ws_resp.clone()).await;
}
}
Err(e) => {
@ -341,6 +363,7 @@ impl EngineConnection {
batch: Arc::new(RwLock::new(Vec::new())),
batch_end: Arc::new(RwLock::new(IndexMap::new())),
artifact_commands: Arc::new(RwLock::new(Vec::new())),
ids_of_async_commands,
default_planes: Default::default(),
session_data,
stats: Default::default(),
@ -366,6 +389,10 @@ impl EngineManager for EngineConnection {
self.artifact_commands.clone()
}
fn ids_of_async_commands(&self) -> Arc<RwLock<IndexMap<Uuid, SourceRange>>> {
self.ids_of_async_commands.clone()
}
fn stats(&self) -> &EngineStats {
&self.stats
}
@ -386,13 +413,13 @@ impl EngineManager for EngineConnection {
Ok(())
}
async fn inner_send_modeling_cmd(
async fn inner_fire_modeling_cmd(
&self,
id: uuid::Uuid,
_id: uuid::Uuid,
source_range: SourceRange,
cmd: WebSocketRequest,
_id_to_source_range: HashMap<Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
) -> Result<(), KclError> {
let (tx, rx) = oneshot::channel();
// Send the request to the engine, via the actor.
@ -424,6 +451,19 @@ impl EngineManager for EngineConnection {
})
})?;
Ok(())
}
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: HashMap<Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
self.inner_fire_modeling_cmd(id, source_range, cmd, id_to_source_range)
.await?;
// Wait for the response.
let current_time = std::time::Instant::now();
while current_time.elapsed().as_secs() < 60 {

View File

@ -12,7 +12,7 @@ use kcmc::{
WebSocketResponse,
},
};
use kittycad_modeling_cmds::{self as kcmc};
use kittycad_modeling_cmds::{self as kcmc, websocket::ModelingCmdReq, ImportFiles, ModelingCmd};
use tokio::sync::RwLock;
use uuid::Uuid;
@ -29,6 +29,8 @@ pub struct EngineConnection {
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
ids_of_async_commands: Arc<RwLock<IndexMap<Uuid, SourceRange>>>,
responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>,
/// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
stats: EngineStats,
@ -40,6 +42,8 @@ impl EngineConnection {
batch: Arc::new(RwLock::new(Vec::new())),
batch_end: Arc::new(RwLock::new(IndexMap::new())),
artifact_commands: Arc::new(RwLock::new(Vec::new())),
ids_of_async_commands: Arc::new(RwLock::new(IndexMap::new())),
responses: Arc::new(RwLock::new(IndexMap::new())),
default_planes: Default::default(),
stats: Default::default(),
})
@ -57,7 +61,7 @@ impl crate::engine::EngineManager for EngineConnection {
}
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
Arc::new(RwLock::new(IndexMap::new()))
self.responses.clone()
}
fn stats(&self) -> &EngineStats {
@ -68,6 +72,10 @@ impl crate::engine::EngineManager for EngineConnection {
self.artifact_commands.clone()
}
fn ids_of_async_commands(&self) -> Arc<RwLock<IndexMap<Uuid, SourceRange>>> {
self.ids_of_async_commands.clone()
}
fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>> {
self.default_planes.clone()
}
@ -80,6 +88,25 @@ impl crate::engine::EngineManager for EngineConnection {
Ok(())
}
async fn inner_fire_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: HashMap<Uuid, SourceRange>,
) -> Result<(), KclError> {
// Pop off the id we care about.
self.ids_of_async_commands.write().await.swap_remove(&id);
// Add the response to our responses.
let response = self
.inner_send_modeling_cmd(id, source_range, cmd, id_to_source_range)
.await?;
self.responses().write().await.insert(id, response);
Ok(())
}
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
@ -109,6 +136,20 @@ impl crate::engine::EngineManager for EngineConnection {
success: true,
}))
}
WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
cmd: ModelingCmd::ImportFiles(ImportFiles { .. }),
cmd_id,
}) => Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
request_id: Some(id),
resp: OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::ImportFiles(
kittycad_modeling_cmds::output::ImportFiles {
object_id: cmd_id.into(),
},
),
},
success: true,
})),
WebSocketRequest::ModelingCmdReq(_) => Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
request_id: Some(id),
resp: OkWebSocketResponseData::Modeling {

View File

@ -22,6 +22,15 @@ extern "C" {
#[derive(Debug, Clone)]
pub type EngineCommandManager;
#[wasm_bindgen(method, js_name = fireModelingCommandFromWasm, catch)]
fn fire_modeling_cmd_from_wasm(
this: &EngineCommandManager,
id: String,
rangeStr: String,
cmdStr: String,
idToRangeStr: String,
) -> Result<(), js_sys::Error>;
#[wasm_bindgen(method, js_name = sendModelingCommandFromWasm, catch)]
fn send_modeling_cmd_from_wasm(
this: &EngineCommandManager,
@ -38,33 +47,128 @@ extern "C" {
#[derive(Debug, Clone)]
pub struct EngineConnection {
manager: Arc<EngineCommandManager>,
response_context: Arc<ResponseContext>,
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>,
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
ids_of_async_commands: Arc<RwLock<IndexMap<Uuid, SourceRange>>>,
/// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
stats: EngineStats,
}
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct ResponseContext {
responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>,
}
#[wasm_bindgen]
impl ResponseContext {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
responses: Arc::new(RwLock::new(IndexMap::new())),
}
}
// Add a response to the context.
pub async fn send_response(&self, data: js_sys::Uint8Array) -> Result<(), JsValue> {
let ws_result: WebSocketResponse = match bson::from_slice(&data.to_vec()) {
Ok(res) => res,
Err(_) => {
// We don't care about the error if we can't parse it.
return Ok(());
}
};
let id = match &ws_result {
WebSocketResponse::Success(res) => res.request_id,
WebSocketResponse::Failure(res) => res.request_id,
};
let Some(id) = id else {
// We only care if we have an id.
return Ok(());
};
// Add this response to our responses.
self.add(id, ws_result.clone()).await;
Ok(())
}
}
impl ResponseContext {
pub async fn add(&self, id: Uuid, response: WebSocketResponse) {
self.responses.write().await.insert(id, response);
}
pub fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
self.responses.clone()
}
}
// Safety: WebAssembly will only ever run in a single-threaded context.
unsafe impl Send for EngineConnection {}
unsafe impl Sync for EngineConnection {}
impl EngineConnection {
pub async fn new(manager: EngineCommandManager) -> Result<EngineConnection, JsValue> {
pub async fn new(
manager: EngineCommandManager,
response_context: Arc<ResponseContext>,
) -> Result<EngineConnection, JsValue> {
#[allow(clippy::arc_with_non_send_sync)]
Ok(EngineConnection {
manager: Arc::new(manager),
batch: Arc::new(RwLock::new(Vec::new())),
batch_end: Arc::new(RwLock::new(IndexMap::new())),
responses: Arc::new(RwLock::new(IndexMap::new())),
response_context,
artifact_commands: Arc::new(RwLock::new(Vec::new())),
ids_of_async_commands: Arc::new(RwLock::new(IndexMap::new())),
default_planes: Default::default(),
stats: Default::default(),
})
}
async fn do_fire_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
) -> Result<(), KclError> {
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize source range: {:?}", e),
source_ranges: vec![source_range],
})
})?;
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize modeling command: {:?}", e),
source_ranges: vec![source_range],
})
})?;
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize id to source range: {:?}", e),
source_ranges: vec![source_range],
})
})?;
self.manager
.fire_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
Ok(())
}
async fn do_send_modeling_cmd(
&self,
id: uuid::Uuid,
@ -151,7 +255,7 @@ impl crate::engine::EngineManager for EngineConnection {
}
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
self.responses.clone()
self.response_context.responses.clone()
}
fn stats(&self) -> &EngineStats {
@ -162,6 +266,10 @@ impl crate::engine::EngineManager for EngineConnection {
self.artifact_commands.clone()
}
fn ids_of_async_commands(&self) -> Arc<RwLock<IndexMap<Uuid, SourceRange>>> {
self.ids_of_async_commands.clone()
}
fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>> {
self.default_planes.clone()
}
@ -193,6 +301,19 @@ impl crate::engine::EngineManager for EngineConnection {
Ok(())
}
async fn inner_fire_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: HashMap<Uuid, SourceRange>,
) -> Result<(), KclError> {
self.do_fire_modeling_cmd(id, source_range, cmd, id_to_source_range)
.await?;
Ok(())
}
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
@ -204,9 +325,7 @@ impl crate::engine::EngineManager for EngineConnection {
.do_send_modeling_cmd(id, source_range, cmd, id_to_source_range)
.await?;
let mut responses = self.responses.write().await;
responses.insert(id, ws_result.clone());
drop(responses);
self.response_context.add(id, ws_result.clone()).await;
Ok(ws_result)
}

View File

@ -76,6 +76,9 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
/// Get the artifact commands that have accumulated so far.
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>>;
/// Get the ids of the async commands we are waiting for.
fn ids_of_async_commands(&self) -> Arc<RwLock<IndexMap<Uuid, SourceRange>>>;
/// Take the batch of commands that have accumulated so far and clear them.
async fn take_batch(&self) -> Vec<(WebSocketRequest, SourceRange)> {
std::mem::take(&mut *self.batch().write().await)
@ -96,6 +99,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
std::mem::take(&mut *self.artifact_commands().write().await)
}
/// Take the ids of async commands that have accumulated so far and clear them.
async fn take_ids_of_async_commands(&self) -> IndexMap<Uuid, SourceRange> {
std::mem::take(&mut *self.ids_of_async_commands().write().await)
}
/// Take the responses that have accumulated so far and clear them.
async fn take_responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
std::mem::take(&mut *self.responses().write().await)
@ -136,8 +144,18 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn clear_queues(&self) {
self.batch().write().await.clear();
self.batch_end().write().await.clear();
self.ids_of_async_commands().write().await.clear();
}
/// Send a modeling command and do not wait for the response message.
async fn inner_fire_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: HashMap<Uuid, SourceRange>,
) -> Result<(), crate::errors::KclError>;
/// Send a modeling command and wait for the response message.
async fn inner_send_modeling_cmd(
&self,
@ -180,6 +198,68 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
Ok(())
}
/// Ensure a specific async command has been completed.
async fn ensure_async_command_completed(
&self,
id: uuid::Uuid,
source_range: Option<SourceRange>,
) -> Result<OkWebSocketResponseData, KclError> {
let source_range = if let Some(source_range) = source_range {
source_range
} else {
// Look it up if we don't have it.
self.ids_of_async_commands()
.read()
.await
.get(&id)
.cloned()
.unwrap_or_default()
};
let current_time = instant::Instant::now();
while current_time.elapsed().as_secs() < 60 {
let responses = self.responses().read().await.clone();
let Some(resp) = responses.get(&id) else {
// Sleep for a little so we don't hog the CPU.
// No seriously WE DO NOT WANT TO PAUSE THE WHOLE APP ON THE JS SIDE.
let duration = instant::Duration::from_millis(100);
#[cfg(target_arch = "wasm32")]
wasm_timer::Delay::new(duration).await.map_err(|err| {
KclError::Internal(KclErrorDetails {
message: format!("Failed to sleep: {:?}", err),
source_ranges: vec![source_range],
})
})?;
#[cfg(not(target_arch = "wasm32"))]
tokio::time::sleep(duration).await;
continue;
};
// If the response is an error, return it.
// Parsing will do that and we can ignore the result, we don't care.
let response = self.parse_websocket_response(resp.clone(), source_range)?;
return Ok(response);
}
Err(KclError::Engine(KclErrorDetails {
message: "async command timed out".to_string(),
source_ranges: vec![source_range],
}))
}
/// Ensure ALL async commands have been completed.
async fn ensure_async_commands_completed(&self) -> Result<(), KclError> {
// Check if all async commands have been completed.
let ids = self.take_ids_of_async_commands().await;
// Try to get them from the responses.
for (id, source_range) in ids {
self.ensure_async_command_completed(id, Some(source_range)).await?;
}
Ok(())
}
/// Set the visibility of edges.
async fn set_edge_visibility(
&self,
@ -342,6 +422,36 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
self.run_batch(requests, source_range).await
}
/// Send the modeling cmd async and don't wait for the response.
/// Add it to our list of async commands.
async fn async_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: SourceRange,
cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> {
// Add the command ID to the list of async commands.
self.ids_of_async_commands().write().await.insert(id, source_range);
// Add to artifact commands.
self.handle_artifact_command(cmd, id.into(), &HashMap::from([(id, source_range)]))
.await?;
// Fire off the command now, but don't wait for the response, we don't care about it.
self.inner_fire_modeling_cmd(
id,
source_range,
WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
cmd: cmd.clone(),
cmd_id: id.into(),
}),
HashMap::from([(id, source_range)]),
)
.await?;
Ok(())
}
/// Run the batch for the specific commands.
async fn run_batch(
&self,

View File

@ -15,6 +15,8 @@ use crate::{
std::{args::TyF64, sketch::PlaneData},
};
use super::ExecutorContext;
type Point2D = kcmc::shared::Point2d<f64>;
type Point3D = kcmc::shared::Point3d<f64>;
@ -76,9 +78,45 @@ pub struct ImportedGeometry {
pub value: Vec<String>,
#[serde(skip)]
pub meta: Vec<Metadata>,
/// If the imported geometry has completed.
#[serde(skip)]
completed: bool,
}
/// Data for a solid or an imported geometry.
impl ImportedGeometry {
pub fn new(id: uuid::Uuid, value: Vec<String>, meta: Vec<Metadata>) -> Self {
Self {
id,
value,
meta,
completed: false,
}
}
async fn wait_for_finish(&mut self, ctx: &ExecutorContext) -> Result<(), KclError> {
if self.completed {
return Ok(());
}
ctx.engine
.ensure_async_command_completed(self.id, self.meta.first().map(|m| m.source_range))
.await?;
self.completed = true;
Ok(())
}
pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
if !self.completed {
self.wait_for_finish(ctx).await?;
}
Ok(self.id)
}
}
/// Data for a solid, sketch, or an imported geometry.
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
@ -128,11 +166,61 @@ impl From<SolidOrSketchOrImportedGeometry> for crate::execution::KclValue {
}
impl SolidOrSketchOrImportedGeometry {
pub(crate) fn ids(&self) -> Vec<uuid::Uuid> {
pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
match self {
SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => vec![s.id],
SolidOrSketchOrImportedGeometry::SolidSet(s) => s.iter().map(|s| s.id).collect(),
SolidOrSketchOrImportedGeometry::SketchSet(s) => s.iter().map(|s| s.id).collect(),
SolidOrSketchOrImportedGeometry::ImportedGeometry(s) => {
let id = s.id(ctx).await?;
Ok(vec![id])
}
SolidOrSketchOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
SolidOrSketchOrImportedGeometry::SketchSet(s) => Ok(s.iter().map(|s| s.id).collect()),
}
}
}
/// Data for a solid or an imported geometry.
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
#[allow(clippy::vec_box)]
pub enum SolidOrImportedGeometry {
ImportedGeometry(Box<ImportedGeometry>),
SolidSet(Vec<Solid>),
}
impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
fn from(value: SolidOrImportedGeometry) -> Self {
match value {
SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
SolidOrImportedGeometry::SolidSet(mut s) => {
if s.len() == 1 {
crate::execution::KclValue::Solid {
value: Box::new(s.pop().unwrap()),
}
} else {
crate::execution::KclValue::HomArray {
value: s
.into_iter()
.map(|s| crate::execution::KclValue::Solid { value: Box::new(s) })
.collect(),
ty: crate::execution::types::RuntimeType::solid(),
}
}
}
}
}
}
impl SolidOrImportedGeometry {
pub(crate) async fn ids(&mut self, ctx: &ExecutorContext) -> Result<Vec<uuid::Uuid>, KclError> {
match self {
SolidOrImportedGeometry::ImportedGeometry(s) => {
let id = s.id(ctx).await?;
Ok(vec![id])
}
SolidOrImportedGeometry::SolidSet(s) => Ok(s.iter().map(|s| s.id).collect()),
}
}
}

View File

@ -5,10 +5,8 @@ use kcmc::{
coord::{System, KITTYCAD},
each_cmd as mcmd,
format::InputFormat3d,
ok_response::OkModelingCmdResponse,
shared::FileImportFormat,
units::UnitLength,
websocket::OkWebSocketResponseData,
ImportFile, ModelingCmd,
};
use kittycad_modeling_cmds as kcmc;
@ -289,34 +287,17 @@ pub struct PreImportedGeometry {
}
pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) -> Result<ImportedGeometry, KclError> {
if ctxt.no_engine_commands().await {
return Ok(ImportedGeometry {
id: pre.id,
value: pre.command.files.iter().map(|f| f.path.to_string()).collect(),
meta: vec![pre.source_range.into()],
});
}
let imported_geometry = ImportedGeometry::new(
pre.id,
pre.command.files.iter().map(|f| f.path.to_string()).collect(),
vec![pre.source_range.into()],
);
let resp = ctxt
.engine
.send_modeling_cmd(pre.id, pre.source_range, &ModelingCmd::from(pre.command.clone()))
ctxt.engine
.async_modeling_cmd(pre.id, pre.source_range, &ModelingCmd::from(pre.command.clone()))
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::ImportFiles(imported_files),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("ImportFiles response was not as expected: {:?}", resp),
source_ranges: vec![pre.source_range],
}));
};
Ok(ImportedGeometry {
id: imported_files.object_id,
value: pre.command.files.iter().map(|f| f.path.to_string()).collect(),
meta: vec![pre.source_range.into()],
})
Ok(imported_geometry)
}
/// Get the source format from the extension.

View File

@ -947,7 +947,7 @@ impl ExecutorContext {
exec_state.global.artifact_graph.clone(),
module_id_to_module_path,
exec_state.global.id_to_source.clone(),
default_planes,
default_planes.clone(),
)
})?;
@ -957,6 +957,7 @@ impl ExecutorContext {
cache::write_old_memory((mem, exec_state.global.module_infos.clone())).await;
}
let session_data = self.engine.get_session_data().await;
Ok((env_ref, session_data))
}
@ -984,6 +985,9 @@ impl ExecutorContext {
)
.await;
// Ensure all the async commands completed.
self.engine.ensure_async_commands_completed().await?;
// If we errored out and early-returned, there might be commands which haven't been executed
// and should be dropped.
self.engine.clear_queues().await;

View File

@ -1338,11 +1338,11 @@ mod test {
value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state)),
},
// No easy way to make a Face, Sketch, Solid, or Helix
KclValue::ImportedGeometry(crate::execution::ImportedGeometry {
id: uuid::Uuid::nil(),
value: Vec::new(),
meta: Vec::new(),
}),
KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
uuid::Uuid::nil(),
Vec::new(),
Vec::new(),
)),
// Other values don't have types
]
}

View File

@ -109,7 +109,7 @@ pub mod exec {
pub mod wasm_engine {
pub use crate::{
coredump::wasm::{CoreDumpManager, CoreDumper},
engine::conn_wasm::{EngineCommandManager, EngineConnection},
engine::conn_wasm::{EngineCommandManager, EngineConnection, ResponseContext},
fs::wasm::{FileManager, FileSystemManager},
};
}

View File

@ -2579,3 +2579,24 @@ mod tangent_to_3_point_arc {
super::execute(TEST_NAME, true).await
}
}
mod import_async {
const TEST_NAME: &str = "import_async";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}

View File

@ -13,7 +13,7 @@ use crate::{
errors::{KclError, KclErrorDetails},
execution::{
types::{NumericType, PrimitiveType, RuntimeType},
ExecState, KclValue, Solid,
ExecState, KclValue, SolidOrImportedGeometry,
},
std::Args,
};
@ -43,7 +43,11 @@ struct AppearanceData {
/// Set the appearance of a solid. This only works on solids, not sketches or individual paths.
pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
let solids = args.get_unlabeled_kw_arg_typed(
"solids",
&RuntimeType::Union(vec![RuntimeType::solids(), RuntimeType::imported()]),
exec_state,
)?;
let color: String = args.get_kw_arg("color")?;
let count_ty = RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()));
@ -270,6 +274,19 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
/// roughness = 50
/// )
/// ```
///
/// ```no_run
/// // Change the appearance of an imported model.
///
/// import "tests/inputs/cube.sldprt" as cube
///
/// cube
/// // |> appearance(
/// // color = "#ff0000",
/// // metalness = 50,
/// // roughness = 50
/// // )
/// ```
#[stdlib {
name = "appearance",
keywords = true,
@ -282,14 +299,16 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
}
}]
async fn inner_appearance(
solids: Vec<Solid>,
solids: SolidOrImportedGeometry,
color: String,
metalness: Option<f64>,
roughness: Option<f64>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Solid>, KclError> {
for solid in &solids {
) -> Result<SolidOrImportedGeometry, KclError> {
let mut solids = solids.clone();
for solid_id in solids.ids(&args.ctx).await? {
// Set the material properties.
let rgb = rgba_simple::RGB::<f32>::from_hex(&color).map_err(|err| {
KclError::Semantic(KclErrorDetails {
@ -308,7 +327,7 @@ async fn inner_appearance(
args.batch_modeling_cmd(
exec_state.next_uuid(),
ModelingCmd::from(mcmd::ObjectSetMaterialParamsPbr {
object_id: solid.id,
object_id: solid_id,
color,
metalness: metalness.unwrap_or_default() as f32 / 100.0,
roughness: roughness.unwrap_or_default() as f32 / 100.0,

View File

@ -28,6 +28,9 @@ use crate::{
ModuleId,
};
const ERROR_STRING_SKETCH_TO_SOLID_HELPER: &str =
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`";
#[derive(Debug, Clone)]
pub struct Arg {
/// The evaluated argument.
@ -220,18 +223,19 @@ impl Args {
ty.human_friendly_type(),
);
let suggestion = match (ty, actual_type_name) {
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
),
(RuntimeType::Array(t, _), "Sketch") if **t == RuntimeType::Primitive(PrimitiveType::Solid) => Some(
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
),
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER),
(RuntimeType::Array(t, _), "Sketch") if **t == RuntimeType::Primitive(PrimitiveType::Solid) => {
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
_ => None,
};
let message = match suggestion {
let mut message = match suggestion {
None => msg_base,
Some(sugg) => format!("{msg_base}. {sugg}"),
};
if message.contains("one or more Solids or imported geometry but it's actually of type Sketch") {
message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
}
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message,
@ -343,18 +347,20 @@ impl Args {
ty.human_friendly_type(),
);
let suggestion = match (ty, actual_type_name) {
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
),
(RuntimeType::Array(ty, _), "Sketch") if **ty == RuntimeType::Primitive(PrimitiveType::Solid) => Some(
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",
),
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER),
(RuntimeType::Array(ty, _), "Sketch") if **ty == RuntimeType::Primitive(PrimitiveType::Solid) => {
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
_ => None,
};
let message = match suggestion {
let mut message = match suggestion {
None => msg_base,
Some(sugg) => format!("{msg_base}. {sugg}"),
};
if message.contains("one or more Solids or imported geometry but it's actually of type Sketch") {
message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
}
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message,
@ -1396,6 +1402,26 @@ impl<'a> FromKclValue<'a> for crate::execution::SolidOrSketchOrImportedGeometry
}
}
impl<'a> FromKclValue<'a> for crate::execution::SolidOrImportedGeometry {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Solid { value } => Some(Self::SolidSet(vec![(**value).clone()])),
KclValue::HomArray { value, .. } => {
let mut solids = vec![];
for item in value {
match item {
KclValue::Solid { value } => solids.push((**value).clone()),
_ => return None,
}
}
Some(Self::SolidSet(solids))
}
KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for super::sketch::SketchData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
// Order is critical since PlaneData is a subset of Plane.

View File

@ -171,7 +171,8 @@ async fn inner_scale(
args.flush_batch_for_solids(exec_state, solids).await?;
}
for object_id in objects.ids() {
let mut objects = objects.clone();
for object_id in objects.ids(&args.ctx).await? {
let id = exec_state.next_uuid();
args.batch_modeling_cmd(
@ -409,7 +410,8 @@ async fn inner_translate(
args.flush_batch_for_solids(exec_state, solids).await?;
}
for object_id in objects.ids() {
let mut objects = objects.clone();
for object_id in objects.ids(&args.ctx).await? {
let id = exec_state.next_uuid();
args.batch_modeling_cmd(
@ -774,7 +776,8 @@ async fn inner_rotate(
args.flush_batch_for_solids(exec_state, solids).await?;
}
for object_id in objects.ids() {
let mut objects = objects.clone();
for object_id in objects.ids(&args.ctx).await? {
let id = exec_state.next_uuid();
if let (Some(axis), Some(angle)) = (axis, angle) {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact graph flowchart import_async.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,503 @@
```mermaid
flowchart LR
subgraph path3 [Path]
3["Path<br>[1035, 1085, 0]"]
4["Segment<br>[1035, 1085, 0]"]
5[Solid2d]
end
subgraph path13 [Path]
13["Path<br>[1562, 1599, 0]"]
14["Segment<br>[1250, 1288, 0]"]
15["Segment<br>[1250, 1288, 0]"]
16["Segment<br>[1250, 1288, 0]"]
17["Segment<br>[1250, 1288, 0]"]
18["Segment<br>[1250, 1288, 0]"]
19["Segment<br>[1250, 1288, 0]"]
20["Segment<br>[1250, 1288, 0]"]
21["Segment<br>[1250, 1288, 0]"]
22["Segment<br>[1250, 1288, 0]"]
23["Segment<br>[1250, 1288, 0]"]
24["Segment<br>[1250, 1288, 0]"]
25["Segment<br>[1250, 1288, 0]"]
26["Segment<br>[1250, 1288, 0]"]
27["Segment<br>[1250, 1288, 0]"]
28["Segment<br>[1250, 1288, 0]"]
29["Segment<br>[1250, 1288, 0]"]
30["Segment<br>[1250, 1288, 0]"]
31["Segment<br>[1250, 1288, 0]"]
32["Segment<br>[1250, 1288, 0]"]
33["Segment<br>[1250, 1288, 0]"]
34["Segment<br>[1250, 1288, 0]"]
35["Segment<br>[1250, 1288, 0]"]
36["Segment<br>[1250, 1288, 0]"]
37["Segment<br>[1250, 1288, 0]"]
38["Segment<br>[1250, 1288, 0]"]
39["Segment<br>[1250, 1288, 0]"]
40["Segment<br>[1250, 1288, 0]"]
41["Segment<br>[1250, 1288, 0]"]
42["Segment<br>[1250, 1288, 0]"]
43["Segment<br>[1250, 1288, 0]"]
44["Segment<br>[1250, 1288, 0]"]
45["Segment<br>[1250, 1288, 0]"]
46["Segment<br>[1250, 1288, 0]"]
47["Segment<br>[1250, 1288, 0]"]
48["Segment<br>[1250, 1288, 0]"]
49["Segment<br>[1250, 1288, 0]"]
50["Segment<br>[1250, 1288, 0]"]
51["Segment<br>[1250, 1288, 0]"]
52["Segment<br>[1250, 1288, 0]"]
53["Segment<br>[1250, 1288, 0]"]
54["Segment<br>[1250, 1288, 0]"]
55["Segment<br>[1250, 1288, 0]"]
56["Segment<br>[1250, 1288, 0]"]
57["Segment<br>[1250, 1288, 0]"]
58["Segment<br>[1250, 1288, 0]"]
59["Segment<br>[1250, 1288, 0]"]
60["Segment<br>[1250, 1288, 0]"]
61["Segment<br>[1250, 1288, 0]"]
62["Segment<br>[1250, 1288, 0]"]
63["Segment<br>[1250, 1288, 0]"]
64["Segment<br>[1250, 1288, 0]"]
65["Segment<br>[1250, 1288, 0]"]
66["Segment<br>[1250, 1288, 0]"]
67["Segment<br>[1250, 1288, 0]"]
68["Segment<br>[1250, 1288, 0]"]
69["Segment<br>[1250, 1288, 0]"]
70["Segment<br>[1250, 1288, 0]"]
71["Segment<br>[1250, 1288, 0]"]
72["Segment<br>[1250, 1288, 0]"]
73["Segment<br>[1250, 1288, 0]"]
74["Segment<br>[1250, 1288, 0]"]
75["Segment<br>[1250, 1288, 0]"]
76["Segment<br>[1250, 1288, 0]"]
77["Segment<br>[1250, 1288, 0]"]
78["Segment<br>[1250, 1288, 0]"]
79["Segment<br>[1250, 1288, 0]"]
80["Segment<br>[1250, 1288, 0]"]
81["Segment<br>[1250, 1288, 0]"]
82["Segment<br>[1250, 1288, 0]"]
83["Segment<br>[1250, 1288, 0]"]
84["Segment<br>[1250, 1288, 0]"]
85["Segment<br>[1250, 1288, 0]"]
86["Segment<br>[1250, 1288, 0]"]
87["Segment<br>[1250, 1288, 0]"]
88["Segment<br>[1250, 1288, 0]"]
89["Segment<br>[1250, 1288, 0]"]
90["Segment<br>[1250, 1288, 0]"]
91["Segment<br>[1250, 1288, 0]"]
92["Segment<br>[1250, 1288, 0]"]
93["Segment<br>[1250, 1288, 0]"]
94["Segment<br>[1250, 1288, 0]"]
95["Segment<br>[1250, 1288, 0]"]
96["Segment<br>[1250, 1288, 0]"]
97["Segment<br>[1250, 1288, 0]"]
98["Segment<br>[1250, 1288, 0]"]
99["Segment<br>[1250, 1288, 0]"]
100["Segment<br>[1250, 1288, 0]"]
101["Segment<br>[1250, 1288, 0]"]
102["Segment<br>[1250, 1288, 0]"]
103["Segment<br>[1250, 1288, 0]"]
104["Segment<br>[1250, 1288, 0]"]
105["Segment<br>[1250, 1288, 0]"]
106["Segment<br>[1250, 1288, 0]"]
107["Segment<br>[1250, 1288, 0]"]
108["Segment<br>[1250, 1288, 0]"]
109["Segment<br>[1250, 1288, 0]"]
110["Segment<br>[1250, 1288, 0]"]
111["Segment<br>[1250, 1288, 0]"]
112["Segment<br>[1250, 1288, 0]"]
113["Segment<br>[1250, 1288, 0]"]
114["Segment<br>[1250, 1288, 0]"]
115["Segment<br>[1651, 1753, 0]"]
116["Segment<br>[1478, 1508, 0]"]
117["Segment<br>[1478, 1508, 0]"]
118["Segment<br>[1478, 1508, 0]"]
119["Segment<br>[1478, 1508, 0]"]
120["Segment<br>[1478, 1508, 0]"]
121["Segment<br>[1478, 1508, 0]"]
122["Segment<br>[1478, 1508, 0]"]
123["Segment<br>[1478, 1508, 0]"]
124["Segment<br>[1478, 1508, 0]"]
125["Segment<br>[1478, 1508, 0]"]
126["Segment<br>[1478, 1508, 0]"]
127["Segment<br>[1478, 1508, 0]"]
128["Segment<br>[1478, 1508, 0]"]
129["Segment<br>[1478, 1508, 0]"]
130["Segment<br>[1478, 1508, 0]"]
131["Segment<br>[1478, 1508, 0]"]
132["Segment<br>[1478, 1508, 0]"]
133["Segment<br>[1478, 1508, 0]"]
134["Segment<br>[1478, 1508, 0]"]
135["Segment<br>[1478, 1508, 0]"]
136["Segment<br>[1478, 1508, 0]"]
137["Segment<br>[1478, 1508, 0]"]
138["Segment<br>[1478, 1508, 0]"]
139["Segment<br>[1478, 1508, 0]"]
140["Segment<br>[1478, 1508, 0]"]
141["Segment<br>[1478, 1508, 0]"]
142["Segment<br>[1478, 1508, 0]"]
143["Segment<br>[1478, 1508, 0]"]
144["Segment<br>[1478, 1508, 0]"]
145["Segment<br>[1478, 1508, 0]"]
146["Segment<br>[1478, 1508, 0]"]
147["Segment<br>[1478, 1508, 0]"]
148["Segment<br>[1478, 1508, 0]"]
149["Segment<br>[1478, 1508, 0]"]
150["Segment<br>[1478, 1508, 0]"]
151["Segment<br>[1478, 1508, 0]"]
152["Segment<br>[1478, 1508, 0]"]
153["Segment<br>[1478, 1508, 0]"]
154["Segment<br>[1478, 1508, 0]"]
155["Segment<br>[1478, 1508, 0]"]
156["Segment<br>[1478, 1508, 0]"]
157["Segment<br>[1478, 1508, 0]"]
158["Segment<br>[1478, 1508, 0]"]
159["Segment<br>[1478, 1508, 0]"]
160["Segment<br>[1478, 1508, 0]"]
161["Segment<br>[1478, 1508, 0]"]
162["Segment<br>[1478, 1508, 0]"]
163["Segment<br>[1478, 1508, 0]"]
164["Segment<br>[1478, 1508, 0]"]
165["Segment<br>[1478, 1508, 0]"]
166["Segment<br>[1478, 1508, 0]"]
167["Segment<br>[1478, 1508, 0]"]
168["Segment<br>[1478, 1508, 0]"]
169["Segment<br>[1478, 1508, 0]"]
170["Segment<br>[1478, 1508, 0]"]
171["Segment<br>[1478, 1508, 0]"]
172["Segment<br>[1478, 1508, 0]"]
173["Segment<br>[1478, 1508, 0]"]
174["Segment<br>[1478, 1508, 0]"]
175["Segment<br>[1478, 1508, 0]"]
176["Segment<br>[1478, 1508, 0]"]
177["Segment<br>[1478, 1508, 0]"]
178["Segment<br>[1478, 1508, 0]"]
179["Segment<br>[1478, 1508, 0]"]
180["Segment<br>[1478, 1508, 0]"]
181["Segment<br>[1478, 1508, 0]"]
182["Segment<br>[1478, 1508, 0]"]
183["Segment<br>[1478, 1508, 0]"]
184["Segment<br>[1478, 1508, 0]"]
185["Segment<br>[1478, 1508, 0]"]
186["Segment<br>[1478, 1508, 0]"]
187["Segment<br>[1478, 1508, 0]"]
188["Segment<br>[1478, 1508, 0]"]
189["Segment<br>[1478, 1508, 0]"]
190["Segment<br>[1478, 1508, 0]"]
191["Segment<br>[1478, 1508, 0]"]
192["Segment<br>[1478, 1508, 0]"]
193["Segment<br>[1478, 1508, 0]"]
194["Segment<br>[1478, 1508, 0]"]
195["Segment<br>[1478, 1508, 0]"]
196["Segment<br>[1478, 1508, 0]"]
197["Segment<br>[1478, 1508, 0]"]
198["Segment<br>[1478, 1508, 0]"]
199["Segment<br>[1478, 1508, 0]"]
200["Segment<br>[1478, 1508, 0]"]
201["Segment<br>[1478, 1508, 0]"]
202["Segment<br>[1478, 1508, 0]"]
203["Segment<br>[1478, 1508, 0]"]
204["Segment<br>[1478, 1508, 0]"]
205["Segment<br>[1478, 1508, 0]"]
206["Segment<br>[1478, 1508, 0]"]
207["Segment<br>[1478, 1508, 0]"]
208["Segment<br>[1478, 1508, 0]"]
209["Segment<br>[1478, 1508, 0]"]
210["Segment<br>[1478, 1508, 0]"]
211["Segment<br>[1478, 1508, 0]"]
212["Segment<br>[1478, 1508, 0]"]
213["Segment<br>[1478, 1508, 0]"]
214["Segment<br>[1478, 1508, 0]"]
215["Segment<br>[1478, 1508, 0]"]
216["Segment<br>[1478, 1508, 0]"]
217["Segment<br>[1799, 1806, 0]"]
218[Solid2d]
end
subgraph path220 [Path]
220["Path<br>[2287, 2387, 0]"]
221["Segment<br>[2393, 2420, 0]"]
222["Segment<br>[2426, 2454, 0]"]
223["Segment<br>[2460, 2488, 0]"]
224["Segment<br>[2494, 2614, 0]"]
225["Segment<br>[2620, 2729, 0]"]
226["Segment<br>[2735, 2742, 0]"]
227[Solid2d]
end
1["Plane<br>[168, 185, 0]"]
2["Plane<br>[1012, 1029, 0]"]
6["Sweep Extrusion<br>[1091, 1119, 0]"]
7[Wall]
8["Cap Start"]
9["Cap End"]
10["SweepEdge Opposite"]
11["SweepEdge Adjacent"]
12["Plane<br>[1539, 1556, 0]"]
219["Sweep Extrusion<br>[1812, 1840, 0]"]
228["Sweep Extrusion<br>[2748, 2777, 0]"]
229[Wall]
230[Wall]
231[Wall]
232[Wall]
233["SweepEdge Opposite"]
234["SweepEdge Adjacent"]
235["SweepEdge Opposite"]
236["SweepEdge Adjacent"]
237["SweepEdge Opposite"]
238["SweepEdge Adjacent"]
239["SweepEdge Opposite"]
240["SweepEdge Adjacent"]
241["StartSketchOnFace<br>[2250, 2281, 0]"]
2 --- 3
3 --- 4
3 ---- 6
3 --- 5
4 --- 7
4 --- 10
4 --- 11
6 --- 7
6 --- 8
6 --- 9
6 --- 10
6 --- 11
9 --- 220
12 --- 13
13 --- 14
13 --- 15
13 --- 16
13 --- 17
13 --- 18
13 --- 19
13 --- 20
13 --- 21
13 --- 22
13 --- 23
13 --- 24
13 --- 25
13 --- 26
13 --- 27
13 --- 28
13 --- 29
13 --- 30
13 --- 31
13 --- 32
13 --- 33
13 --- 34
13 --- 35
13 --- 36
13 --- 37
13 --- 38
13 --- 39
13 --- 40
13 --- 41
13 --- 42
13 --- 43
13 --- 44
13 --- 45
13 --- 46
13 --- 47
13 --- 48
13 --- 49
13 --- 50
13 --- 51
13 --- 52
13 --- 53
13 --- 54
13 --- 55
13 --- 56
13 --- 57
13 --- 58
13 --- 59
13 --- 60
13 --- 61
13 --- 62
13 --- 63
13 --- 64
13 --- 65
13 --- 66
13 --- 67
13 --- 68
13 --- 69
13 --- 70
13 --- 71
13 --- 72
13 --- 73
13 --- 74
13 --- 75
13 --- 76
13 --- 77
13 --- 78
13 --- 79
13 --- 80
13 --- 81
13 --- 82
13 --- 83
13 --- 84
13 --- 85
13 --- 86
13 --- 87
13 --- 88
13 --- 89
13 --- 90
13 --- 91
13 --- 92
13 --- 93
13 --- 94
13 --- 95
13 --- 96
13 --- 97
13 --- 98
13 --- 99
13 --- 100
13 --- 101
13 --- 102
13 --- 103
13 --- 104
13 --- 105
13 --- 106
13 --- 107
13 --- 108
13 --- 109
13 --- 110
13 --- 111
13 --- 112
13 --- 113
13 --- 114
13 --- 115
13 --- 116
13 --- 117
13 --- 118
13 --- 119
13 --- 120
13 --- 121
13 --- 122
13 --- 123
13 --- 124
13 --- 125
13 --- 126
13 --- 127
13 --- 128
13 --- 129
13 --- 130
13 --- 131
13 --- 132
13 --- 133
13 --- 134
13 --- 135
13 --- 136
13 --- 137
13 --- 138
13 --- 139
13 --- 140
13 --- 141
13 --- 142
13 --- 143
13 --- 144
13 --- 145
13 --- 146
13 --- 147
13 --- 148
13 --- 149
13 --- 150
13 --- 151
13 --- 152
13 --- 153
13 --- 154
13 --- 155
13 --- 156
13 --- 157
13 --- 158
13 --- 159
13 --- 160
13 --- 161
13 --- 162
13 --- 163
13 --- 164
13 --- 165
13 --- 166
13 --- 167
13 --- 168
13 --- 169
13 --- 170
13 --- 171
13 --- 172
13 --- 173
13 --- 174
13 --- 175
13 --- 176
13 --- 177
13 --- 178
13 --- 179
13 --- 180
13 --- 181
13 --- 182
13 --- 183
13 --- 184
13 --- 185
13 --- 186
13 --- 187
13 --- 188
13 --- 189
13 --- 190
13 --- 191
13 --- 192
13 --- 193
13 --- 194
13 --- 195
13 --- 196
13 --- 197
13 --- 198
13 --- 199
13 --- 200
13 --- 201
13 --- 202
13 --- 203
13 --- 204
13 --- 205
13 --- 206
13 --- 207
13 --- 208
13 --- 209
13 --- 210
13 --- 211
13 --- 212
13 --- 213
13 --- 214
13 --- 215
13 --- 216
13 --- 217
13 ---- 219
13 --- 218
220 --- 221
220 --- 222
220 --- 223
220 --- 224
220 --- 225
220 --- 226
220 ---- 228
220 --- 227
221 --- 232
221 --- 239
221 --- 240
222 --- 231
222 --- 237
222 --- 238
223 --- 230
223 --- 235
223 --- 236
225 --- 229
225 --- 233
225 --- 234
228 --- 229
228 --- 230
228 --- 231
228 --- 232
228 --- 233
228 --- 234
228 --- 235
228 --- 236
228 --- 237
228 --- 238
228 --- 239
228 --- 240
9 <--x 241
```

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,117 @@
@(lengthUnit = m)
import "../../e2e/executor/inputs/2-5-long-m8-chc-screw.stl" as screw
// Set units
@settings(defaultLengthUnit = mm)
myScrew = screw
surface001 = startSketchOn(XY)
// Define parameters
nTeeth = 21
module = 0.5
pitchDiameter = module * nTeeth
pressureAngle = 20
addendum = module
deddendum = 1.25 * module
baseDiameter = pitchDiameter * cos(toRadians(pressureAngle))
tipDiameter = pitchDiameter + 2 * module
gearHeight = 3
// Interpolate points along the involute curve
cmo = 101
rs = map([0..cmo], fn(i) {
return baseDiameter / 2 + i / cmo * (tipDiameter - baseDiameter) / 2
})
// Calculate operating pressure angle
angles = map(rs, fn(r) {
return toDegrees( acos(baseDiameter / 2 / r))
})
// Calculate the involute function
invas = map(angles, fn(a) {
return tan(toRadians(a)) - toRadians(a)
})
// Map the involute curve
xs = map([0..cmo], fn(i) {
return rs[i] * cos(invas[i])
})
ys = map([0..cmo], fn(i) {
return rs[i] * sin(invas[i])
})
// Extrude the gear body
body = startSketchOn(XY)
|> circle(center = [0, 0], radius = baseDiameter / 2)
|> extrude(length = gearHeight)
toothAngle = 360 / nTeeth / 1.5
// Plot the involute curve
fn leftInvolute(i, sg) {
j = 100 - i // iterate backwards
return line(sg, endAbsolute = [xs[j], ys[j]])
}
fn rightInvolute(i, sg) {
x = rs[i] * cos(toRadians(-toothAngle + toDegrees(atan(ys[i] / xs[i]))))
y = -rs[i] * sin(toRadians(-toothAngle + toDegrees(atan(ys[i] / xs[i]))))
return line(sg, endAbsolute = [x, y])
}
// Draw gear teeth
start = startSketchOn(XY)
|> startProfileAt([xs[101], ys[101]], %)
teeth = reduce([0..100], start, leftInvolute)
|> arc({
angleStart = 0,
angleEnd = toothAngle,
radius = baseDiameter / 2
}, %)
|> reduce([1..101], %, rightInvolute)
|> close()
|> extrude(length = gearHeight)
|> patternCircular3d(
axis = [0, 0, 1],
center = [0, 0, 0],
instances = nTeeth,
arcDegrees = 360,
rotateDuplicates = true,
)
// Define the constants of the keyway and the bore hole
keywayWidth = 0.250
keywayDepth = keywayWidth / 2
holeDiam = 2
holeRadius = 1
startAngle = asin(keywayWidth / 2 / holeRadius)
// Sketch the keyway and center hole and extrude
keyWay = startSketchOn(body, face = END)
|> startProfileAt([
holeRadius * cos(startAngle),
holeRadius * sin(startAngle)
], %)
|> xLine(length = keywayDepth)
|> yLine(length = -keywayWidth)
|> xLine(length = -keywayDepth)
|> arc({
angleEnd = 180,
angleStart = -1 * toDegrees(startAngle) + 360,
radius = holeRadius
}, %)
|> arc({
angleEnd = toDegrees(startAngle),
angleStart = 180,
radius = holeRadius
}, %)
|> close()
|> extrude(length = -gearHeight)
myScrew
|> translate(y=10)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -0,0 +1,120 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing import_async.kcl
---
// Set units
@settings(defaultLengthUnit = mm)
@(lengthUnit = m)
import "../../e2e/executor/inputs/2-5-long-m8-chc-screw.stl" as screw
myScrew = screw
surface001 = startSketchOn(XY)
// Define parameters
nTeeth = 21
module = 0.5
pitchDiameter = module * nTeeth
pressureAngle = 20
addendum = module
deddendum = 1.25 * module
baseDiameter = pitchDiameter * cos(toRadians(pressureAngle))
tipDiameter = pitchDiameter + 2 * module
gearHeight = 3
// Interpolate points along the involute curve
cmo = 101
rs = map([0..cmo], fn(i) {
return baseDiameter / 2 + i / cmo * (tipDiameter - baseDiameter) / 2
})
// Calculate operating pressure angle
angles = map(rs, fn(r) {
return toDegrees( acos(baseDiameter / 2 / r))
})
// Calculate the involute function
invas = map(angles, fn(a) {
return tan(toRadians(a)) - toRadians(a)
})
// Map the involute curve
xs = map([0..cmo], fn(i) {
return rs[i] * cos(invas[i])
})
ys = map([0..cmo], fn(i) {
return rs[i] * sin(invas[i])
})
// Extrude the gear body
body = startSketchOn(XY)
|> circle(center = [0, 0], radius = baseDiameter / 2)
|> extrude(length = gearHeight)
toothAngle = 360 / nTeeth / 1.5
// Plot the involute curve
fn leftInvolute(i, sg) {
j = 100 - i // iterate backwards
return line(sg, endAbsolute = [xs[j], ys[j]])
}
fn rightInvolute(i, sg) {
x = rs[i] * cos(toRadians(-toothAngle + toDegrees(atan(ys[i] / xs[i]))))
y = -rs[i] * sin(toRadians(-toothAngle + toDegrees(atan(ys[i] / xs[i]))))
return line(sg, endAbsolute = [x, y])
}
// Draw gear teeth
start = startSketchOn(XY)
|> startProfileAt([xs[101], ys[101]], %)
teeth = reduce([0..100], start, leftInvolute)
|> arc({
angleStart = 0,
angleEnd = toothAngle,
radius = baseDiameter / 2
}, %)
|> reduce([1..101], %, rightInvolute)
|> close()
|> extrude(length = gearHeight)
|> patternCircular3d(
axis = [0, 0, 1],
center = [0, 0, 0],
instances = nTeeth,
arcDegrees = 360,
rotateDuplicates = true,
)
// Define the constants of the keyway and the bore hole
keywayWidth = 0.250
keywayDepth = keywayWidth / 2
holeDiam = 2
holeRadius = 1
startAngle = asin(keywayWidth / 2 / holeRadius)
// Sketch the keyway and center hole and extrude
keyWay = startSketchOn(body, face = END)
|> startProfileAt([
holeRadius * cos(startAngle),
holeRadius * sin(startAngle)
], %)
|> xLine(length = keywayDepth)
|> yLine(length = -keywayWidth)
|> xLine(length = -keywayDepth)
|> arc({
angleEnd = 180,
angleStart = -1 * toDegrees(startAngle) + 360,
radius = holeRadius
}, %)
|> arc({
angleEnd = toDegrees(startAngle),
angleStart = 180,
radius = holeRadius
}, %)
|> close()
|> extrude(length = -gearHeight)
myScrew
|> translate(y = 10)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact graph flowchart axial-fan.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,754 @@
```mermaid
flowchart LR
subgraph path2 [Path]
2["Path<br>[323, 370, 5]"]
3["Segment<br>[376, 444, 5]"]
4["Segment<br>[450, 550, 5]"]
5["Segment<br>[556, 673, 5]"]
6["Segment<br>[679, 764, 5]"]
7["Segment<br>[770, 777, 5]"]
8[Solid2d]
end
subgraph path9 [Path]
9["Path<br>[788, 823, 5]"]
10["Segment<br>[788, 823, 5]"]
11[Solid2d]
end
subgraph path12 [Path]
12["Path<br>[838, 985, 5]"]
13["Segment<br>[838, 985, 5]"]
14[Solid2d]
end
subgraph path15 [Path]
15["Path<br>[1000, 1148, 5]"]
16["Segment<br>[1000, 1148, 5]"]
17[Solid2d]
end
subgraph path18 [Path]
18["Path<br>[1163, 1311, 5]"]
19["Segment<br>[1163, 1311, 5]"]
20[Solid2d]
end
subgraph path21 [Path]
21["Path<br>[1326, 1475, 5]"]
22["Segment<br>[1326, 1475, 5]"]
23[Solid2d]
end
subgraph path39 [Path]
39["Path<br>[1646, 1702, 5]"]
40["Segment<br>[1708, 1773, 5]"]
41["Segment<br>[1779, 1831, 5]"]
42["Segment<br>[1837, 1888, 5]"]
43["Segment<br>[1894, 1946, 5]"]
44["Segment<br>[1952, 2018, 5]"]
45["Segment<br>[2024, 2076, 5]"]
46["Segment<br>[2082, 2114, 5]"]
47["Segment<br>[2120, 2185, 5]"]
48["Segment<br>[2191, 2198, 5]"]
49[Solid2d]
end
subgraph path78 [Path]
78["Path<br>[2547, 2660, 5]"]
79["Segment<br>[2666, 2721, 5]"]
80["Segment<br>[2727, 2762, 5]"]
81["Segment<br>[2768, 2823, 5]"]
82["Segment<br>[2829, 2865, 5]"]
83["Segment<br>[2871, 2926, 5]"]
84["Segment<br>[2932, 2968, 5]"]
85["Segment<br>[2974, 3029, 5]"]
86["Segment<br>[3035, 3091, 5]"]
end
subgraph path113 [Path]
113["Path<br>[3240, 3291, 5]"]
114["Segment<br>[3240, 3291, 5]"]
115[Solid2d]
end
subgraph path120 [Path]
120["Path<br>[3470, 3529, 5]"]
121["Segment<br>[3535, 3603, 5]"]
122["Segment<br>[3609, 3709, 5]"]
123["Segment<br>[3715, 3832, 5]"]
124["Segment<br>[3838, 3923, 5]"]
125["Segment<br>[3929, 3936, 5]"]
126[Solid2d]
end
subgraph path127 [Path]
127["Path<br>[3947, 3998, 5]"]
128["Segment<br>[3947, 3998, 5]"]
129[Solid2d]
end
subgraph path130 [Path]
130["Path<br>[4013, 4160, 5]"]
131["Segment<br>[4013, 4160, 5]"]
132[Solid2d]
end
subgraph path133 [Path]
133["Path<br>[4175, 4323, 5]"]
134["Segment<br>[4175, 4323, 5]"]
135[Solid2d]
end
subgraph path136 [Path]
136["Path<br>[4338, 4486, 5]"]
137["Segment<br>[4338, 4486, 5]"]
138[Solid2d]
end
subgraph path139 [Path]
139["Path<br>[4501, 4650, 5]"]
140["Segment<br>[4501, 4650, 5]"]
141[Solid2d]
end
subgraph path157 [Path]
157["Path<br>[4795, 4833, 5]"]
158["Segment<br>[4795, 4833, 5]"]
159[Solid2d]
end
subgraph path165 [Path]
165["Path<br>[4906, 4942, 5]"]
166["Segment<br>[4906, 4942, 5]"]
167[Solid2d]
end
subgraph path181 [Path]
181["Path<br>[277, 327, 6]"]
182["Segment<br>[277, 327, 6]"]
183[Solid2d]
end
subgraph path191 [Path]
191["Path<br>[502, 537, 6]"]
192["Segment<br>[502, 537, 6]"]
193[Solid2d]
end
subgraph path203 [Path]
203["Path<br>[216, 255, 7]"]
204["Segment<br>[261, 291, 7]"]
205["Segment<br>[297, 336, 7]"]
206["Segment<br>[342, 366, 7]"]
207["Segment<br>[372, 396, 7]"]
208["Segment<br>[402, 443, 7]"]
209["Segment<br>[449, 487, 7]"]
210["Segment<br>[493, 516, 7]"]
211["Segment<br>[522, 539, 7]"]
212["Segment<br>[545, 566, 7]"]
213["Segment<br>[572, 659, 7]"]
214["Segment<br>[665, 702, 7]"]
215["Segment<br>[708, 745, 7]"]
216["Segment<br>[751, 758, 7]"]
217[Solid2d]
end
subgraph path243 [Path]
243["Path<br>[1100, 1212, 7]"]
244["Segment<br>[1220, 1330, 7]"]
245["Segment<br>[1338, 1672, 7]"]
246["Segment<br>[1680, 2016, 7]"]
247["Segment<br>[2024, 2255, 7]"]
248["Segment<br>[2263, 2270, 7]"]
249[Solid2d]
end
subgraph path251 [Path]
251["Path<br>[1100, 1212, 7]"]
252["Segment<br>[1220, 1330, 7]"]
253["Segment<br>[1338, 1672, 7]"]
254["Segment<br>[1680, 2016, 7]"]
255["Segment<br>[2024, 2255, 7]"]
256["Segment<br>[2263, 2270, 7]"]
257[Solid2d]
end
subgraph path259 [Path]
259["Path<br>[1100, 1212, 7]"]
264["Segment<br>[2263, 2270, 7]"]
265[Solid2d]
end
1["Plane<br>[300, 317, 5]"]
24["Sweep Extrusion<br>[1485, 1504, 5]"]
25[Wall]
26[Wall]
27[Wall]
28[Wall]
29["Cap Start"]
30["Cap End"]
31["SweepEdge Opposite"]
32["SweepEdge Adjacent"]
33["SweepEdge Opposite"]
34["SweepEdge Adjacent"]
35["SweepEdge Opposite"]
36["SweepEdge Adjacent"]
37["SweepEdge Opposite"]
38["SweepEdge Adjacent"]
50["Sweep Extrusion<br>[2338, 2358, 5]"]
51[Wall]
52[Wall]
53[Wall]
54[Wall]
55[Wall]
56[Wall]
57[Wall]
58[Wall]
59["SweepEdge Opposite"]
60["SweepEdge Adjacent"]
61["SweepEdge Opposite"]
62["SweepEdge Adjacent"]
63["SweepEdge Opposite"]
64["SweepEdge Adjacent"]
65["SweepEdge Opposite"]
66["SweepEdge Adjacent"]
67["SweepEdge Opposite"]
68["SweepEdge Adjacent"]
69["SweepEdge Opposite"]
70["SweepEdge Adjacent"]
71["SweepEdge Opposite"]
72["SweepEdge Adjacent"]
73["SweepEdge Opposite"]
74["SweepEdge Adjacent"]
75["Sweep Extrusion<br>[2338, 2358, 5]"]
76["Sweep Extrusion<br>[2338, 2358, 5]"]
77["Sweep Extrusion<br>[2338, 2358, 5]"]
87["Sweep Extrusion<br>[3097, 3132, 5]"]
88[Wall]
89[Wall]
90[Wall]
91[Wall]
92[Wall]
93[Wall]
94[Wall]
95[Wall]
96["Cap End"]
97["SweepEdge Opposite"]
98["SweepEdge Adjacent"]
99["SweepEdge Opposite"]
100["SweepEdge Adjacent"]
101["SweepEdge Opposite"]
102["SweepEdge Adjacent"]
103["SweepEdge Opposite"]
104["SweepEdge Adjacent"]
105["SweepEdge Opposite"]
106["SweepEdge Adjacent"]
107["SweepEdge Opposite"]
108["SweepEdge Adjacent"]
109["SweepEdge Opposite"]
110["SweepEdge Adjacent"]
111["SweepEdge Opposite"]
112["SweepEdge Adjacent"]
116["Sweep Extrusion<br>[3297, 3335, 5]"]
117[Wall]
118["SweepEdge Opposite"]
119["SweepEdge Adjacent"]
142["Sweep Extrusion<br>[4660, 4679, 5]"]
143[Wall]
144[Wall]
145[Wall]
146[Wall]
147["Cap Start"]
148["Cap End"]
149["SweepEdge Opposite"]
150["SweepEdge Adjacent"]
151["SweepEdge Opposite"]
152["SweepEdge Adjacent"]
153["SweepEdge Opposite"]
154["SweepEdge Adjacent"]
155["SweepEdge Opposite"]
156["SweepEdge Adjacent"]
160["Sweep Extrusion<br>[4839, 4859, 5]"]
161[Wall]
162["Cap End"]
163["SweepEdge Opposite"]
164["SweepEdge Adjacent"]
168["Sweep Extrusion<br>[4948, 4969, 5]"]
169[Wall]
170["SweepEdge Opposite"]
171["SweepEdge Adjacent"]
172["EdgeCut Fillet<br>[5010, 5521, 5]"]
173["EdgeCut Fillet<br>[5010, 5521, 5]"]
174["EdgeCut Fillet<br>[5010, 5521, 5]"]
175["EdgeCut Fillet<br>[5010, 5521, 5]"]
176["EdgeCut Fillet<br>[5010, 5521, 5]"]
177["EdgeCut Fillet<br>[5010, 5521, 5]"]
178["EdgeCut Fillet<br>[5010, 5521, 5]"]
179["EdgeCut Fillet<br>[5010, 5521, 5]"]
180["Plane<br>[204, 231, 6]"]
184["Sweep Extrusion<br>[333, 353, 6]"]
185[Wall]
186["Cap Start"]
187["Cap End"]
188["SweepEdge Opposite"]
189["SweepEdge Adjacent"]
190["Plane<br>[467, 495, 6]"]
194["Sweep Extrusion<br>[543, 564, 6]"]
195[Wall]
196["Cap Start"]
197["Cap End"]
198["SweepEdge Opposite"]
199["SweepEdge Adjacent"]
200["EdgeCut Fillet<br>[394, 452, 6]"]
201["EdgeCut Fillet<br>[394, 452, 6]"]
202["Plane<br>[193, 210, 7]"]
218["Sweep Revolve<br>[764, 846, 7]"]
219[Wall]
220[Wall]
221[Wall]
222[Wall]
223[Wall]
224[Wall]
225[Wall]
226[Wall]
227[Wall]
228[Wall]
229[Wall]
230[Wall]
231["SweepEdge Adjacent"]
232["SweepEdge Adjacent"]
233["SweepEdge Adjacent"]
234["SweepEdge Adjacent"]
235["SweepEdge Adjacent"]
236["SweepEdge Adjacent"]
237["SweepEdge Adjacent"]
238["SweepEdge Adjacent"]
239["SweepEdge Adjacent"]
240["SweepEdge Adjacent"]
241["SweepEdge Adjacent"]
242["Plane<br>[1053, 1091, 7]"]
250["Plane<br>[1053, 1091, 7]"]
258["Plane<br>[1053, 1091, 7]"]
260["SweepEdge Opposite"]
261["SweepEdge Opposite"]
262["SweepEdge Opposite"]
263["SweepEdge Opposite"]
266["Sweep Loft<br>[2389, 2509, 7]"]
267[Wall]
268[Wall]
269[Wall]
270[Wall]
271["Cap End"]
272["Cap End"]
273["SweepEdge Adjacent"]
274["SweepEdge Adjacent"]
275["SweepEdge Adjacent"]
276["SweepEdge Adjacent"]
277["StartSketchOnFace<br>[1597, 1640, 5]"]
278["StartSketchOnFace<br>[2498, 2541, 5]"]
279["StartSketchOnFace<br>[3197, 3234, 5]"]
280["StartSketchOnFace<br>[3421, 3458, 5]"]
281["StartSketchOnFace<br>[4746, 4789, 5]"]
282["StartSketchOnFace<br>[4861, 4900, 5]"]
283["StartSketchOnPlane<br>[244, 271, 6]"]
284["StartSketchOnPlane<br>[453, 496, 6]"]
285["StartSketchOnPlane<br>[1039, 1092, 7]"]
286["StartSketchOnPlane<br>[1039, 1092, 7]"]
287["StartSketchOnPlane<br>[1039, 1092, 7]"]
1 --- 2
1 --- 9
1 --- 12
1 --- 15
1 --- 18
1 --- 21
2 --- 3
2 --- 4
2 --- 5
2 --- 6
2 --- 7
2 ---- 24
2 --- 8
3 --- 25
3 --- 31
3 --- 32
4 --- 26
4 --- 33
4 --- 34
5 --- 27
5 --- 35
5 --- 36
6 --- 28
6 --- 37
6 --- 38
9 --- 10
9 --- 11
12 --- 13
12 --- 14
15 --- 16
15 --- 17
18 --- 19
18 --- 20
21 --- 22
21 --- 23
24 --- 25
24 --- 26
24 --- 27
24 --- 28
24 --- 29
24 --- 30
24 --- 31
24 --- 32
24 --- 33
24 --- 34
24 --- 35
24 --- 36
24 --- 37
24 --- 38
30 --- 39
30 --- 78
30 --- 157
39 --- 40
39 --- 41
39 --- 42
39 --- 43
39 --- 44
39 --- 45
39 --- 46
39 --- 47
39 --- 48
39 ---- 50
39 --- 49
40 --- 51
40 --- 59
40 --- 60
41 --- 52
41 --- 61
41 --- 62
42 --- 53
42 --- 63
42 --- 64
43 --- 54
43 --- 65
43 --- 66
44 --- 55
44 --- 67
44 --- 68
45 --- 56
45 --- 69
45 --- 70
46 --- 57
46 --- 71
46 --- 72
47 --- 58
47 --- 73
47 --- 74
50 --- 51
50 --- 52
50 --- 53
50 --- 54
50 --- 55
50 --- 56
50 --- 57
50 --- 58
50 --- 59
50 --- 60
50 --- 61
50 --- 62
50 --- 63
50 --- 64
50 --- 65
50 --- 66
50 --- 67
50 --- 68
50 --- 69
50 --- 70
50 --- 71
50 --- 72
50 --- 73
50 --- 74
78 --- 79
78 --- 80
78 --- 81
78 --- 82
78 --- 83
78 --- 84
78 --- 85
78 --- 86
78 ---- 87
79 --- 88
79 --- 97
79 --- 98
80 --- 89
80 --- 99
80 --- 100
81 --- 90
81 --- 101
81 --- 102
82 --- 91
82 --- 103
82 --- 104
83 --- 92
83 --- 105
83 --- 106
84 --- 93
84 --- 107
84 --- 108
85 --- 94
85 --- 109
85 --- 110
86 --- 95
86 --- 111
86 --- 112
87 --- 88
87 --- 89
87 --- 90
87 --- 91
87 --- 92
87 --- 93
87 --- 94
87 --- 95
87 --- 96
87 --- 97
87 --- 98
87 --- 99
87 --- 100
87 --- 101
87 --- 102
87 --- 103
87 --- 104
87 --- 105
87 --- 106
87 --- 107
87 --- 108
87 --- 109
87 --- 110
87 --- 111
87 --- 112
96 --- 113
96 --- 120
96 --- 127
96 --- 130
96 --- 133
96 --- 136
96 --- 139
113 --- 114
113 ---- 116
113 --- 115
114 --- 117
114 --- 118
114 --- 119
116 --- 117
116 --- 118
116 --- 119
120 --- 121
120 --- 122
120 --- 123
120 --- 124
120 --- 125
120 ---- 142
120 --- 126
121 --- 143
121 --- 149
121 --- 150
122 --- 144
122 --- 151
122 --- 152
123 --- 145
123 --- 153
123 --- 154
124 --- 146
124 --- 155
124 --- 156
127 --- 128
127 --- 129
130 --- 131
130 --- 132
133 --- 134
133 --- 135
136 --- 137
136 --- 138
139 --- 140
139 --- 141
142 --- 143
142 --- 144
142 --- 145
142 --- 146
142 --- 147
142 --- 148
142 --- 149
142 --- 150
142 --- 151
142 --- 152
142 --- 153
142 --- 154
142 --- 155
142 --- 156
157 --- 158
157 ---- 160
157 --- 159
158 --- 161
158 --- 163
158 --- 164
160 --- 161
160 --- 162
160 --- 163
160 --- 164
162 --- 165
165 --- 166
165 ---- 168
165 --- 167
166 --- 169
166 --- 170
166 --- 171
168 --- 169
168 --- 170
168 --- 171
32 <--x 172
34 <--x 173
36 <--x 174
38 <--x 175
150 <--x 176
152 <--x 177
154 <--x 178
156 <--x 179
180 --- 181
181 --- 182
181 ---- 184
181 --- 183
182 --- 185
182 --- 188
182 --- 189
182 --- 201
184 --- 185
184 --- 186
184 --- 187
184 --- 188
184 --- 189
190 --- 191
191 --- 192
191 ---- 194
191 --- 193
192 --- 195
192 --- 198
192 --- 199
194 --- 195
194 --- 196
194 --- 197
194 --- 198
194 --- 199
188 <--x 200
202 --- 203
203 --- 204
203 --- 205
203 --- 206
203 --- 207
203 --- 208
203 --- 209
203 --- 210
203 --- 211
203 --- 212
203 --- 213
203 --- 214
203 --- 215
203 --- 216
203 ---- 218
203 --- 217
204 --- 219
204 x--> 231
205 --- 220
205 --- 231
206 --- 221
206 --- 232
207 --- 222
207 --- 233
208 --- 223
208 --- 234
209 --- 224
209 --- 235
210 --- 225
210 --- 236
211 --- 226
211 --- 237
212 --- 227
212 --- 238
213 --- 228
213 --- 239
214 --- 229
214 --- 240
215 --- 230
215 --- 241
218 --- 219
218 --- 220
218 --- 221
218 --- 222
218 --- 223
218 --- 224
218 --- 225
218 --- 226
218 --- 227
218 --- 228
218 --- 229
218 --- 230
218 <--x 204
218 --- 231
218 <--x 205
218 <--x 206
218 --- 232
218 <--x 207
218 --- 233
218 <--x 208
218 --- 234
218 <--x 209
218 --- 235
218 <--x 210
218 --- 236
218 <--x 211
218 --- 237
218 <--x 212
218 --- 238
218 <--x 213
218 --- 239
218 <--x 214
218 --- 240
218 <--x 215
218 --- 241
242 --- 243
243 --- 244
243 --- 245
243 --- 246
243 --- 247
243 --- 248
243 ---- 266
243 --- 249
244 --- 267
244 --- 260
244 --- 273
245 --- 268
245 --- 261
245 --- 274
246 --- 269
246 --- 262
246 --- 275
247 --- 270
247 --- 263
247 --- 276
250 --- 251
251 --- 252
251 --- 253
251 --- 254
251 --- 255
251 --- 256
251 x---> 266
251 --- 257
258 --- 259
259 x--> 260
259 x--> 261
259 x--> 262
259 x--> 263
259 --- 264
259 x---> 266
259 --- 265
266 --- 260
266 --- 261
266 --- 262
266 --- 263
266 --- 267
266 --- 268
266 --- 269
266 --- 270
266 --- 271
266 --- 272
266 --- 273
266 --- 274
266 --- 275
266 --- 276
30 <--x 277
30 <--x 278
96 <--x 279
96 <--x 280
30 <--x 281
162 <--x 282
180 <--x 283
190 <--x 284
242 <--x 285
250 <--x 286
258 <--x 287
```

View File

@ -0,0 +1,220 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of parsing axial-fan.kcl
---
{
"Ok": {
"body": [
{
"commentStart": 0,
"end": 0,
"path": {
"type": "Kcl",
"filename": "fan-housing.kcl"
},
"preComments": [
"// Import all parts into assembly file"
],
"selector": {
"type": "None",
"alias": {
"commentStart": 0,
"end": 0,
"name": "fanHousing",
"start": 0,
"type": "Identifier"
}
},
"start": 0,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"commentStart": 0,
"end": 0,
"path": {
"type": "Kcl",
"filename": "motor.kcl"
},
"selector": {
"type": "None",
"alias": {
"commentStart": 0,
"end": 0,
"name": "motor",
"start": 0,
"type": "Identifier"
}
},
"start": 0,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"commentStart": 0,
"end": 0,
"path": {
"type": "Kcl",
"filename": "fan.kcl"
},
"selector": {
"type": "None",
"alias": {
"commentStart": 0,
"end": 0,
"name": "fan",
"start": 0,
"type": "Identifier"
}
},
"start": 0,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"commentStart": 0,
"end": 0,
"expression": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "fanHousing",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
},
"preComments": [
"",
"",
"// Produce the model for each imported part"
],
"start": 0,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"commentStart": 0,
"end": 0,
"expression": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "motor",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"commentStart": 0,
"end": 0,
"expression": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "fan",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"commentStart": 0,
"end": 0,
"innerAttrs": [
{
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "settings",
"start": 0,
"type": "Identifier"
},
"preComments": [
"// PC Fan",
"// A small axial fan, used to push or draw airflow over components to remove excess heat",
"",
"",
"// Set units"
],
"properties": [
{
"commentStart": 0,
"end": 0,
"key": {
"commentStart": 0,
"end": 0,
"name": "defaultLengthUnit",
"start": 0,
"type": "Identifier"
},
"start": 0,
"type": "ObjectProperty",
"value": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "mm",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
}
}
],
"start": 0,
"type": "Annotation"
}
],
"nonCodeMeta": {
"nonCodeNodes": {},
"startNodes": [
{
"commentStart": 0,
"end": 0,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"start": 0
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Variables in memory after executing axial-fan.kcl
---
{
"fan": {
"type": "Module",
"value": 7
},
"fanHousing": {
"type": "Module",
"value": 5
},
"motor": {
"type": "Module",
"value": 6
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@ -0,0 +1,544 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact commands bottle.kcl
---
[
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "edge_lines_visible",
"hidden": false
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "set_scene_units",
"unit": "mm"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"size": 60.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "enable_sketch_mode",
"entity_id": "[uuid]",
"ortho": false,
"animated": false,
"adjust_camera": false,
"planar_normal": {
"x": 0.0,
"y": 0.0,
"z": 1.0
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "start_path"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": -62.5,
"y": 0.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 0.0,
"y": 26.6667,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "arc_to",
"interior": {
"x": 0.0,
"y": 40.0,
"z": 0.0
},
"end": {
"x": 62.5,
"y": 26.6667,
"z": 0.0
},
"relative": false
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 62.5,
"y": 0.0,
"z": 0.0
},
"relative": false
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "entity_mirror",
"ids": [
"[uuid]"
],
"axis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"point": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "entity_get_all_child_uuids",
"entity_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "close_path",
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "enable_sketch_mode",
"entity_id": "[uuid]",
"ortho": false,
"animated": false,
"adjust_camera": false,
"planar_normal": {
"x": 0.0,
"y": 0.0,
"z": 1.0
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extrude",
"target": "[uuid]",
"distance": 202.0,
"faces": null,
"opposite": "None"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_bring_to_front",
"object_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_extrusion_face_info",
"object_id": "[uuid]",
"edge_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "enable_sketch_mode",
"entity_id": "[uuid]",
"ortho": false,
"animated": false,
"adjust_camera": false,
"planar_normal": null
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "start_path"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": 22.5,
"y": 0.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "arc",
"center": {
"x": 0.0,
"y": 0.0
},
"radius": 22.5,
"start": {
"unit": "degrees",
"value": 0.0
},
"end": {
"unit": "degrees",
"value": 360.0
},
"relative": false
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "close_path",
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "enable_sketch_mode",
"entity_id": "[uuid]",
"ortho": false,
"animated": false,
"adjust_camera": false,
"planar_normal": null
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extrude",
"target": "[uuid]",
"distance": 18.0,
"faces": null,
"opposite": "None"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_bring_to_front",
"object_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_extrusion_face_info",
"object_id": "[uuid]",
"edge_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_shell_face",
"object_id": "[uuid]",
"face_ids": [
"[uuid]"
],
"shell_thickness": 4.0,
"hollow": false
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_set_material_params_pbr",
"object_id": "[uuid]",
"color": {
"r": 0.0,
"g": 0.47058824,
"b": 0.7607843,
"a": 100.0
},
"metalness": 0.0,
"roughness": 0.0,
"ambient_occlusion": 0.0
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "set_scene_units",
"unit": "mm"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "set_scene_units",
"unit": "mm"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "set_scene_units",
"unit": "mm"
}
}
]

View File

@ -0,0 +1,6 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact graph flowchart bottle.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,44 @@
```mermaid
flowchart LR
subgraph path2 [Path]
2["Path<br>[337, 378, 0]"]
3["Segment<br>[384, 415, 0]"]
4["Segment<br>[421, 528, 0]"]
5["Segment<br>[534, 556, 0]"]
6["Segment<br>[586, 593, 0]"]
7[Solid2d]
end
subgraph path10 [Path]
10["Path<br>[750, 800, 0]"]
11["Segment<br>[750, 800, 0]"]
12[Solid2d]
end
1["Plane<br>[314, 331, 0]"]
8["Sweep Extrusion<br>[599, 641, 0]"]
9["Plane<br>[750, 800, 0]"]
13["Sweep Extrusion<br>[806, 833, 0]"]
14[Wall]
15["Cap End"]
16["SweepEdge Opposite"]
17["SweepEdge Adjacent"]
18["StartSketchOnFace<br>[707, 744, 0]"]
1 --- 2
2 --- 3
2 --- 4
2 --- 5
2 --- 6
2 ---- 8
2 --- 7
9 --- 10
10 --- 11
10 ---- 13
10 --- 12
11 --- 14
11 --- 16
11 --- 17
13 --- 14
13 --- 15
13 --- 16
13 --- 17
9 <--x 18
```

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,152 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Operations executed bottle.kcl
---
[
{
"labeledArgs": {
"planeOrSolid": {
"value": {
"type": "Plane",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": null
},
{
"labeledArgs": {
"length": {
"value": {
"type": "Number",
"value": 202.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "extrude",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"face": {
"value": {
"type": "String",
"value": "end"
},
"sourceRange": []
}
},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Solid",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"length": {
"value": {
"type": "Number",
"value": 18.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "extrude",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"faces": {
"value": {
"type": "Array",
"value": [
{
"type": "String",
"value": "end"
}
]
},
"sourceRange": []
},
"thickness": {
"value": {
"type": "Number",
"value": 4.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "shell",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Solid",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
}
]

View File

@ -0,0 +1,817 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Variables in memory after executing bottle.kcl
---
{
"bottleBody": {
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": [],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-62.5,
0.0
],
"tag": null,
"to": [
-62.5,
26.6667
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-62.5,
26.6667
],
"p1": [
-62.5,
26.666666666666668
],
"p2": [
0.0,
40.0
],
"p3": [
62.5,
26.666666666666668
],
"tag": null,
"to": [
62.5,
26.6667
],
"type": "ArcThreePoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
62.5,
26.6667
],
"tag": null,
"to": [
62.5,
0.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
62.5,
0.0
],
"tag": null,
"to": [
-62.5,
0.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0,
"units": {
"type": "Mm"
}
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-62.5,
0.0
],
"to": [
-62.5,
0.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 202.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
}
}
},
"bottleHeight": {
"type": "Number",
"value": 220.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"bottleLength": {
"type": "Number",
"value": 125.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"bottleNeck": {
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudeArc"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"ccw": true,
"center": [
0.0,
0.0
],
"from": [
22.5,
0.0
],
"radius": 22.5,
"tag": null,
"to": [
22.5,
0.0
],
"type": "Circle",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "face",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "end",
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0,
"units": {
"type": "Mm"
}
},
"solid": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": [],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-62.5,
0.0
],
"tag": null,
"to": [
-62.5,
26.6667
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-62.5,
26.6667
],
"p1": [
-62.5,
26.666666666666668
],
"p2": [
0.0,
40.0
],
"p3": [
62.5,
26.666666666666668
],
"tag": null,
"to": [
62.5,
26.6667
],
"type": "ArcThreePoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
62.5,
26.6667
],
"tag": null,
"to": [
62.5,
0.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
62.5,
0.0
],
"tag": null,
"to": [
-62.5,
0.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0,
"units": {
"type": "Mm"
}
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-62.5,
0.0
],
"to": [
-62.5,
0.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 202.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
}
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
22.5,
0.0
],
"to": [
22.5,
0.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 18.0,
"startCapId": null,
"endCapId": "[uuid]",
"units": {
"type": "Mm"
}
}
},
"bottleShell": {
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudeArc"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"ccw": true,
"center": [
0.0,
0.0
],
"from": [
22.5,
0.0
],
"radius": 22.5,
"tag": null,
"to": [
22.5,
0.0
],
"type": "Circle",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "face",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "end",
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0,
"units": {
"type": "Mm"
}
},
"solid": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": [],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-62.5,
0.0
],
"tag": null,
"to": [
-62.5,
26.6667
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-62.5,
26.6667
],
"p1": [
-62.5,
26.666666666666668
],
"p2": [
0.0,
40.0
],
"p3": [
62.5,
26.666666666666668
],
"tag": null,
"to": [
62.5,
26.6667
],
"type": "ArcThreePoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
62.5,
26.6667
],
"tag": null,
"to": [
62.5,
0.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
62.5,
0.0
],
"tag": null,
"to": [
-62.5,
0.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0,
"units": {
"type": "Mm"
}
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0,
"units": {
"type": "Mm"
}
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-62.5,
0.0
],
"to": [
-62.5,
0.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 202.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
}
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
22.5,
0.0
],
"to": [
22.5,
0.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 18.0,
"startCapId": null,
"endCapId": "[uuid]",
"units": {
"type": "Mm"
}
}
},
"bottleWidth": {
"type": "Number",
"value": 80.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"neckDepth": {
"type": "Number",
"value": 18.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"neckDiameter": {
"type": "Number",
"value": 45.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"wallThickness": {
"type": "Number",
"value": 4.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -23,6 +23,7 @@ pub struct EngineConnection {
batch: Arc<RwLock<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>>,
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>>,
core_test: Arc<RwLock<String>>,
ids_of_async_commands: Arc<RwLock<IndexMap<Uuid, kcl_lib::SourceRange>>>,
/// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
stats: EngineStats,
@ -37,6 +38,7 @@ impl EngineConnection {
batch_end: Arc::new(RwLock::new(IndexMap::new())),
core_test: result,
default_planes: Default::default(),
ids_of_async_commands: Arc::new(RwLock::new(IndexMap::new())),
stats: Default::default(),
})
}
@ -379,6 +381,10 @@ impl kcl_lib::EngineManager for EngineConnection {
Arc::new(RwLock::new(Vec::new()))
}
fn ids_of_async_commands(&self) -> Arc<RwLock<IndexMap<Uuid, kcl_lib::SourceRange>>> {
self.ids_of_async_commands.clone()
}
fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>> {
self.default_planes.clone()
}
@ -391,6 +397,25 @@ impl kcl_lib::EngineManager for EngineConnection {
Ok(())
}
async fn inner_fire_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: kcl_lib::SourceRange,
cmd: WebSocketRequest,
id_to_source_range: HashMap<Uuid, kcl_lib::SourceRange>,
) -> Result<(), KclError> {
// Pop off the id we care about.
self.ids_of_async_commands.write().await.swap_remove(&id);
// Add the response to our responses.
let response = self
.inner_send_modeling_cmd(id, source_range, cmd, id_to_source_range)
.await?;
self.responses().write().await.insert(id, response);
Ok(())
}
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,

View File

@ -9,6 +9,7 @@ use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct Context {
engine: Arc<Box<dyn EngineManager>>,
response_context: Arc<kcl_lib::wasm_engine::ResponseContext>,
fs: Arc<FileManager>,
mock_engine: Arc<Box<dyn EngineManager>>,
}
@ -22,9 +23,10 @@ impl Context {
) -> Result<Self, JsValue> {
console_error_panic_hook::set_once();
let response_context = Arc::new(kcl_lib::wasm_engine::ResponseContext::new());
Ok(Self {
engine: Arc::new(Box::new(
kcl_lib::wasm_engine::EngineConnection::new(engine_manager)
kcl_lib::wasm_engine::EngineConnection::new(engine_manager, response_context.clone())
.await
.map_err(|e| format!("{:?}", e))?,
)),
@ -34,6 +36,7 @@ impl Context {
.await
.map_err(|e| format!("{:?}", e))?,
)),
response_context,
})
}
@ -100,6 +103,12 @@ impl Context {
}
}
/// Send a response to kcl lib's engine.
#[wasm_bindgen(js_name = sendResponse)]
pub async fn send_response(&self, data: js_sys::Uint8Array) -> Result<(), JsValue> {
self.response_context.send_response(data).await
}
/// Execute a program in mock mode.
#[wasm_bindgen(js_name = executeMock)]
pub async fn execute_mock(

View File

@ -127,7 +127,7 @@ export const CreateNewVariable = ({
autoFocus={true}
autoCapitalize="off"
autoCorrect="off"
className={`font-mono flex-1 sm:text-sm px-2 py-1 rounded-sm bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-90 dark:text-chalkboard-10 ${
className={`flex-1 sm:text-sm px-2 py-1 rounded-sm bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-90 dark:text-chalkboard-10 ${
!shouldCreateVariable ? 'opacity-50' : ''
}`}
value={newVariableName}

View File

@ -628,8 +628,8 @@ const CustomIconMap = {
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M4 3H4.5H11H11.2071L11.3536 3.14645L15.8536 7.64646L16 7.7929V8.00001V11.3773C15.6992 11.1362 15.3628 10.9376 15 10.7908V8.50001H11H10.5V8.00001V4H5V16H9.79076C9.93763 16.3628 10.1362 16.6992 10.3773 17H4.5H4V16.5V3.5V3ZM11.5 4.70711L14.2929 7.50001H11.5V4.70711ZM13.8123 17.3904L16.3123 15.3904L15.6877 14.6096L14 15.9597V12H13V15.9597L11.3123 14.6096L10.6877 15.3904L13.1877 17.3904L13.5 17.6403L13.8123 17.3904Z"
fill="currentColor"
/>

View File

@ -18,7 +18,7 @@
.header {
@apply z-10 relative rounded-tr;
@apply flex h-[41px] items-center justify-between gap-2 px-2;
@apply font-mono text-xs font-bold select-none text-chalkboard-90;
@apply text-xs select-none text-chalkboard-90;
@apply bg-chalkboard-10 border-b border-chalkboard-30;
}

View File

@ -1,6 +1,6 @@
.button {
@apply flex justify-between items-center gap-2 px-2 py-1 text-left border-none rounded-sm;
@apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90;
@apply !no-underline text-xs select-none text-chalkboard-90;
@apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit;
@apply transition-colors ease-out;
@apply m-0;

View File

@ -1617,6 +1617,12 @@ export class EngineCommandManager extends EventTarget {
return
}
// In either case we want to send the response back over the wire to
// the rust side.
this.rustContext?.sendResponse(message).catch((err) => {
console.error('Error sending response to rust', err)
})
const pending = this.pendingCommands[message.request_id || '']
if (pending && !message.success) {
@ -1931,6 +1937,46 @@ export class EngineCommandManager extends EventTarget {
return e
})
}
/**
* A wrapper around the sendCommand where all inputs are JSON strings
*
* This one does not wait for a response.
*/
fireModelingCommandFromWasm(
id: string,
rangeStr: string,
commandStr: string,
idToRangeStr: string
): void | Error {
if (this.engineConnection === undefined)
return new Error('engineConnection is undefined')
if (
!this.engineConnection?.isReady() &&
!this.engineConnection.isUsingConnectionLite
)
return new Error('engineConnection is not ready')
if (id === undefined) return new Error('id is undefined')
if (rangeStr === undefined) return new Error('rangeStr is undefined')
if (commandStr === undefined) return new Error('commandStr is undefined')
const range: SourceRange = JSON.parse(rangeStr)
const command: EngineCommand = JSON.parse(commandStr)
const idToRangeMap: { [key: string]: SourceRange } =
JSON.parse(idToRangeStr)
// Current executeAst is stale, going to interrupt, a new executeAst will trigger
// Used in conjunction with rejectAllModelingCommands
if (this?.kclManager?.executeIsStale) {
return new Error(EXECUTE_AST_INTERRUPT_ERROR_MESSAGE)
}
// We purposely don't wait for a response here
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.sendCommand(id, {
command,
range,
idToRangeMap,
})
}
/**
* A wrapper around the sendCommand where all inputs are JSON strings
*/

View File

@ -1,5 +1,6 @@
import toast from 'react-hot-toast'
import { BSON } from 'bson'
import type { Configuration } from '@rust/kcl-lib/bindings/Configuration'
import type { DefaultPlanes } from '@rust/kcl-lib/bindings/DefaultPlanes'
import type { KclError as RustKclError } from '@rust/kcl-lib/bindings/KclError'
@ -24,6 +25,7 @@ import { err, reportRejection } from '@src/lib/trap'
import type { DeepPartial } from '@src/lib/types'
import type { ModuleType } from '@src/lib/wasm_lib_wrapper'
import { getModule } from '@src/lib/wasm_lib_wrapper'
import type { Models } from '@kittycad/lib/dist/types/src'
export default class RustContext {
private wasmInitFailed: boolean = true
@ -58,6 +60,7 @@ export default class RustContext {
// Create a new context instance
async create(): Promise<Context> {
this.rustInstance = getModule()
// We need this await here, DO NOT REMOVE it even if your editor says it's
// unnecessary. The constructor of the module is async and it will not
// resolve if you don't await it.
@ -203,6 +206,21 @@ export default class RustContext {
return this.defaultPlanes[key]
}
// Send a response back to the rust side, that we got back from the engine.
async sendResponse(
response: Models['WebSocketResponse_type']
): Promise<void> {
const instance = await this._checkInstance()
try {
const serialized = BSON.serialize(response)
await instance.sendResponse(serialized)
} catch (e: any) {
const err = errFromErrWithOutputs(e)
return Promise.reject(err)
}
}
// Helper to check if context instance exists
private async _checkInstance(): Promise<Context> {
if (!this.ctxInstance) {