Compare commits

...

7 Commits

Author SHA1 Message Date
19a8a2bba8 add script to build on mac mini 2025-03-14 15:36:01 -04:00
e9806b83d7 Clip deltaY to +/- 100 (#5793)
* Clip deltaY to +/- 100
Should fix #5120

* Lint

* Fix the clip with @nickmccleery's suggestion
2025-03-14 13:17:10 -04:00
0229105158 fix: add better errors for missing commas in arrays and objects (#5210)
* fix: add better errors for missing commas in arrays and objects

* chore: add object prop shorthand missing comma test

* fix: wording on unexpected character in arrays and objects

* fix: don't eagerly evaluate whether there is a closing brace/bracket

* feat: exit early when detecting missing commas if encountering invalid tokens

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* fix: updated reserved word in test to replace removed one

---------

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-14 16:52:10 +00:00
9e37e13b6b Further unfrig playwright (#5794)
* Bail on cleanup after 10s; move setup even higher

* Give macos time before starting an electron instance

* Try it again

* Again

* Remove stale TODO

* Use Kolmogorov complexity instead of text for ai assertions

* Help out prompt-to-edit test with being more explicit

* Try to give Mac more time to open a window

* Fix the other var change for prompt-to-edit test

* Forgot this

* Try some crazier shit

* 🤦 I forgot the return.

* Ok things were actually just not working well at all

* Fix export test to expect smaller size

* yarn tsc && yarn lint && yarn fmt
2025-03-14 10:19:58 +00:00
58e0c0e916 Improve KCL Samples (#5767)
* improve KCL Samples & .gitignore

* update block and car wheel assembly

* update flange and lego, delete flange xy

* artifacts

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* scale

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2025-03-13 23:38:51 -07:00
dd99c27d56 bump all the rust things (#5806)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* getrandom

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* simlimk

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* Revert "simlimk"

This reverts commit 3f5221db7e.

* terst

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-13 23:38:41 -07:00
3cff26b987 make sure all enter sketch mode are with the stuff they need in the same batch order always (#5646)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* comment out

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* update artifacts

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* small

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* last of the artifacts

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* update playwirght

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add crazy multi-profile test

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* steps

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix artifact graph

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates
;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* more artifact grph

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* turn back on playwright

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fmt

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* playwright fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* playwright fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-03-13 21:59:39 -07:00
331 changed files with 129274 additions and 60766 deletions

3
.gitignore vendored
View File

@ -53,13 +53,14 @@ e2e/playwright/export-snapshots/*
/public/kcl-samples.zip
/public/kcl-samples/.github
/public/kcl-samples/screenshots/main.kcl
/public/kcl-samples/step/main.kcl
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/src/lang/std/artifactMapCache
## generated files
src/**/*.typegen.ts

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -248838,7 +248838,7 @@
"keywordArguments": true,
"args": [
{
"name": "solid",
"name": "solidSet",
"type": "SolidOrImportedGeometry",
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -248948,6 +248948,26 @@
}
}
}
},
{
"type": [
"object",
"array"
],
"items": {
"$ref": "#/components/schemas/Solid"
},
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"solidSet"
]
}
}
}
],
"definitions": {
@ -250490,7 +250510,7 @@
},
"required": true,
"includeInSnippet": true,
"description": "The solid to rotate.",
"description": "The solid or set of solids to rotate.",
"labelRequired": false
},
{
@ -259918,6 +259938,26 @@
}
}
}
},
{
"type": [
"object",
"array"
],
"items": {
"$ref": "#/components/schemas/Solid"
},
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"solidSet"
]
}
}
}
],
"definitions": {
@ -261467,7 +261507,8 @@
"examples": [
"// Rotate a pipe with roll, pitch, and yaw.\n\n\n// Create a path for the sweep.\nsweepPath = startSketchOn('XZ')\n |> startProfileAt([0.05, 0.05], %)\n |> line(end = [0, 7])\n |> tangentialArc({ offset = 90, radius = 5 }, %)\n |> line(end = [-3, 0])\n |> tangentialArc({ offset = -90, radius = 5 }, %)\n |> line(end = [0, 7])\n\n// Create a hole for the pipe.\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 |> rotate(roll = 10, pitch = 10, yaw = 90)",
"// Rotate a pipe about an axis with an angle.\n\n\n// Create a path for the sweep.\nsweepPath = startSketchOn('XZ')\n |> startProfileAt([0.05, 0.05], %)\n |> line(end = [0, 7])\n |> tangentialArc({ offset = 90, radius = 5 }, %)\n |> line(end = [-3, 0])\n |> tangentialArc({ offset = -90, radius = 5 }, %)\n |> line(end = [0, 7])\n\n// Create a hole for the pipe.\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 |> rotate(axis = [0, 0, 1.0], angle = 90)",
"// Rotate an imported model.\n\n\nimport \"tests/inputs/cube.sldprt\" as cube\n\ncube\n |> rotate(axis = [0, 0, 1.0], angle = 90)"
"// Rotate an imported model.\n\n\nimport \"tests/inputs/cube.sldprt\" as cube\n\ncube\n |> rotate(axis = [0, 0, 1.0], angle = 90)",
"// Sweep two sketches along the same path.\n\n\nsketch001 = startSketchOn('XY')\nrectangleSketch = startProfileAt([-200, 23.86], sketch001)\n |> angledLine([0, 73.47], %, $rectangleSegmentA001)\n |> angledLine([\n segAng(rectangleSegmentA001) - 90,\n 50.61\n ], %)\n |> angledLine([\n segAng(rectangleSegmentA001),\n -segLen(rectangleSegmentA001)\n ], %)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\n\ncircleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)\n\nsketch002 = startSketchOn('YZ')\nsweepPath = startProfileAt([0, 0], sketch002)\n |> yLine(length = 231.81)\n |> tangentialArc({ radius = 80, offset = -90 }, %)\n |> xLine(length = 384.93)\n\nparts = sweep([rectangleSketch, circleSketch], path = sweepPath)\n\n// Rotate the sweeps.\nrotate(parts, axis = [0, 0, 1.0], angle = 90)"
]
},
{
@ -261520,7 +261561,7 @@
"keywordArguments": true,
"args": [
{
"name": "solid",
"name": "solidSet",
"type": "SolidOrImportedGeometry",
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -261630,6 +261671,26 @@
}
}
}
},
{
"type": [
"object",
"array"
],
"items": {
"$ref": "#/components/schemas/Solid"
},
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"solidSet"
]
}
}
}
],
"definitions": {
@ -263172,7 +263233,7 @@
},
"required": true,
"includeInSnippet": true,
"description": "The solid to scale.",
"description": "The solid or set of solids to scale.",
"labelRequired": false
},
{
@ -266393,6 +266454,26 @@
}
}
}
},
{
"type": [
"object",
"array"
],
"items": {
"$ref": "#/components/schemas/Solid"
},
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"solidSet"
]
}
}
}
],
"definitions": {
@ -267941,7 +268022,8 @@
"deprecated": false,
"examples": [
"// Scale a pipe.\n\n\n// Create a path for the sweep.\nsweepPath = startSketchOn('XZ')\n |> startProfileAt([0.05, 0.05], %)\n |> line(end = [0, 7])\n |> tangentialArc({ offset = 90, radius = 5 }, %)\n |> line(end = [-3, 0])\n |> tangentialArc({ offset = -90, radius = 5 }, %)\n |> line(end = [0, 7])\n\n// Create a hole for the pipe.\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 |> scale(scale = [1.0, 1.0, 2.5])",
"// Scale an imported model.\n\n\nimport \"tests/inputs/cube.sldprt\" as cube\n\ncube\n |> scale(scale = [1.0, 1.0, 2.5])"
"// Scale an imported model.\n\n\nimport \"tests/inputs/cube.sldprt\" as cube\n\ncube\n |> scale(scale = [1.0, 1.0, 2.5])",
"// Sweep two sketches along the same path.\n\n\nsketch001 = startSketchOn('XY')\nrectangleSketch = startProfileAt([-200, 23.86], sketch001)\n |> angledLine([0, 73.47], %, $rectangleSegmentA001)\n |> angledLine([\n segAng(rectangleSegmentA001) - 90,\n 50.61\n ], %)\n |> angledLine([\n segAng(rectangleSegmentA001),\n -segLen(rectangleSegmentA001)\n ], %)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\n\ncircleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)\n\nsketch002 = startSketchOn('YZ')\nsweepPath = startProfileAt([0, 0], sketch002)\n |> yLine(length = 231.81)\n |> tangentialArc({ radius = 80, offset = -90 }, %)\n |> xLine(length = 384.93)\n\nparts = sweep([rectangleSketch, circleSketch], path = sweepPath)\n\n// Scale the sweep.\nscale(parts, scale = [1.0, 1.0, 0.5])"
]
},
{
@ -317079,7 +317161,7 @@
"keywordArguments": true,
"args": [
{
"name": "solid",
"name": "solidSet",
"type": "SolidOrImportedGeometry",
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -317189,6 +317271,26 @@
}
}
}
},
{
"type": [
"object",
"array"
],
"items": {
"$ref": "#/components/schemas/Solid"
},
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"solidSet"
]
}
}
}
],
"definitions": {
@ -318731,7 +318833,7 @@
},
"required": true,
"includeInSnippet": true,
"description": "The solid to move.",
"description": "The solid or set of solids to move.",
"labelRequired": false
},
{
@ -321952,6 +322054,26 @@
}
}
}
},
{
"type": [
"object",
"array"
],
"items": {
"$ref": "#/components/schemas/Solid"
},
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"solidSet"
]
}
}
}
],
"definitions": {
@ -323500,7 +323622,8 @@
"deprecated": false,
"examples": [
"// Move a pipe.\n\n\n// Create a path for the sweep.\nsweepPath = startSketchOn('XZ')\n |> startProfileAt([0.05, 0.05], %)\n |> line(end = [0, 7])\n |> tangentialArc({ offset = 90, radius = 5 }, %)\n |> line(end = [-3, 0])\n |> tangentialArc({ offset = -90, radius = 5 }, %)\n |> line(end = [0, 7])\n\n// Create a hole for the pipe.\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 |> translate(translate = [1.0, 1.0, 2.5])",
"// Move an imported model.\n\n\nimport \"tests/inputs/cube.sldprt\" as cube\n\ncube\n |> translate(translate = [1.0, 1.0, 2.5])"
"// Move an imported model.\n\n\nimport \"tests/inputs/cube.sldprt\" as cube\n\ncube\n |> translate(translate = [1.0, 1.0, 2.5])",
"// Sweep two sketches along the same path.\n\n\nsketch001 = startSketchOn('XY')\nrectangleSketch = startProfileAt([-200, 23.86], sketch001)\n |> angledLine([0, 73.47], %, $rectangleSegmentA001)\n |> angledLine([\n segAng(rectangleSegmentA001) - 90,\n 50.61\n ], %)\n |> angledLine([\n segAng(rectangleSegmentA001),\n -segLen(rectangleSegmentA001)\n ], %)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\n\ncircleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)\n\nsketch002 = startSketchOn('YZ')\nsweepPath = startProfileAt([0, 0], sketch002)\n |> yLine(length = 231.81)\n |> tangentialArc({ radius = 80, offset = -90 }, %)\n |> xLine(length = 384.93)\n\nparts = sweep([rectangleSketch, circleSketch], path = sweepPath)\n\n// Move the sweeps.\ntranslate(parts, translate = [1.0, 1.0, 2.5])"
]
},
{

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -55,6 +55,21 @@ Data for an imported geometry.
----
**Type:** `[object, array]`
`[` [`Solid`](/docs/kcl/types/Solid) `]`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `solidSet`| | No |
----

View File

@ -185,7 +185,7 @@ test(
},
{ timeout: 15_000 }
)
.toBeGreaterThan(100_000)
.toBeGreaterThan(70_000)
})
})
}

View File

@ -90,8 +90,9 @@ export class ElectronZoo {
constructor() {}
// Help remote end by signaling we're done with the connection.
// If it takes longer than 10s to stop, just resolve.
async makeAvailableAgain() {
// Help remote end by signaling we're done with the connection.
await this.page.evaluate(async () => {
return new Promise((resolve) => {
if (!window.engineCommandManager.engineConnection?.state?.type) {
@ -99,7 +100,9 @@ export class ElectronZoo {
}
window.engineCommandManager.tearDown()
// Keep polling (per js event tick) until state is Disconnected.
const timeA = Date.now()
const checkDisconnected = () => {
// It's possible we never even created an engineConnection
// e.g. never left Projects view.
@ -109,6 +112,11 @@ export class ElectronZoo {
) {
return resolve(undefined)
}
if (Date.now() - timeA > 10000) {
return resolve(undefined)
}
setTimeout(checkDisconnected, 0)
}
checkDisconnected()
@ -130,6 +138,7 @@ export class ElectronZoo {
const that = this
const options = {
timeout: 120000,
args: ['.', '--no-sandbox'],
env: {
...process.env,
@ -155,8 +164,28 @@ export class ElectronZoo {
// Do this once and then reuse window on subsequent calls.
if (!this.electron) {
this.electron = await electron.launch(options)
// Mac takes quite a long time to create the first window in CI.
// Turns out we can't trust firstWindow() either. So loop.
let timeoutId: ReturnType<typeof setTimeout>
const tryToGetWindowPage = () =>
new Promise((resolve) => {
const fn = () => {
this.page = this.electron.windows()[0]
timeoutId = setTimeout(() => {
if (this.page) {
clearTimeout(timeoutId)
return resolve(undefined)
}
fn()
}, 0)
}
fn()
})
await tryToGetWindowPage()
this.context = this.electron.context()
this.page = await this.electron.firstWindow()
await this.context.tracing.start({ screenshots: true, snapshots: true })
}
@ -304,16 +333,13 @@ const fixturesForElectron = {
use: FnUse,
testInfo: TestInfo
) => {
await tronApp.createInstanceIfMissing(testInfo)
await use(tronApp.page)
await tronApp?.makeAvailableAgain()
},
context: async (
{ tronApp }: { tronApp: ElectronZoo },
use: FnUse,
testInfo: TestInfo
) => {
await tronApp.createInstanceIfMissing(testInfo)
await use(tronApp.context)
},
}

View File

@ -1408,7 +1408,7 @@ sketch002 = startSketchOn('XZ')
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await scene.expectPixelColor([135, 64, 73], testPoint, 15)
// await scene.expectPixelColor([135, 64, 73], testPoint, 15) // FIXME
await editor.expectEditor.toContain(sweepDeclaration)
await editor.expectState({
diagnostics: [],

View File

@ -233,7 +233,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
await cmdBar.openCmdBar('promptToEdit')
await page
.getByTestId('cmd-bar-arg-value')
.fill('Please rename to mySketch')
.fill('Please rename to mySketch001')
await page.waitForTimeout(100)
await cmdBar.progressCmdBar()
await expect(submittingToast).toBeVisible()
@ -244,10 +244,10 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
})
await test.step('verify rename change and accept it', async () => {
await editor.expectEditor.toContain('mySketch = startSketchOn')
await editor.expectEditor.toContain('mySketch001 = startSketchOn')
await editor.expectEditor.not.toContain('sketch002 = startSketchOn')
await editor.expectEditor.toContain(
'extrude002 = extrude(mySketch, length = 50)'
'extrude002 = extrude(mySketch001, length = 50)'
)
await acceptBtn.click()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -84,7 +84,6 @@ test.describe('Test network and connection issues', () => {
'Engine disconnect & reconnect in sketch mode',
{ tag: '@skipLocalEngine' },
async ({ page, homePage }) => {
// TODO: Don't skip Mac for these. After `window.engineCommandManager.tearDown` is working in Safari, these should work on webkit
const networkToggle = page.getByTestId('network-toggle')
const u = await getUtils(page)

View File

@ -430,7 +430,8 @@ test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
await expect(page.getByText(promptWithNewline)).toBeVisible()
})
test(
// This will be fine once greg makes prompt at top of file deterministic
test.fixme(
'can do many at once and get many prompts back, and interact with many',
{ tag: ['@skipWin'] },
async ({ page, homePage }) => {
@ -491,8 +492,15 @@ test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
// Click the button.
await copyToClipboardButton.first().click()
// Expect the code to be pasted.
await expect(page.locator('.cm-content')).toContainText(`2x8`)
// Do NOT do AI tests like this: "Expect the code to be pasted."
// Reason: AI tests are NONDETERMINISTIC. Thus we need to be as most
// general as we can for the assertion.
// We can use Kolmogorov complexity as a measurement of the
// "probably most minimal version of this program" to have a lower
// bound to work with. It is completely by feel because there are
// no proofs that any program is its smallest self.
const code2x8 = await page.locator('.cm-content').innerText()
await expect(code2x8.length).toBeGreaterThan(249)
// Ensure the final toast remains.
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
@ -505,7 +513,8 @@ test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
await copyToClipboardButton.click()
// Expect the code to be pasted.
await expect(page.locator('.cm-content')).toContainText(`2x4`)
const code2x4 = await page.locator('.cm-content').innerText()
await expect(code2x4.length).toBeGreaterThan(249)
}
)

View File

@ -45,7 +45,9 @@ const playwrightTestFnWithFixtures_ = playwrightTestFn.extend<{
return
}
await electronZooInstance.createInstanceIfMissing(testInfo)
await use(electronZooInstance)
await electronZooInstance.makeAvailableAgain()
},
})

View File

@ -93,7 +93,7 @@
"fetch:wasm:windows": "./scripts/get-latest-wasm-bundle.ps1",
"fetch:samples": "rm -rf public/kcl-samples* && curl -L -o public/kcl-samples.zip https://github.com/KittyCAD/kcl-samples/archive/refs/heads/achalmers/kw-args-xylineto.zip && unzip -o public/kcl-samples.zip -d public && mv public/kcl-samples-* public/kcl-samples",
"build:wasm-dev": "yarn wasm-prep && (cd rust && wasm-pack build kcl-wasm-lib --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
"build:wasm:nocopy": "yarn wasm-prep && cd rust && wasm-pack build kcl-wasm-lib --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings",
"build:wasm:nocopy": "yarn wasm-prep && cd rust && RUSTFLAGS='--cfg getrandom_backend=\"wasm_js\"' wasm-pack build kcl-wasm-lib --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings",
"build:wasm": "yarn build:wasm:nocopy && cp rust/kcl-wasm-lib/pkg/kcl_wasm_lib_bg.wasm public && yarn fmt",
"build:wasm:windows": "yarn install:wasm-pack:cargo && yarn build:wasm:nocopy && ./scripts/copy-wasm.ps1 && yarn fmt",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\" || echo \"sed for both mac and linux\"",

View File

@ -23,14 +23,14 @@ KCL samples conform to a set of style guidelines to ensure consistency and reada
When you submit a PR to add or modify KCL samples, images and STEP files will be generated and added to the repository automatically.
---
#### [3d-boaty](3d-boaty/main.kcl) ([step](step/3d-boaty.step)) ([screenshot](screenshots/3d-boaty.png))
[![3d-boaty](screenshots/3d-boaty.png)](3d-boaty/main.kcl)
#### [80-20-rail](80-20-rail/main.kcl) ([step](step/80-20-rail.step)) ([screenshot](screenshots/80-20-rail.png))
[![80-20-rail](screenshots/80-20-rail.png)](80-20-rail/main.kcl)
#### [a-parametric-bearing-pillow-block](a-parametric-bearing-pillow-block/main.kcl) ([step](step/a-parametric-bearing-pillow-block.step)) ([screenshot](screenshots/a-parametric-bearing-pillow-block.png))
[![a-parametric-bearing-pillow-block](screenshots/a-parametric-bearing-pillow-block.png)](a-parametric-bearing-pillow-block/main.kcl)
#### [ball-bearing](ball-bearing/main.kcl) ([step](step/ball-bearing.step)) ([screenshot](screenshots/ball-bearing.png))
[![ball-bearing](screenshots/ball-bearing.png)](ball-bearing/main.kcl)
#### [bench](bench/main.kcl) ([step](step/bench.step)) ([screenshot](screenshots/bench.png))
[![bench](screenshots/bench.png)](bench/main.kcl)
#### [bracket](bracket/main.kcl) ([step](step/bracket.step)) ([screenshot](screenshots/bracket.png))
[![bracket](screenshots/bracket.png)](bracket/main.kcl)
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([step](step/car-wheel-assembly.step)) ([screenshot](screenshots/car-wheel-assembly.png))
@ -45,10 +45,8 @@ When you submit a PR to add or modify KCL samples, images and STEP files will be
[![enclosure](screenshots/enclosure.png)](enclosure/main.kcl)
#### [exhaust-manifold](exhaust-manifold/main.kcl) ([step](step/exhaust-manifold.step)) ([screenshot](screenshots/exhaust-manifold.png))
[![exhaust-manifold](screenshots/exhaust-manifold.png)](exhaust-manifold/main.kcl)
#### [flange-with-patterns](flange-with-patterns/main.kcl) ([step](step/flange-with-patterns.step)) ([screenshot](screenshots/flange-with-patterns.png))
[![flange-with-patterns](screenshots/flange-with-patterns.png)](flange-with-patterns/main.kcl)
#### [flange-xy](flange-xy/main.kcl) ([step](step/flange-xy.step)) ([screenshot](screenshots/flange-xy.png))
[![flange-xy](screenshots/flange-xy.png)](flange-xy/main.kcl)
#### [flange](flange/main.kcl) ([step](step/flange.step)) ([screenshot](screenshots/flange.png))
[![flange](screenshots/flange.png)](flange/main.kcl)
#### [focusrite-scarlett-mounting-bracket](focusrite-scarlett-mounting-bracket/main.kcl) ([step](step/focusrite-scarlett-mounting-bracket.step)) ([screenshot](screenshots/focusrite-scarlett-mounting-bracket.png))
[![focusrite-scarlett-mounting-bracket](screenshots/focusrite-scarlett-mounting-bracket.png)](focusrite-scarlett-mounting-bracket/main.kcl)
#### [food-service-spatula](food-service-spatula/main.kcl) ([step](step/food-service-spatula.step)) ([screenshot](screenshots/food-service-spatula.png))

View File

@ -15,90 +15,57 @@ padding = 1.5
bearingDia = 3
// (Needs to be updated). Sketch the block and extrude up to where the counterbore diameter starts.
block = startSketchOn('XY')
extrude001 = startSketchOn('XY')
|> startProfileAt([-width / 2, -length / 2], %)
|> line(endAbsolute = [width / 2, -length / 2])
|> line(endAbsolute = [width / 2, length / 2])
|> line(endAbsolute = [-width / 2, length / 2])
|> close()
|> hole(circle(
center = [
|> extrude(length = height)
extrude002 = startSketchOn(extrude001, 'end')
|> circle(
center = [
-(width / 2 - (padding / 2)),
-(length / 2 - (padding / 2))
],
radius = holeDia / 2
), %)
|> hole(circle(
center = [
-(width / 2 - (padding / 2)),
length / 2 - (padding / 2)
],
radius = holeDia / 2
), %)
|> hole(circle(
center = [
width / 2 - (padding / 2),
length / 2 - (padding / 2)
],
radius = holeDia / 2
), %)
|> hole(circle(
center = [
width / 2 - (padding / 2),
-(length / 2 - (padding / 2))
],
radius = holeDia / 2
), %)
|> hole(circle(
center = [0, 0],
radius = bearingDia / 2
), %)
|> extrude(length = height - cbDepth)
],
radius = cbDia / 2,
)
|> patternLinear2d(
instances = 2,
distance = length - padding,
axis = [0, 1],
)
|> patternLinear2d(
instances = 2,
distance = width - padding,
axis = [1, 0],
)
|> extrude(%, length = -cbDepth)
// Create a second sketch that creates the counterbore diameters and extrude the rest of the way to get the total height. Note: You cannot use startSketchOn(block, 'end'). The extrude lives outside the bounds, and the engine will not execute. This is a known issue.
secondHalf = startSketchOn({
plane = {
origin = { x = 0, y = 0, z = height - cbDepth },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 }
}
})
|> startProfileAt([-width / 2, -length / 2], %)
|> line(endAbsolute = [width / 2, -length / 2])
|> line(endAbsolute = [width / 2, length / 2])
|> line(endAbsolute = [-width / 2, length / 2])
|> close()
|> hole(circle(
center = [
-(width / 2 - (padding / 2)),
-(length / 2 - (padding / 2))
],
radius = cbDia / 2
), %)
|> hole(circle(
center = [
-(width / 2 - (padding / 2)),
length / 2 - (padding / 2)
],
radius = cbDia / 2
), %)
|> hole(circle(
center = [
width / 2 - (padding / 2),
length / 2 - (padding / 2)
],
radius = cbDia / 2
), %)
|> hole(circle(
center = [
width / 2 - (padding / 2),
-(length / 2 - (padding / 2))
],
radius = cbDia / 2
), %)
|> hole(circle(
center = [0, 0],
radius = bearingDia / 2
), %)
|> extrude(length = cbDepth)
extrude003 = startSketchOn(extrude001, 'start')
|> circle(
center = [
-(width / 2 - (padding / 2)),
-(length / 2 - (padding / 2))
],
radius = holeDia / 2,
)
|> patternLinear2d(
instances = 2,
distance = length - padding,
axis = [0, 1],
)
|> patternLinear2d(
instances = 2,
distance = width - padding,
axis = [1, 0],
)
|> extrude(length = -height + cbDepth)
extrude004 = startSketchOn(extrude001, 'end')
|> circle(
center = [0, 0],
radius = bearingDia/2,
)
|> extrude(length = -height)

View File

@ -16,21 +16,8 @@ chainWidth = sphereDia / 2
chainThickness = sphereDia / 8
linkDiameter = sphereDia / 4
customPlane = {
plane = {
origin = {
x = 0,
y = 0,
z = -overallThickness / 2
},
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 }
}
}
// Sketch the inside bearing piece
insideWallSketch = startSketchOn(customPlane)
insideWallSketch = startSketchOn(offsetPlane("XY", offset = -overallThickness / 2))
|> circle(
center = [0, 0],
radius = shaftDia / 2 + wallThickness
@ -109,7 +96,7 @@ linkRevolve = revolve({ axis = 'Y', angle = 360 / nBalls }, linkSketch)
)
// Create the sketch for the outside walls
outsideWallSketch = startSketchOn(customPlane)
outsideWallSketch = startSketchOn(offsetPlane("XY", offset = -overallThickness / 2))
|> circle(
center = [0, 0],
radius = outsideDiameter / 2

View File

@ -1,4 +1,4 @@
// 3D Boaty
// Bench
// 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.
// Set units in millimeters (mm)
@ -8,12 +8,12 @@
benchLength = 56
// Import various constants and functions from our library
import dividerThickness from "boat-parts.kcl"
import divider from "boat-parts.kcl"
import connector from "boat-parts.kcl"
import seatSlats from "boat-parts.kcl"
import backSlats from "boat-parts.kcl"
import armRest from "boat-parts.kcl"
import dividerThickness from "bench-parts.kcl"
import divider from "bench-parts.kcl"
import connector from "bench-parts.kcl"
import seatSlats from "bench-parts.kcl"
import backSlats from "bench-parts.kcl"
import armRest from "bench-parts.kcl"
// Create the dividers, these hold the seat and back slats
divider("YZ")

View File

@ -1,113 +1,76 @@
// Shelf Bracket
// This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided.
// Set units
@settings(defaultLengthUnit = in)
// Define constants
sigmaAllow = 35000 // psi (6061-T6 aluminum)
width = 6
width = 6 // inch
p = 300 // Force on shelf - lbs
factorOfSafety = 1.2 // FOS of 1.2
shelfMountL = 5
wallMountL = 2
shelfMountL = 5 // inches
wallMountL = 2 // inches
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
filletRadius = .375
extFilletRadius = .25
mountingHoleDiameter = 0.5
// Calculate required thickness of bracket
thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
filletRadius = .25
extFilletRadius = filletRadius + thickness
mountingHoleDiameter = 0.5
// Sketch the bracket body and fillet the inner and outer edges of the bend
bracketLeg1Sketch = startSketchOn('XY')
sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line(end = [shelfMountL - filletRadius, 0], tag = $fillet1)
|> line(end = [0, width], tag = $fillet2)
|> line(end = [-shelfMountL + filletRadius, 0])
|> xLine(length = shelfMountL - thickness, tag = $seg01)
|> yLine(length = thickness, tag = $seg02)
|> xLine(length = -shelfMountL, tag = $seg03)
|> yLine(length = -wallMountL, tag = $seg04)
|> xLine(length = thickness, tag = $seg05)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg06)
|> close()
|> hole(circle(
center = [1, 1],
radius = mountingHoleDiameter / 2
), %)
|> hole(circle(
center = [shelfMountL - 1.5, width - 1],
radius = mountingHoleDiameter / 2
), %)
|> hole(circle(
center = [1, width - 1],
radius = mountingHoleDiameter / 2
), %)
|> hole(circle(
center = [shelfMountL - 1.5, 1],
radius = mountingHoleDiameter / 2
), %)
// Extrude the leg 2 bracket sketch
bracketLeg1Extrude = extrude(bracketLeg1Sketch, length = thickness)
|> extrude(%, length = width)
|> fillet(
radius = extFilletRadius,
tags = [
getNextAdjacentEdge(fillet1),
getNextAdjacentEdge(fillet2)
]
tags = [getNextAdjacentEdge(seg03)],
)
// Sketch the fillet arc
filletSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line(end = [0, thickness])
|> arc({
angleEnd = 180,
angleStart = 90,
radius = filletRadius + thickness
}, %)
|> line(end = [thickness, 0])
|> arc({
angleEnd = 90,
angleStart = 180,
radius = filletRadius
}, %)
// Sketch the bend
filletExtrude = extrude(filletSketch, length = -width)
// Create a custom plane for the leg that sits on the wall
customPlane = {
plane = {
origin = { x = -filletRadius, y = 0, z = 0 },
xAxis = { x = 0, y = 1, z = 0 },
yAxis = { x = 0, y = 0, z = 1 },
zAxis = { x = 1, y = 0, z = 0 }
}
}
// Create a sketch for the second leg
bracketLeg2Sketch = startSketchOn(customPlane)
|> startProfileAt([0, -filletRadius], %)
|> line(end = [width, 0])
|> line(end = [0, -wallMountL], tag = $fillet3)
|> line(end = [-width, 0], tag = $fillet4)
|> close()
|> hole(circle(
center = [1, -1.5],
radius = mountingHoleDiameter / 2
), %)
|> hole(circle(
center = [5, -1.5],
radius = mountingHoleDiameter / 2
), %)
// Extrude the second leg
bracketLeg2Extrude = extrude(bracketLeg2Sketch, length = -thickness)
|> fillet(
radius = extFilletRadius,
tags = [
getNextAdjacentEdge(fillet3),
getNextAdjacentEdge(fillet4)
]
radius = filletRadius,
tags = [getNextAdjacentEdge(seg06)],
)
|> fillet(
radius = filletRadius,
tags = [seg02, getOppositeEdge(seg02)],
)
|> fillet(
radius = filletRadius,
tags = [seg05, getOppositeEdge(seg05)],
)
sketch002 = startSketchOn(sketch001, seg03)
|> circle(
center = [-1.25, 1],
radius = mountingHoleDiameter / 2,
)
|> patternLinear2d(
instances = 2,
distance = 2.5,
axis = [-1, 0],
)
|> patternLinear2d(
instances = 2,
distance = 4,
axis = [0, 1],
)
|> extrude(%, length = -thickness-.01)
sketch003 = startSketchOn(sketch001, seg04)
|> circle(
center = [1, -1],
radius = mountingHoleDiameter / 2,
)
|> patternLinear2d(
instances = 2,
distance = 4,
axis = [1, 0],
)
|> extrude(%, length = -thickness-0.1)

View File

@ -9,18 +9,8 @@
// Import Constants
import caliperTolerance, caliperPadLength, caliperThickness, caliperOuterEdgeRadius, caliperInnerEdgeRadius, rotorDiameter, rotorTotalThickness, yAxisOffset from "globals.kcl"
// Create the plane for the brake caliper. This is so it can match up with the rotor model.
brakeCaliperPlane = {
plane = {
origin = { x = 0, y = yAxisOffset, z = 0 },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 }
}
}
// Sketch the brake caliper profile
brakeCaliperSketch = startSketchOn(brakeCaliperPlane)
brakeCaliperSketch = startSketchOn('XY')
|> startProfileAt([
rotorDiameter / 2 + caliperTolerance,
0

View File

@ -9,64 +9,61 @@
// Import Constants
import rotorDiameter, rotorInnerDiameter, rotorSinglePlateThickness, rotorInnerDiameterThickness, lugHolePatternDia, lugSpacing, rotorTotalThickness, spacerPatternDiameter, spacerDiameter, spacerLength, spacerCount, wheelDiameter, lugCount, yAxisOffset, drillAndSlotCount from "globals.kcl"
rotorPlane = {
plane = {
origin = { x = 0, y = yAxisOffset, z = 0 },
xAxis = { x = -1, y = 0, z = 0 },
yAxis = { x = 0, y = 0, z = 1 },
zAxis = { x = 0, y = 1, z = 0 }
}
}
fn lugPattern(plane) {
lugHolePattern = circle(
plane,
center = [-lugSpacing / 2, 0],
radius = 0.315
)
|> patternCircular2d(
arcDegrees = 360,
center = [0, 0],
instances = lugCount,
rotateDuplicates = true
)
return lugHolePattern
}
rotorSketch = startSketchOn(rotorPlane)
rotorSketch = startSketchOn('XZ')
|> circle(
center = [0, 0],
radius = rotorDiameter / 2
)
|> hole(lugPattern(%), %)
rotor = extrude(rotorSketch, length = rotorSinglePlateThickness)
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
rotorBumpSketch = startSketchOn(rotorPlane)
rotorBumpSketch = startSketchOn(rotor, 'end')
|> circle(
center = [0, 0],
radius = rotorInnerDiameter / 2
)
|> hole(lugPattern(%), %)
rotorBump = extrude(rotorBumpSketch, length = -rotorInnerDiameterThickness)
rotorBump = extrude(rotorBumpSketch, length = rotorInnerDiameterThickness)
lugHoles = startSketchOn(rotorBump, 'end')
|> circle(
center = [-lugSpacing / 2, 0],
radius = 0.315
)
|> patternCircular2d(
arcDegrees = 360,
center = [0, 0],
instances = lugCount,
rotateDuplicates = true
)
|> extrude(%, length = -(rotorInnerDiameterThickness + rotorSinglePlateThickness))
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
rotorSecondaryPlatePlane = {
plane = {
origin = {
x = 0,
y = yAxisOffset + rotorTotalThickness * 0.75,
z = 0
},
xAxis = { x = -1, y = 0, z = 0 },
yAxis = { x = 0, y = 0, z = 1 },
zAxis = { x = 0, y = 1, z = 0 }
}
}
secondaryRotorSketch = startSketchOn(rotorSecondaryPlatePlane)
// (update when boolean is available)
centerSpacer = startSketchOn(rotor, 'start')
|> circle(%, center = [0, 0], radius = .25)
|> extrude(%, length = spacerLength)
secondaryRotorSketch = startSketchOn(centerSpacer, 'end')
|> circle(
center = [0, 0],
radius = rotorDiameter / 2
)
|> hole(lugPattern(%), %)
secondRotor = extrude(secondaryRotorSketch, length = rotorSinglePlateThickness)
spacerSketch = startSketchOn(rotorSecondaryPlatePlane)
lugHoles2 = startSketchOn(secondRotor, 'end')
|> circle(
center = [-lugSpacing / 2, 0],
radius = 0.315
)
|> patternCircular2d(
arcDegrees = 360,
center = [0, 0],
instances = lugCount,
rotateDuplicates = true
)
|> extrude(length = -rotorSinglePlateThickness)
spacerSketch = startSketchOn(rotor, 'start')
|> circle(
center = [spacerPatternDiameter / 2, 0],
radius = spacerDiameter
@ -77,8 +74,8 @@ spacerSketch = startSketchOn(rotorSecondaryPlatePlane)
instances = spacerCount,
rotateDuplicates = true
)
spacers = extrude(spacerSketch, length = -spacerLength)
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
spacers = extrude(spacerSketch, length = spacerLength)
rotorSlottedSketch = startSketchOn(rotor, 'START')
|> startProfileAt([2.17, 2.56], %)
|> xLine(length = 0.12)
@ -107,5 +104,6 @@ secondRotorSlottedSketch = startSketchOn(secondRotor, 'END')
arcDegrees = 360,
rotateDuplicates = true
)
secondRotorSlotted = extrude(secondRotorSlottedSketch, length = -rotorSinglePlateThickness / 2)
extrude(secondRotorSlottedSketch, length = -rotorSinglePlateThickness / 2)
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)

View File

@ -12,6 +12,7 @@ import 'car-tire.kcl' as carTire
import lugCount from 'globals.kcl'
carRotor
|> translate(translate = [0, 0.5, 0])
carWheel
lugNut
|> patternCircular3d(
@ -22,4 +23,5 @@ lugNut
rotateDuplicates = false
)
brakeCaliper
|> translate(translate = [0, 0.5, 0])
carTire

View File

@ -1,87 +0,0 @@
// Flange with XY coordinates
// A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others.
// Set units
@settings(defaultLengthUnit = in)
// Define constants
mountingHoleDia = .625
baseDia = 4.625
pipeDia = 1.25
thickness = .625
totalThickness = 0.813
topTotalDiameter = 2.313
bottomThickness = 0.06
bottomTotalDiameter = 2.5
mountingHolePlacementDiameter = 3.5
baseThickness = .625
topTotalThickness = totalThickness - (bottomThickness + baseThickness)
holeLocator = baseDia - 8
nHoles = 4
// Add assertion so nHoles are always greater than 1
assertGreaterThan(nHoles, 1, "nHoles must be greater than 1")
// Create the flange base and the six mounting holes
flangeBase = startSketchOn('XY')
|> circle(
center = [0, 0],
radius = baseDia / 2
)
|> hole(circle(
center = [mountingHolePlacementDiameter / 2, 0],
radius = mountingHoleDia / 2
), %)
|> hole(circle(
center = [0, mountingHolePlacementDiameter / 2],
radius = mountingHoleDia / 2
), %)
|> hole(circle(
center = [-mountingHolePlacementDiameter / 2, 0],
radius = mountingHoleDia / 2
), %)
|> hole(circle(
center = [0, -mountingHolePlacementDiameter / 2],
radius = mountingHoleDia / 2
), %)
|> hole(circle(
center = [0, 0],
radius = pipeDia / 2
), %)
|> extrude(length = baseThickness)
// Plane for top face
topFacePlane = {
plane = {
origin = { x = 0, y = 0, z = baseThickness },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 }
}
}
// Create the extrusion on the top of the flange base
topExtrusion = startSketchOn(topFacePlane, 'end')
|> circle(
center = [0, 0],
radius = topTotalDiameter / 2
)
|> hole(circle(
center = [0, 0],
radius = pipeDia / 2
), %)
|> extrude(length = topTotalThickness)
// Create the extrusion on the bottom of the flange base
bottomExtrusion = startSketchOn("XY")
|> circle(
center = [0, 0],
radius = bottomTotalDiameter / 2
)
|> hole(circle(
center = [0, 0],
radius = pipeDia / 2
), %)
|> extrude(length = -bottomThickness)
// https://www.mcmaster.com/44685K193/

View File

@ -8,7 +8,6 @@
mountingHoleDia = .625
baseDia = 4.625
pipeDia = 1.25
thickness = .625
totalThickness = 0.813
topTotalDiameter = 2.313
bottomThickness = 0.06
@ -16,7 +15,6 @@ bottomTotalDiameter = 2.5
mountingHolePlacementDiameter = 3.5
baseThickness = .625
topTotalThickness = totalThickness - (bottomThickness + baseThickness)
holeLocator = baseDia - 8
nHoles = 4
// Add assertion so nHoles are always greater than 1
@ -42,42 +40,25 @@ flangeBase = startSketchOn('XY')
radius = baseDia / 2
)
|> hole(circles, %)
|> hole(circle(
center = [0, 0],
radius = pipeDia / 2
), %)
|> extrude(length = baseThickness)
// Plane for top face
topFacePlane = {
plane = {
origin = { x = 0, y = 0, z = baseThickness },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 }
}
}
// Create the extrusion on the top of the flange base
topExtrusion = startSketchOn(topFacePlane)
topExtrusion = startSketchOn(flangeBase, 'end')
|> circle(
center = [0, 0],
radius = topTotalDiameter / 2
)
|> hole(circle(
center = [0, 0],
radius = pipeDia / 2
), %)
|> extrude(length = topTotalThickness)
// Create the extrusion on the bottom of the flange base
bottomExtrusion = startSketchOn("XY")
bottomExtrusion = startSketchOn(flangeBase, 'start')
|> circle(
center = [0, 0],
radius = bottomTotalDiameter / 2
)
|> hole(circle(
center = [0, 0],
radius = pipeDia / 2
), %)
|> extrude(length = -bottomThickness)
|> extrude(length = bottomThickness)
// Cut a hole through the entire body
pipeHole = startSketchOn(topExtrusion, 'end')
|> circle(center = [0, 0], radius = pipeDia/2)
|> extrude(%, length = -(topTotalThickness + baseThickness + bottomThickness))

View File

@ -5,8 +5,8 @@
@settings(defaultLengthUnit = in)
// Define constants
lbumps = 5 // number of bumps long
wbumps = 3 // number of bumps wide
lbumps = 10 // number of bumps long
wbumps = 5 // number of bumps wide
pitch = 8.0
clearance = 0.1
bumpDiam = 4.8
@ -25,28 +25,8 @@ wSegments = totalWidth / wbumps
assertGreaterThan(lbumps, 1, "lbumps must be greater than 1")
assertGreaterThan(wbumps, 1, "wbumps must be greater than 1")
// Create the plane for the pegs. This is a hack so that the pegs can be patterned along the face of the lego base.
pegFace = {
plane = {
origin = { x = 0, y = 0, z = height },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 }
}
}
// Create the plane for the tubes underneath the lego. This is a hack so that the tubes can be patterned underneath the lego.
tubeFace = {
plane = {
origin = { x = 0, y = 0, z = height - t },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 }
}
}
// Make the base
s = startSketchOn('XY')
base = startSketchOn('XY')
|> startProfileAt([-totalWidth / 2, -totalLength / 2], %)
|> line(end = [totalWidth, 0])
|> line(end = [0, totalLength])
@ -54,8 +34,8 @@ s = startSketchOn('XY')
|> close()
|> extrude(length = height)
// Sketch and extrude a rectangular shape to create the shell underneath the lego. This is a hack until we have a shell function.
shellExtrude = startSketchOn(s, "start")
// Sketch and extrude a rectangular shape to create the shell underneath the lego. Will replace with shell function when able to call a face created from shell.
shellExtrude = startSketchOn(base, "start")
|> startProfileAt([
-(totalWidth / 2 - t),
-(totalLength / 2 - t)
@ -67,7 +47,7 @@ shellExtrude = startSketchOn(s, "start")
|> extrude(length = -(height - t))
// Create the pegs on the top of the base
peg = startSketchOn(s, 'end')
peg = startSketchOn(base, 'end')
|> circle(
center = [
-(pitch * (wbumps - 1) / 2),
@ -88,7 +68,7 @@ peg = startSketchOn(s, 'end')
|> extrude(length = bumpHeight)
// Create the pegs on the bottom of the base
tubePattern = startSketchOn(tubeFace)
tubePattern = startSketchOn(shellExtrude, 'start')
|> circle(
center = [
-(pitch * (wbumps - 1) / 2 - (pitch / 2)),
@ -106,4 +86,4 @@ tubePattern = startSketchOn(tubeFace)
instances = lbumps - 1,
distance = pitch
)
|> extrude(length = -bumpHeight)
|> extrude(length = bumpHeight)

View File

@ -1,11 +1,4 @@
[
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "3d-boaty/main.kcl",
"multipleFiles": true,
"title": "3D Boaty",
"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": "80-20-rail/main.kcl",
@ -27,6 +20,13 @@
"title": "Ball Bearing",
"description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads."
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "bench/main.kcl",
"multipleFiles": true,
"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": "bracket/main.kcl",
@ -78,18 +78,11 @@
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "flange-with-patterns/main.kcl",
"pathFromProjectDirectoryToFirstFile": "flange/main.kcl",
"multipleFiles": false,
"title": "Flange",
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "flange-xy/main.kcl",
"multipleFiles": false,
"title": "Flange with XY coordinates",
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "focusrite-scarlett-mounting-bracket/main.kcl",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 96 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,5 @@
[alias]
kcl-language-server-release = "run --manifest-path ./kcl-language-server-release/Cargo.toml --"
[target.wasm32-unknown-unknown]
rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""]

707
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -34,8 +34,8 @@ clap = { version = "4.5.31", features = ["derive"] }
dashmap = { version = "6.1.0" }
http = "1"
indexmap = "2.7.0"
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.104", features = ["ts-rs", "websocket"] }
kittycad = { version = "0.3.33", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.105", features = ["ts-rs", "websocket"] }
lazy_static = "1.5.0"
miette = "7.5.0"
pyo3 = { version = "0.24.0" }

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-bumper"
version = "0.1.49"
version = "0.1.50"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-api"
rust-version = "1.76"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.49"
version = "0.1.50"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-directory-test-macro"
description = "A tool for generating tests from a directory of kcl files"
version = "0.1.49"
version = "0.1.50"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-language-server-release"
version = "0.1.49"
version = "0.1.50"
edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false

View File

@ -2,7 +2,7 @@
name = "kcl-language-server"
description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.49"
version = "0.2.50"
edition = "2021"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.49"
version = "0.2.50"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -152,7 +152,9 @@ harness = false
[[test]]
name = "executor"
path = "e2e/executor/main.rs"
required-features = ["engine"]
[[test]]
name = "modify"
path = "e2e/modify/main.rs"
required-features = ["engine"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -243,6 +243,30 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
Ok(())
}
// Add a vector of modeling commands to the batch but don't fire it right away.
// This allows you to force them all to be added together in the same order.
// When we are running things in parallel this prevents race conditions that might come
// if specific commands are run before others.
async fn batch_modeling_cmds(
&self,
source_range: SourceRange,
cmds: &[ModelingCmdReq],
) -> Result<(), crate::errors::KclError> {
// In isolated mode, we don't send the command to the engine.
if self.execution_kind().await.is_isolated() {
return Ok(());
}
// Add cmds to the batch.
let mut extended_cmds = Vec::with_capacity(cmds.len());
for cmd in cmds {
extended_cmds.push((WebSocketRequest::ModelingCmdReq(cmd.clone()), source_range));
}
self.batch().write().await.extend(extended_cmds);
Ok(())
}
/// Add a command to the batch that needs to be executed at the very end.
/// This for stuff like fillets or chamfers where if we execute too soon the
/// engine will eat the ID and we can't reference it for other commands.

View File

@ -6,7 +6,7 @@ use kittycad_modeling_cmds::{
ok_response::OkModelingCmdResponse,
shared::ExtrusionFaceCapType,
websocket::{BatchResponse, OkWebSocketResponseData, WebSocketResponse},
EnableSketchMode, ModelingCmd, SketchModeDisable,
EnableSketchMode, ModelingCmd,
};
use schemars::JsonSchema;
use serde::{ser::SerializeSeq, Deserialize, Serialize};
@ -498,13 +498,23 @@ pub(super) fn build_artifact_graph(
) -> Result<ArtifactGraph, KclError> {
let mut map = IndexMap::new();
let mut path_to_plane_id_map = FnvHashMap::default();
let mut current_plane_id = None;
for artifact_command in artifact_commands {
if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
current_plane_id = Some(entity_id);
}
if let ModelingCmd::SketchModeDisable(SketchModeDisable { .. }) = artifact_command.command {
// If we get a start path command, we need to set the plane ID to the
// current plane ID.
// THIS IS THE ONLY THING WE CAN ASSUME IS ALWAYS SEQUENTIAL SINCE ITS PART OF THE
// SAME ATOMIC COMMANDS BATCHING.
if let ModelingCmd::StartPath(_) = artifact_command.command {
if let Some(plane_id) = current_plane_id {
path_to_plane_id_map.insert(artifact_command.cmd_id, plane_id);
}
}
if let ModelingCmd::SketchModeDisable(_) = artifact_command.command {
current_plane_id = None;
}
@ -513,7 +523,7 @@ pub(super) fn build_artifact_graph(
&map,
artifact_command,
&flattened_responses,
current_plane_id,
&path_to_plane_id_map,
ast,
exec_artifacts,
)?;
@ -609,7 +619,7 @@ fn artifacts_to_update(
artifacts: &IndexMap<ArtifactId, Artifact>,
artifact_command: &ArtifactCommand,
responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
current_plane_id: Option<Uuid>,
path_to_plane_id_map: &FnvHashMap<Uuid, Uuid>,
_ast: &Node<Program>,
exec_artifacts: &IndexMap<ArtifactId, Artifact>,
) -> Result<Vec<Artifact>, KclError> {
@ -643,20 +653,12 @@ fn artifacts_to_update(
code_ref: CodeRef { range, path_to_node },
})]);
}
ModelingCmd::EnableSketchMode(_) => {
let current_plane_id = current_plane_id.ok_or_else(|| {
KclError::Internal(KclErrorDetails {
message: format!(
"Expected a current plane ID when processing EnableSketchMode command, but we have none: {id:?}"
),
source_ranges: vec![range],
})
})?;
let existing_plane = artifacts.get(&ArtifactId::new(current_plane_id));
ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
let existing_plane = artifacts.get(&ArtifactId::new(*entity_id));
match existing_plane {
Some(Artifact::Wall(wall)) => {
return Ok(vec![Artifact::Wall(Wall {
id: current_plane_id.into(),
id: entity_id.into(),
seg_id: wall.seg_id,
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
sweep_id: wall.sweep_id,
@ -666,7 +668,7 @@ fn artifacts_to_update(
}
Some(Artifact::Cap(cap)) => {
return Ok(vec![Artifact::Cap(Cap {
id: current_plane_id.into(),
id: entity_id.into(),
sub_type: cap.sub_type,
edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
sweep_id: cap.sweep_id,
@ -680,7 +682,7 @@ fn artifacts_to_update(
_ => Vec::new(),
};
return Ok(vec![Artifact::Plane(Plane {
id: current_plane_id.into(),
id: entity_id.into(),
path_ids,
code_ref: CodeRef { range, path_to_node },
})]);
@ -689,7 +691,7 @@ fn artifacts_to_update(
}
ModelingCmd::StartPath(_) => {
let mut return_arr = Vec::new();
let current_plane_id = current_plane_id.ok_or_else(|| {
let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
KclError::Internal(KclErrorDetails {
message: format!(
"Expected a current plane ID when processing StartPath command, but we have none: {id:?}"
@ -699,24 +701,24 @@ fn artifacts_to_update(
})?;
return_arr.push(Artifact::Path(Path {
id,
plane_id: current_plane_id.into(),
plane_id: (*current_plane_id).into(),
seg_ids: Vec::new(),
sweep_id: None,
solid2d_id: None,
code_ref: CodeRef { range, path_to_node },
}));
let plane = artifacts.get(&ArtifactId::new(current_plane_id));
let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
if let Some(Artifact::Plane(plane)) = plane {
let code_ref = plane.code_ref.clone();
return_arr.push(Artifact::Plane(Plane {
id: current_plane_id.into(),
id: (*current_plane_id).into(),
path_ids: vec![id],
code_ref,
}));
}
if let Some(Artifact::Wall(wall)) = plane {
return_arr.push(Artifact::Wall(Wall {
id: current_plane_id.into(),
id: (*current_plane_id).into(),
seg_id: wall.seg_id,
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
sweep_id: wall.sweep_id,
@ -726,7 +728,7 @@ fn artifacts_to_update(
}
if let Some(Artifact::Cap(cap)) = plane {
return_arr.push(Artifact::Cap(Cap {
id: current_plane_id.into(),
id: (*current_plane_id).into(),
sub_type: cap.sub_type,
edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
sweep_id: cap.sweep_id,

View File

@ -3,15 +3,14 @@ use std::ops::{Add, AddAssign, Mul};
use anyhow::Result;
use indexmap::IndexMap;
use kittycad_modeling_cmds as kcmc;
use kittycad_modeling_cmds::length_unit::LengthUnit;
use kittycad_modeling_cmds::{each_cmd as mcmd, length_unit::LengthUnit, websocket::ModelingCmdReq, ModelingCmd};
use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::ArtifactId;
use crate::{
errors::KclError,
execution::{ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
execution::{ArtifactId, ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
std::sketch::PlaneData,
};
@ -227,18 +226,11 @@ pub struct ImportedGeometry {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
#[allow(clippy::vec_box)]
pub enum SolidOrImportedGeometry {
Solid(Box<Solid>),
ImportedGeometry(Box<ImportedGeometry>),
}
impl SolidOrImportedGeometry {
pub fn id(&self) -> uuid::Uuid {
match self {
SolidOrImportedGeometry::Solid(s) => s.id,
SolidOrImportedGeometry::ImportedGeometry(s) => s.id,
}
}
SolidSet(Vec<Box<Solid>>),
}
impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
@ -246,6 +238,17 @@ impl From<SolidOrImportedGeometry> for crate::execution::KclValue {
match value {
SolidOrImportedGeometry::Solid(s) => crate::execution::KclValue::Solid { value: s },
SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s),
SolidOrImportedGeometry::SolidSet(s) => crate::execution::KclValue::Solids { value: s },
}
}
}
impl SolidOrImportedGeometry {
pub(crate) fn ids(&self) -> Vec<uuid::Uuid> {
match self {
SolidOrImportedGeometry::Solid(s) => vec![s.id],
SolidOrImportedGeometry::ImportedGeometry(s) => vec![s.id],
SolidOrImportedGeometry::SolidSet(s) => s.iter().map(|s| s.id).collect(),
}
}
}
@ -532,6 +535,41 @@ pub struct Sketch {
pub meta: Vec<Metadata>,
}
impl Sketch {
// Tell the engine to enter sketch mode on the sketch.
// Run a specific command, then exit sketch mode.
pub(crate) fn build_sketch_mode_cmds(
&self,
exec_state: &mut ExecState,
inner_cmd: ModelingCmdReq,
) -> Vec<ModelingCmdReq> {
vec![
// Before we extrude, we need to enable the sketch mode.
// We do this here in case extrude is called out of order.
ModelingCmdReq {
cmd: ModelingCmd::from(mcmd::EnableSketchMode {
animated: false,
ortho: false,
entity_id: self.on.id(),
adjust_camera: false,
planar_normal: if let SketchSurface::Plane(plane) = &self.on {
// We pass in the normal for the plane here.
Some(plane.z_axis.into())
} else {
None
},
}),
cmd_id: exec_state.next_uuid().into(),
},
inner_cmd,
ModelingCmdReq {
cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
cmd_id: exec_state.next_uuid().into(),
},
]
}
}
/// A sketch type.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]

View File

@ -4,17 +4,17 @@
use std::{cell::RefCell, collections::BTreeMap};
use winnow::{
combinator::{alt, delimited, opt, peek, preceded, repeat, separated, separated_pair, terminated},
combinator::{alt, delimited, opt, peek, preceded, repeat, repeat_till, separated, separated_pair, terminated},
dispatch,
error::{ErrMode, StrContext, StrContextValue},
prelude::*,
stream::Stream,
token::{any, one_of, take_till},
token::{any, none_of, one_of, take_till},
};
use super::{
ast::types::{Ascription, ImportPath, LabelledExpression},
token::NumericSuffix,
token::{NumericSuffix, RESERVED_WORDS},
};
use crate::{
docs::StdLibFn,
@ -746,21 +746,58 @@ pub(crate) fn array_elem_by_elem(i: &mut TokenSlice) -> PResult<Node<ArrayExpres
)
.context(expected("array contents, a list of elements (like [1, 2, 3])"))
.parse_next(i)?;
ignore_trailing_comma(i);
ignore_whitespace(i);
let end = close_bracket(i)
.map_err(|e| {
if let Some(mut err) = e.clone().into_inner() {
err.cause = Some(CompilationError::fatal(
open.as_source_range(),
"Array is missing a closing bracket(`]`)",
));
ErrMode::Cut(err)
} else {
// ErrMode::Incomplete, not sure if it's actually possible to end up with this here
e
}
})?
.end;
let maybe_end = close_bracket(i).map_err(|e| {
if let Some(mut err) = e.clone().into_inner() {
let start_range = open.as_source_range();
let end_range = i.as_source_range();
err.cause = Some(CompilationError::fatal(
SourceRange::from([start_range.start(), end_range.start(), end_range.module_id().as_usize()]),
"Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
));
ErrMode::Cut(err)
} else {
// ErrMode::Incomplete, not sure if it's actually possible to end up with this here
e
}
});
if maybe_end.is_err() {
// if there is a closing bracket at some point, but it wasn't the next token, it's likely that they forgot a comma between some
// of the elements
let maybe_closing_bracket: PResult<((), Token)> = peek(repeat_till(
0..,
none_of(|token: Token| {
// bail out early if we encounter something that is for sure not allowed in an
// array, otherwise we could seek to find a closing bracket until the end of the
// file
RESERVED_WORDS
.keys()
.chain([",,", "{", "}", "["].iter())
.any(|word| *word == token.value)
})
.void(),
one_of(|term: Token| term.value == "]"),
))
.parse_next(i);
let has_closing_bracket = maybe_closing_bracket.is_ok();
if has_closing_bracket {
let start_range = i.as_source_range();
// safe to unwrap here because we checked it was Ok above
let end_range = maybe_closing_bracket.unwrap().1.as_source_range();
let e = ContextError {
context: vec![],
cause: Some(CompilationError::fatal(
SourceRange::from([start_range.start(), end_range.end(), end_range.module_id().as_usize()]),
"Unexpected character encountered. You might be missing a comma in between elements.",
)),
};
return Err(ErrMode::Cut(e));
}
}
let end = maybe_end?.end;
// Sort the array's elements (i.e. expression nodes) from the noncode nodes.
let (elements, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = elements.into_iter().enumerate().fold(
@ -819,7 +856,7 @@ fn array_end_start(i: &mut TokenSlice) -> PResult<Node<ArrayRangeExpression>> {
}
fn object_property_same_key_and_val(i: &mut TokenSlice) -> PResult<Node<ObjectProperty>> {
let key = nameable_identifier.context(expected("the property's key (the name or identifier of the property), e.g. in 'height: 4', 'height' is the property key")).parse_next(i)?;
let key = nameable_identifier.context(expected("the property's key (the name or identifier of the property), e.g. in 'height = 4', 'height' is the property key")).parse_next(i)?;
ignore_whitespace(i);
Ok(Node {
start: key.start,
@ -846,7 +883,7 @@ fn object_property(i: &mut TokenSlice) -> PResult<Node<ObjectProperty>> {
ignore_whitespace(i);
let expr = match expression
.context(expected(
"the value which you're setting the property to, e.g. in 'height: 4', the value is 4",
"the value which you're setting the property to, e.g. in 'height = 4', the value is 4",
))
.parse_next(i)
{
@ -892,7 +929,7 @@ fn property_separator(i: &mut TokenSlice) -> PResult<()> {
alt((
// Normally you need a comma.
comma_sep,
// But, if the array is ending, no need for a comma.
// But, if the object is ending, no need for a comma.
peek(preceded(opt(whitespace), close_brace)).void(),
))
.parse_next(i)
@ -926,10 +963,62 @@ pub(crate) fn object(i: &mut TokenSlice) -> PResult<Node<ObjectExpression>> {
)),
)
.context(expected(
"a comma-separated list of key-value pairs, e.g. 'height: 4, width: 3'",
"a comma-separated list of key-value pairs, e.g. 'height = 4, width = 3'",
))
.parse_next(i)?;
ignore_trailing_comma(i);
ignore_whitespace(i);
let maybe_end = close_brace(i).map_err(|e| {
if let Some(mut err) = e.clone().into_inner() {
let start_range = open.as_source_range();
let end_range = i.as_source_range();
err.cause = Some(CompilationError::fatal(
SourceRange::from([start_range.start(), end_range.start(), end_range.module_id().as_usize()]),
"Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
));
ErrMode::Cut(err)
} else {
// ErrMode::Incomplete, not sure if it's actually possible to end up with this here
e
}
});
if maybe_end.is_err() {
// if there is a closing brace at some point, but it wasn't the next token, it's likely that they forgot a comma between some
// of the properties
let maybe_closing_brace: PResult<((), Token)> = peek(repeat_till(
0..,
none_of(|token: Token| {
// bail out early if we encounter something that is for sure not allowed in an
// object, otherwise we could seek to find a closing brace until the end of the
// file
RESERVED_WORDS
.keys()
.chain([",,", "[", "]", "{"].iter())
.any(|word| *word == token.value)
})
.void(),
one_of(|c: Token| c.value == "}"),
))
.parse_next(i);
let has_closing_brace = maybe_closing_brace.is_ok();
if has_closing_brace {
let start_range = i.as_source_range();
// okay to unwrap here because we checked it was Ok above
let end_range = maybe_closing_brace.unwrap().1.as_source_range();
let e = ContextError {
context: vec![],
cause: Some(CompilationError::fatal(
SourceRange::from([start_range.start(), end_range.end(), end_range.module_id().as_usize()]),
"Unexpected character encountered. You might be missing a comma in between properties.",
)),
};
return Err(ErrMode::Cut(e));
}
}
let end = maybe_end?.end;
// Sort the object's properties from the noncode nodes.
let (properties, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = properties.into_iter().enumerate().fold(
(Vec::new(), BTreeMap::new()),
@ -945,9 +1034,7 @@ pub(crate) fn object(i: &mut TokenSlice) -> PResult<Node<ObjectExpression>> {
(properties, non_code_nodes)
},
);
ignore_trailing_comma(i);
ignore_whitespace(i);
let end = close_brace(i)?.end;
let non_code_meta = NonCodeMeta {
non_code_nodes,
..Default::default()
@ -3869,7 +3956,7 @@ mySk1 = startSketchOn(XY)
assert_eq!(
src_expected,
src_actual,
"expected error would highlight {} but it actually highlighted {}",
"expected error would highlight `{}` but it actually highlighted `{}`",
&p[src_expected[0]..src_expected[1]],
&p[src_actual[0]..src_actual[1]],
);
@ -4060,7 +4147,11 @@ z(-[["#,
#[test]
fn test_parse_weird_lots_of_fancy_brackets() {
assert_err(r#"zz({{{{{{{{)iegAng{{{{{{{##"#, "Unexpected token: (", [2, 3]);
assert_err(
r#"zz({{{{{{{{)iegAng{{{{{{{##"#,
"Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
[3, 4],
);
}
#[test]
@ -4601,10 +4692,123 @@ let myBox = box([0,0], -3, -16, -10)
}
#[test]
fn test_parse_missing_closing_bracket() {
fn test_parse_array_missing_closing_bracket() {
let some_program_string = r#"
sketch001 = startSketchOn('XZ') |> startProfileAt([90.45, 119.09, %)"#;
assert_err(some_program_string, "Array is missing a closing bracket(`]`)", [51, 52]);
assert_err(
some_program_string,
"Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
[51, 67],
);
}
#[test]
fn test_parse_array_missing_comma() {
let some_program_string = r#"
sketch001 = startSketchOn('XZ') |> startProfileAt([90.45 119.09], %)"#;
assert_err(
some_program_string,
"Unexpected character encountered. You might be missing a comma in between elements.",
[52, 65],
);
}
#[test]
fn test_parse_array_reserved_word_early_exit() {
// since there is an early exit if encountering a reserved word, the error should be about
// that and not the missing comma
let some_program_string = r#"
sketch001 = startSketchOn('XZ') |> startProfileAt([90.45 $struct], %)"#;
assert_err(
some_program_string,
"Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
[51, 52],
);
}
#[test]
fn test_parse_array_random_brace() {
let some_program_string = r#"
sketch001 = startSketchOn('XZ') |> startProfileAt([}], %)"#;
assert_err(
some_program_string,
"Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array",
[51, 52],
);
}
#[test]
fn test_parse_object_missing_closing_brace() {
let some_program_string = r#"{
foo = bar,"#;
assert_err(
some_program_string,
"Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
[0, 23],
);
}
#[test]
fn test_parse_object_reserved_word_early_exit() {
// since there is an early exit if encountering a reserved word, the error should be about
// that and not the missing comma
let some_program_string = r#"{bar = foo struct = man}"#;
assert_err(
some_program_string,
"Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
[0, 1],
);
}
#[test]
fn test_parse_object_missing_comma() {
let some_program_string = r#"{
foo = bar,
bar = foo
bat = man
}"#;
assert_err(
some_program_string,
"Unexpected character encountered. You might be missing a comma in between properties.",
[37, 78],
);
}
#[test]
fn test_parse_object_missing_comma_one_line() {
let some_program_string = r#"{bar = foo bat = man}"#;
assert_err(
some_program_string,
"Unexpected character encountered. You might be missing a comma in between properties.",
[1, 21],
);
}
#[test]
fn test_parse_object_random_bracket() {
let some_program_string = r#"{]}"#;
assert_err(
some_program_string,
"Encountered an unexpected character(s) before finding a closing brace(`}`) for the object",
[0, 1],
);
}
#[test]
fn test_parse_object_shorthand_missing_comma() {
let some_program_string = r#"
bar = 1
{
foo = bar,
bar
bat = man
}"#;
assert_err(
some_program_string,
"Unexpected character encountered. You might be missing a comma in between properties.",
[54, 89],
);
}
#[test]

View File

@ -24,7 +24,6 @@ use crate::{
mod tokeniser;
#[cfg(test)]
pub(crate) use tokeniser::RESERVED_WORDS;
// Note the ordering, it's important that `m` comes after `mm` and `cm`.
@ -162,7 +161,9 @@ impl IntoIterator for TokenStream {
#[derive(Debug, Clone)]
pub(crate) struct TokenSlice<'a> {
stream: &'a TokenStream,
/// Current position of the leading Token in the stream
start: usize,
/// The number of total Tokens in the stream
end: usize,
}
@ -190,6 +191,21 @@ impl<'a> TokenSlice<'a> {
stream: self.stream,
}
}
pub fn as_source_range(&self) -> SourceRange {
let stream_len = self.stream.tokens.len();
let first_token = if stream_len == self.start {
&self.stream.tokens[self.start - 1]
} else {
self.token(0)
};
let last_token = if stream_len == self.end {
&self.stream.tokens[stream_len - 1]
} else {
self.token(self.end - self.start)
};
SourceRange::new(first_token.start, last_token.end, last_token.module_id)
}
}
impl<'a> IntoIterator for TokenSlice<'a> {
@ -294,6 +310,14 @@ impl<'a> winnow::stream::StreamIsPartial for TokenSlice<'a> {
}
}
impl<'a> winnow::stream::FindSlice<&str> for TokenSlice<'a> {
fn find_slice(&self, substr: &str) -> Option<std::ops::Range<usize>> {
self.iter()
.enumerate()
.find_map(|(i, b)| if b.value == substr { Some(i..self.end) } else { None })
}
}
#[derive(Clone, Debug)]
pub struct Checkpoint(usize, usize);

View File

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

View File

@ -1,12 +1,14 @@
use std::{any::type_name, collections::HashMap, num::NonZeroU32};
use anyhow::Result;
use kcmc::{websocket::OkWebSocketResponseData, ModelingCmd};
use kcmc::{
websocket::{ModelingCmdReq, OkWebSocketResponseData},
ModelingCmd,
};
use kittycad_modeling_cmds as kcmc;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::shapes::PolygonType;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
@ -16,7 +18,11 @@ use crate::{
},
parsing::ast::types::TagNode,
source_range::SourceRange,
std::{shapes::SketchOrSurface, sketch::FaceTag, sweep::SweepPath},
std::{
shapes::{PolygonType, SketchOrSurface},
sketch::FaceTag,
sweep::SweepPath,
},
ModuleId,
};
@ -254,6 +260,11 @@ impl Args {
self.ctx.engine.batch_modeling_cmd(id, self.source_range, &cmd).await
}
// Add multiple modeling commands to the batch but don't fire them right away.
pub(crate) async fn batch_modeling_cmds(&self, cmds: &[ModelingCmdReq]) -> Result<(), crate::errors::KclError> {
self.ctx.engine.batch_modeling_cmds(self.source_range, cmds).await
}
// Add a modeling command to the batch that gets executed at the end of the file.
// This is good for something like fillet or chamfer where the engine would
// eat the path id if we executed it right away.
@ -1327,6 +1338,7 @@ impl<'a> FromKclValue<'a> for crate::execution::SolidOrImportedGeometry {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Solid { value } => Some(Self::Solid(value.clone())),
KclValue::Solids { value } => Some(Self::SolidSet(value.clone())),
KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
_ => None,
}

View File

@ -5,8 +5,13 @@ use std::collections::HashMap;
use anyhow::Result;
use kcl_derive_docs::stdlib;
use kcmc::{
each_cmd as mcmd, length_unit::LengthUnit, ok_response::OkModelingCmdResponse, output::ExtrusionFaceInfo,
shared::ExtrusionFaceCapType, websocket::OkWebSocketResponseData, ModelingCmd,
each_cmd as mcmd,
length_unit::LengthUnit,
ok_response::OkModelingCmdResponse,
output::ExtrusionFaceInfo,
shared::ExtrusionFaceCapType,
websocket::{ModelingCmdReq, OkWebSocketResponseData},
ModelingCmd,
};
use kittycad_modeling_cmds as kcmc;
use uuid::Uuid;
@ -95,50 +100,24 @@ async fn inner_extrude(
exec_state: &mut ExecState,
args: Args,
) -> Result<SolidSet, KclError> {
let id = exec_state.next_uuid();
// Extrude the element(s).
let sketches: Vec<Sketch> = sketch_set.into();
let mut solids = Vec::new();
for sketch in &sketches {
// Before we extrude, we need to enable the sketch mode.
// We do this here in case extrude is called out of order.
args.batch_modeling_cmd(
exec_state.next_uuid(),
ModelingCmd::from(mcmd::EnableSketchMode {
animated: false,
ortho: false,
entity_id: sketch.on.id(),
adjust_camera: false,
planar_normal: if let SketchSurface::Plane(plane) = &sketch.on {
// We pass in the normal for the plane here.
Some(plane.z_axis.into())
} else {
None
},
}),
)
let id = exec_state.next_uuid();
args.batch_modeling_cmds(&sketch.build_sketch_mode_cmds(
exec_state,
ModelingCmdReq {
cmd_id: id.into(),
cmd: ModelingCmd::from(mcmd::Extrude {
target: sketch.id.into(),
distance: LengthUnit(length),
faces: Default::default(),
}),
},
))
.await?;
// TODO: We're reusing the same UUID for multiple commands. This seems
// like the artifact graph would never be able to find all the
// responses.
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::Extrude {
target: sketch.id.into(),
distance: LengthUnit(length),
faces: Default::default(),
}),
)
.await?;
// Disable the sketch mode.
args.batch_modeling_cmd(
exec_state.next_uuid(),
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
)
.await?;
solids.push(do_post_extrude(sketch.clone(), id.into(), length, exec_state, args.clone()).await?);
}

View File

@ -334,6 +334,7 @@ async fn inner_get_next_adjacent_edge(
}),
)
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
} = &resp

View File

@ -4,7 +4,7 @@ use anyhow::Result;
use indexmap::IndexMap;
use kcl_derive_docs::stdlib;
use kcmc::shared::Point2d as KPoint2d; // Point2d is already defined in this pkg, to impl ts_rs traits.
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, ModelingCmd};
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, websocket::ModelingCmdReq, ModelingCmd};
use kittycad_modeling_cmds as kcmc;
use kittycad_modeling_cmds::shared::PathSegment;
use parse_display::{Display, FromStr};
@ -230,6 +230,7 @@ async fn straight_line(
}),
)
.await?;
let end = if is_absolute {
point
} else {
@ -1217,38 +1218,43 @@ pub(crate) async fn inner_start_profile_at(
_ => {}
}
// Enter sketch mode on the surface.
// We call this here so you can reuse the sketch surface for multiple sketches.
let id = exec_state.next_uuid();
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::EnableSketchMode {
animated: false,
ortho: false,
entity_id: sketch_surface.id(),
adjust_camera: false,
planar_normal: if let SketchSurface::Plane(plane) = &sketch_surface {
// We pass in the normal for the plane here.
Some(plane.z_axis.into())
} else {
None
},
}),
)
.await?;
let id = exec_state.next_uuid();
let enable_sketch_id = exec_state.next_uuid();
let path_id = exec_state.next_uuid();
args.batch_modeling_cmd(path_id, ModelingCmd::from(mcmd::StartPath::default()))
.await?;
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::MovePathPen {
path: path_id.into(),
to: KPoint2d::from(to).with_z(0.0).map(LengthUnit),
}),
)
let move_pen_id = exec_state.next_uuid();
args.batch_modeling_cmds(&[
// Enter sketch mode on the surface.
// We call this here so you can reuse the sketch surface for multiple sketches.
ModelingCmdReq {
cmd: ModelingCmd::from(mcmd::EnableSketchMode {
animated: false,
ortho: false,
entity_id: sketch_surface.id(),
adjust_camera: false,
planar_normal: if let SketchSurface::Plane(plane) = &sketch_surface {
// We pass in the normal for the plane here.
Some(plane.z_axis.into())
} else {
None
},
}),
cmd_id: enable_sketch_id.into(),
},
ModelingCmdReq {
cmd: ModelingCmd::from(mcmd::StartPath::default()),
cmd_id: path_id.into(),
},
ModelingCmdReq {
cmd: ModelingCmd::from(mcmd::MovePathPen {
path: path_id.into(),
to: KPoint2d::from(to).with_z(0.0).map(LengthUnit),
}),
cmd_id: move_pen_id.into(),
},
ModelingCmdReq {
cmd: ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
cmd_id: exec_state.next_uuid().into(),
},
])
.await?;
let current_path = BasePath {
@ -1257,7 +1263,7 @@ pub(crate) async fn inner_start_profile_at(
tag: tag.clone(),
units: sketch_surface.units(),
geo_meta: GeoMeta {
id,
id: move_pen_id,
metadata: args.source_range.into(),
},
};
@ -1419,16 +1425,6 @@ pub(crate) async fn inner_close(
args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id }))
.await?;
// If we are sketching on a plane we can close the sketch now.
if let SketchSurface::Plane(_) = sketch.on {
// We were on a plane, disable the sketch mode.
args.batch_modeling_cmd(
exec_state.next_uuid(),
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable::default()),
)
.await?;
}
let current_path = Path::ToPoint {
base: BasePath {
from: from.into(),

View File

@ -24,12 +24,12 @@ pub enum SweepPath {
/// Extrude a sketch along a path.
pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let sketch = args.get_unlabeled_kw_arg("sketch")?;
let sketch_set = args.get_unlabeled_kw_arg("sketch_set")?;
let path: SweepPath = args.get_kw_arg("path")?;
let sectional = args.get_kw_arg_opt("sectional")?;
let tolerance = args.get_kw_arg_opt("tolerance")?;
let value = inner_sweep(sketch, path, sectional, tolerance, exec_state, args).await?;
let value = inner_sweep(sketch_set, path, sectional, tolerance, exec_state, args).await?;
Ok(value.into())
}

View File

@ -19,11 +19,11 @@ use crate::{
/// Scale a solid.
pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solid = args.get_unlabeled_kw_arg("solid")?;
let solid_set = args.get_unlabeled_kw_arg("solid_set")?;
let scale = args.get_kw_arg("scale")?;
let global = args.get_kw_arg_opt("global")?;
let solid = inner_scale(solid, scale, global, exec_state, args).await?;
let solid = inner_scale(solid_set, scale, global, exec_state, args).await?;
Ok(solid.into())
}
@ -84,58 +84,94 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// scale = [1.0, 1.0, 2.5],
/// )
/// ```
///
/// ```
/// // Sweep two sketches along the same path.
///
/// sketch001 = startSketchOn('XY')
/// rectangleSketch = startProfileAt([-200, 23.86], sketch001)
/// |> angledLine([0, 73.47], %, $rectangleSegmentA001)
/// |> angledLine([
/// segAng(rectangleSegmentA001) - 90,
/// 50.61
/// ], %)
/// |> angledLine([
/// segAng(rectangleSegmentA001),
/// -segLen(rectangleSegmentA001)
/// ], %)
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
///
/// sketch002 = startSketchOn('YZ')
/// sweepPath = startProfileAt([0, 0], sketch002)
/// |> yLine(length = 231.81)
/// |> tangentialArc({
/// radius = 80,
/// offset = -90,
/// }, %)
/// |> xLine(length = 384.93)
///
/// parts = sweep([rectangleSketch, circleSketch], path = sweepPath)
///
/// // Scale the sweep.
/// scale(parts, scale = [1.0, 1.0, 0.5])
/// ```
#[stdlib {
name = "scale",
feature_tree_operation = false,
keywords = true,
unlabeled_first = true,
args = {
solid = {docs = "The solid to scale."},
solid_set = {docs = "The solid or set of solids to scale."},
scale = {docs = "The scale factor for the x, y, and z axes."},
global = {docs = "If true, the transform is applied in global space. The origin of the model will move. By default, the transform is applied in local sketch axis, therefore the origin will not move."}
}
}]
async fn inner_scale(
solid: SolidOrImportedGeometry,
solid_set: SolidOrImportedGeometry,
scale: [f64; 3],
global: Option<bool>,
exec_state: &mut ExecState,
args: Args,
) -> Result<SolidOrImportedGeometry, KclError> {
let id = exec_state.next_uuid();
for solid_id in solid_set.ids() {
let id = exec_state.next_uuid();
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::SetObjectTransform {
object_id: solid.id(),
transforms: vec![shared::ComponentTransform {
scale: Some(shared::TransformBy::<Point3d<f64>> {
property: Point3d {
x: scale[0],
y: scale[1],
z: scale[2],
},
set: false,
is_local: !global.unwrap_or(false),
}),
translate: None,
rotate_rpy: None,
rotate_angle_axis: None,
}],
}),
)
.await?;
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::SetObjectTransform {
object_id: solid_id,
transforms: vec![shared::ComponentTransform {
scale: Some(shared::TransformBy::<Point3d<f64>> {
property: Point3d {
x: scale[0],
y: scale[1],
z: scale[2],
},
set: false,
is_local: !global.unwrap_or(false),
}),
translate: None,
rotate_rpy: None,
rotate_angle_axis: None,
}],
}),
)
.await?;
}
Ok(solid)
Ok(solid_set)
}
/// Move a solid.
pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solid = args.get_unlabeled_kw_arg("solid")?;
let solid_set = args.get_unlabeled_kw_arg("solid_set")?;
let translate = args.get_kw_arg("translate")?;
let global = args.get_kw_arg_opt("global")?;
let solid = inner_translate(solid, translate, global, exec_state, args).await?;
let solid = inner_translate(solid_set, translate, global, exec_state, args).await?;
Ok(solid.into())
}
@ -188,54 +224,90 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
/// translate = [1.0, 1.0, 2.5],
/// )
/// ```
///
/// ```
/// // Sweep two sketches along the same path.
///
/// sketch001 = startSketchOn('XY')
/// rectangleSketch = startProfileAt([-200, 23.86], sketch001)
/// |> angledLine([0, 73.47], %, $rectangleSegmentA001)
/// |> angledLine([
/// segAng(rectangleSegmentA001) - 90,
/// 50.61
/// ], %)
/// |> angledLine([
/// segAng(rectangleSegmentA001),
/// -segLen(rectangleSegmentA001)
/// ], %)
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
///
/// sketch002 = startSketchOn('YZ')
/// sweepPath = startProfileAt([0, 0], sketch002)
/// |> yLine(length = 231.81)
/// |> tangentialArc({
/// radius = 80,
/// offset = -90,
/// }, %)
/// |> xLine(length = 384.93)
///
/// parts = sweep([rectangleSketch, circleSketch], path = sweepPath)
///
/// // Move the sweeps.
/// translate(parts, translate = [1.0, 1.0, 2.5])
/// ```
#[stdlib {
name = "translate",
feature_tree_operation = false,
keywords = true,
unlabeled_first = true,
args = {
solid = {docs = "The solid to move."},
solid_set = {docs = "The solid or set of solids to move."},
translate = {docs = "The amount to move the solid in all three axes."},
global = {docs = "If true, the transform is applied in global space. The origin of the model will move. By default, the transform is applied in local sketch axis, therefore the origin will not move."}
}
}]
async fn inner_translate(
solid: SolidOrImportedGeometry,
solid_set: SolidOrImportedGeometry,
translate: [f64; 3],
global: Option<bool>,
exec_state: &mut ExecState,
args: Args,
) -> Result<SolidOrImportedGeometry, KclError> {
let id = exec_state.next_uuid();
for solid_id in solid_set.ids() {
let id = exec_state.next_uuid();
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::SetObjectTransform {
object_id: solid.id(),
transforms: vec![shared::ComponentTransform {
translate: Some(shared::TransformBy::<Point3d<LengthUnit>> {
property: shared::Point3d {
x: LengthUnit(translate[0]),
y: LengthUnit(translate[1]),
z: LengthUnit(translate[2]),
},
set: false,
is_local: !global.unwrap_or(false),
}),
scale: None,
rotate_rpy: None,
rotate_angle_axis: None,
}],
}),
)
.await?;
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::SetObjectTransform {
object_id: solid_id,
transforms: vec![shared::ComponentTransform {
translate: Some(shared::TransformBy::<Point3d<LengthUnit>> {
property: shared::Point3d {
x: LengthUnit(translate[0]),
y: LengthUnit(translate[1]),
z: LengthUnit(translate[2]),
},
set: false,
is_local: !global.unwrap_or(false),
}),
scale: None,
rotate_rpy: None,
rotate_angle_axis: None,
}],
}),
)
.await?;
}
Ok(solid)
Ok(solid_set)
}
/// Rotate a solid.
pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solid = args.get_unlabeled_kw_arg("solid")?;
let solid_set = args.get_unlabeled_kw_arg("solid_set")?;
let roll = args.get_kw_arg_opt("roll")?;
let pitch = args.get_kw_arg_opt("pitch")?;
let yaw = args.get_kw_arg_opt("yaw")?;
@ -343,7 +415,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
}
}
let solid = inner_rotate(solid, roll, pitch, yaw, axis, angle, global, exec_state, args).await?;
let solid = inner_rotate(solid_set, roll, pitch, yaw, axis, angle, global, exec_state, args).await?;
Ok(solid.into())
}
@ -459,13 +531,47 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// angle = 90,
/// )
/// ```
///
/// ```
/// // Sweep two sketches along the same path.
///
/// sketch001 = startSketchOn('XY')
/// rectangleSketch = startProfileAt([-200, 23.86], sketch001)
/// |> angledLine([0, 73.47], %, $rectangleSegmentA001)
/// |> angledLine([
/// segAng(rectangleSegmentA001) - 90,
/// 50.61
/// ], %)
/// |> angledLine([
/// segAng(rectangleSegmentA001),
/// -segLen(rectangleSegmentA001)
/// ], %)
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
///
/// sketch002 = startSketchOn('YZ')
/// sweepPath = startProfileAt([0, 0], sketch002)
/// |> yLine(length = 231.81)
/// |> tangentialArc({
/// radius = 80,
/// offset = -90,
/// }, %)
/// |> xLine(length = 384.93)
///
/// parts = sweep([rectangleSketch, circleSketch], path = sweepPath)
///
/// // Rotate the sweeps.
/// rotate(parts, axis = [0, 0, 1.0], angle = 90)
/// ```
#[stdlib {
name = "rotate",
feature_tree_operation = false,
keywords = true,
unlabeled_first = true,
args = {
solid = {docs = "The solid to rotate."},
solid_set = {docs = "The solid or set of solids to rotate."},
roll = {docs = "The roll angle in degrees. Must be used with `pitch` and `yaw`. Must be between -360 and 360.", include_in_snippet = true},
pitch = {docs = "The pitch angle in degrees. Must be used with `roll` and `yaw`. Must be between -360 and 360.", include_in_snippet = true},
yaw = {docs = "The yaw angle in degrees. Must be used with `roll` and `pitch`. Must be between -360 and 360.", include_in_snippet = true},
@ -476,7 +582,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
}]
#[allow(clippy::too_many_arguments)]
async fn inner_rotate(
solid: SolidOrImportedGeometry,
solid_set: SolidOrImportedGeometry,
roll: Option<f64>,
pitch: Option<f64>,
yaw: Option<f64>,
@ -486,58 +592,60 @@ async fn inner_rotate(
exec_state: &mut ExecState,
args: Args,
) -> Result<SolidOrImportedGeometry, KclError> {
let id = exec_state.next_uuid();
for solid_id in solid_set.ids() {
let id = exec_state.next_uuid();
if let (Some(roll), Some(pitch), Some(yaw)) = (roll, pitch, yaw) {
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::SetObjectTransform {
object_id: solid.id(),
transforms: vec![shared::ComponentTransform {
rotate_rpy: Some(shared::TransformBy::<Point3d<f64>> {
property: shared::Point3d {
x: roll,
y: pitch,
z: yaw,
},
set: false,
is_local: !global.unwrap_or(false),
}),
scale: None,
rotate_angle_axis: None,
translate: None,
}],
}),
)
.await?;
if let (Some(roll), Some(pitch), Some(yaw)) = (roll, pitch, yaw) {
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::SetObjectTransform {
object_id: solid_id,
transforms: vec![shared::ComponentTransform {
rotate_rpy: Some(shared::TransformBy::<Point3d<f64>> {
property: shared::Point3d {
x: roll,
y: pitch,
z: yaw,
},
set: false,
is_local: !global.unwrap_or(false),
}),
scale: None,
rotate_angle_axis: None,
translate: None,
}],
}),
)
.await?;
}
if let (Some(axis), Some(angle)) = (axis, angle) {
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::SetObjectTransform {
object_id: solid_id,
transforms: vec![shared::ComponentTransform {
rotate_angle_axis: Some(shared::TransformBy::<Point4d<f64>> {
property: shared::Point4d {
x: axis[0],
y: axis[1],
z: axis[2],
w: angle,
},
set: false,
is_local: !global.unwrap_or(false),
}),
scale: None,
rotate_rpy: None,
translate: None,
}],
}),
)
.await?;
}
}
if let (Some(axis), Some(angle)) = (axis, angle) {
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::SetObjectTransform {
object_id: solid.id(),
transforms: vec![shared::ComponentTransform {
rotate_angle_axis: Some(shared::TransformBy::<Point4d<f64>> {
property: shared::Point4d {
x: axis[0],
y: axis[1],
z: axis[2],
w: angle,
},
set: false,
is_local: !global.unwrap_or(false),
}),
scale: None,
rotate_rpy: None,
translate: None,
}],
}),
)
.await?;
}
Ok(solid)
Ok(solid_set)
}
#[cfg(test)]

View File

@ -130,6 +130,17 @@ description: Artifact commands angled_line.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
35,
67,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -247,17 +258,6 @@ description: Artifact commands angled_line.kcl
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
256,
264,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [

View File

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

View File

@ -130,6 +130,17 @@ description: Artifact commands artifact_graph_example_code1.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
37,
64,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -226,17 +237,6 @@ description: Artifact commands artifact_graph_example_code1.kcl
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
239,
246,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -479,6 +479,17 @@ description: Artifact commands artifact_graph_example_code1.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
383,
410,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [

View File

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

View File

@ -130,6 +130,17 @@ description: Artifact commands artifact_graph_example_code_no_3d.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
37,
65,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -226,17 +237,6 @@ description: Artifact commands artifact_graph_example_code_no_3d.kcl
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
421,
428,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -314,6 +314,17 @@ description: Artifact commands artifact_graph_example_code_no_3d.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
467,
496,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -242,6 +242,17 @@ description: Artifact commands artifact_graph_example_code_offset_planes.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
193,
218,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -130,6 +130,17 @@ description: Artifact commands artifact_graph_sketch_on_face_etc.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
37,
62,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -205,17 +216,6 @@ description: Artifact commands artifact_graph_sketch_on_face_etc.kcl
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
193,
200,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -414,6 +414,17 @@ description: Artifact commands artifact_graph_sketch_on_face_etc.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
295,
325,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -683,6 +694,17 @@ description: Artifact commands artifact_graph_sketch_on_face_etc.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
544,
571,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -952,6 +974,17 @@ description: Artifact commands artifact_graph_sketch_on_face_etc.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
806,
833,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [

View File

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

View File

@ -154,6 +154,17 @@ description: Artifact commands assembly_non_default_units.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
197,
232,
3
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -284,6 +295,17 @@ description: Artifact commands assembly_non_default_units.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
114,
149,
4
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -130,6 +130,17 @@ description: Artifact commands basic_fillet_cube_close_opposite.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
35,
60,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -205,17 +216,6 @@ description: Artifact commands basic_fillet_cube_close_opposite.kcl
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
171,
191,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [

View File

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

View File

@ -130,6 +130,17 @@ description: Artifact commands basic_fillet_cube_end.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
35,
60,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -205,17 +216,6 @@ description: Artifact commands basic_fillet_cube_end.kcl
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
171,
179,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [

View File

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

View File

@ -130,6 +130,17 @@ description: Artifact commands basic_fillet_cube_next_adjacent.kcl
}
}
},
{
"cmdId": "[uuid]",
"range": [
35,
60,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
@ -205,17 +216,6 @@ description: Artifact commands basic_fillet_cube_next_adjacent.kcl
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
186,
206,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [

View File

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

Some files were not shown because too many files have changed in this diff Show More