Compare commits
29 Commits
move-tests
...
v0.32.0
Author | SHA1 | Date | |
---|---|---|---|
efe8089b08 | |||
49de3b0ac9 | |||
2b2ed470c1 | |||
96652a0c48 | |||
04e586d07b | |||
fe5f574a77 | |||
e787495ad0 | |||
8bb9be7a5e | |||
00892464e8 | |||
05ed2a3367 | |||
10cc5bce59 | |||
a32f150fc1 | |||
ac60082e67 | |||
d44dc1b21a | |||
813962ea4c | |||
738443a6ab | |||
4b6bbbe2c5 | |||
6ff8addc8b | |||
da05c38b9e | |||
191b9b71fd | |||
05163fdded | |||
7ed26e21c6 | |||
c668d40efc | |||
f38c6b90b7 | |||
7bc8bae0ec | |||
3804aca27e | |||
b127680f2f | |||
b7de8e60cf | |||
058fccb5e1 |
22
.github/workflows/cargo-test.yml
vendored
@ -2,28 +2,8 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
|
||||||
- 'src/wasm-lib/**.rs'
|
|
||||||
- 'src/wasm-lib/**.hbs'
|
|
||||||
- 'src/wasm-lib/**.gen'
|
|
||||||
- 'src/wasm-lib/**.snap'
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
- '**/Cargo.lock'
|
|
||||||
- '**/rust-toolchain.toml'
|
|
||||||
- 'src/wasm-lib/**.kcl'
|
|
||||||
- .github/workflows/cargo-test.yml
|
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- 'src/wasm-lib/**.rs'
|
|
||||||
- 'src/wasm-lib/**.hbs'
|
|
||||||
- 'src/wasm-lib/**.gen'
|
|
||||||
- 'src/wasm-lib/**.snap'
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
- '**/Cargo.lock'
|
|
||||||
- '**/rust-toolchain.toml'
|
|
||||||
- 'src/wasm-lib/**.kcl'
|
|
||||||
- .github/workflows/cargo-test.yml
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
concurrency:
|
concurrency:
|
||||||
@ -71,7 +51,7 @@ jobs:
|
|||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||||
RUST_MIN_STACK: 10485760000
|
RUST_MIN_STACK: 10485760000
|
||||||
- name: Upload to codecov.io
|
- name: Upload to codecov.io
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{secrets.CODECOV_TOKEN}}
|
token: ${{secrets.CODECOV_TOKEN}}
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
|
49
docs/kcl/atan2.md
Normal file
@ -30,6 +30,7 @@ layout: manual
|
|||||||
* [`assertLessThan`](kcl/assertLessThan)
|
* [`assertLessThan`](kcl/assertLessThan)
|
||||||
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
||||||
* [`atan`](kcl/atan)
|
* [`atan`](kcl/atan)
|
||||||
|
* [`atan2`](kcl/atan2)
|
||||||
* [`bezierCurve`](kcl/bezierCurve)
|
* [`bezierCurve`](kcl/bezierCurve)
|
||||||
* [`ceil`](kcl/ceil)
|
* [`ceil`](kcl/ceil)
|
||||||
* [`chamfer`](kcl/chamfer)
|
* [`chamfer`](kcl/chamfer)
|
||||||
@ -102,6 +103,7 @@ layout: manual
|
|||||||
* [`startProfileAt`](kcl/startProfileAt)
|
* [`startProfileAt`](kcl/startProfileAt)
|
||||||
* [`startSketchAt`](kcl/startSketchAt)
|
* [`startSketchAt`](kcl/startSketchAt)
|
||||||
* [`startSketchOn`](kcl/startSketchOn)
|
* [`startSketchOn`](kcl/startSketchOn)
|
||||||
|
* [`sweep`](kcl/sweep)
|
||||||
* [`tan`](kcl/tan)
|
* [`tan`](kcl/tan)
|
||||||
* [`tangentToEnd`](kcl/tangentToEnd)
|
* [`tangentToEnd`](kcl/tangentToEnd)
|
||||||
* [`tangentialArc`](kcl/tangentialArc)
|
* [`tangentialArc`](kcl/tangentialArc)
|
||||||
|
@ -43,7 +43,7 @@ fn sum(arr) {
|
|||||||
|
|
||||||
/* The above is basically like this pseudo-code:
|
/* The above is basically like this pseudo-code:
|
||||||
fn sum(arr):
|
fn sum(arr):
|
||||||
let sumSoFar = 0
|
sumSoFar = 0
|
||||||
for i in arr:
|
for i in arr:
|
||||||
sumSoFar = add(sumSoFar, i)
|
sumSoFar = add(sumSoFar, i)
|
||||||
return sumSoFar */
|
return sumSoFar */
|
||||||
@ -96,14 +96,14 @@ fn decagon(radius) {
|
|||||||
|
|
||||||
/* The `decagon` above is basically like this pseudo-code:
|
/* The `decagon` above is basically like this pseudo-code:
|
||||||
fn decagon(radius):
|
fn decagon(radius):
|
||||||
let stepAngle = (1/10) * tau()
|
stepAngle = (1/10) * tau()
|
||||||
let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
|
startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
|
||||||
|
|
||||||
// Here's the reduce part.
|
// Here's the reduce part.
|
||||||
let partialDecagon = startOfDecagonSketch
|
partialDecagon = startOfDecagonSketch
|
||||||
for i in [1..10]:
|
for i in [1..10]:
|
||||||
let x = cos(stepAngle * i) * radius
|
x = cos(stepAngle * i) * radius
|
||||||
let y = sin(stepAngle * i) * radius
|
y = sin(stepAngle * i) * radius
|
||||||
partialDecagon = lineTo([x, y], partialDecagon)
|
partialDecagon = lineTo([x, y], partialDecagon)
|
||||||
fullDecagon = partialDecagon // it's now full
|
fullDecagon = partialDecagon // it's now full
|
||||||
return fullDecagon */
|
return fullDecagon */
|
||||||
|
8333
docs/kcl/std.json
55
docs/kcl/sweep.md
Normal file
@ -12,5 +12,10 @@ KCL value for an optional parameter which was not given an argument. (remember,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
23
docs/kcl/types/SweepData.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
title: "SweepData"
|
||||||
|
excerpt: "Data for a sweep."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Data for a sweep.
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `path` |[`Sketch`](/docs/kcl/types/Sketch)| The path to sweep along. | No |
|
||||||
|
| `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
||||||
|
| `tolerance` |`number`| Tolerance for the sweep operation. | No |
|
||||||
|
|
||||||
|
|
@ -94,6 +94,51 @@ test.describe('Editor tests', () => {
|
|||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('ensure we use the cache, and do not re-execute', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
|
// Ensure we execute the first time.
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||||
|
).toHaveCount(2)
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-message-type="execution-done"]')
|
||||||
|
).toHaveCount(2)
|
||||||
|
|
||||||
|
// Add whitespace to the end of the code.
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('Home')
|
||||||
|
await page.keyboard.type(' ')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type(' ')
|
||||||
|
|
||||||
|
// Ensure we don't execute the second time.
|
||||||
|
await u.openDebugPanel()
|
||||||
|
// Make sure we didn't clear the scene.
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-message-type="execution-done"]')
|
||||||
|
).toHaveCount(3)
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||||
|
).toHaveCount(2)
|
||||||
|
})
|
||||||
|
|
||||||
test('if you click the format button it formats your code and executes so lints are still there', async ({
|
test('if you click the format button it formats your code and executes so lints are still there', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -214,23 +214,7 @@ export class SceneFixture {
|
|||||||
coords: { x: number; y: number },
|
coords: { x: number; y: number },
|
||||||
diff: number
|
diff: number
|
||||||
) => {
|
) => {
|
||||||
let finalValue = colour
|
await expectPixelColor(this.page, colour, coords, diff)
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
const pixel = (await getPixelRGBs(this.page)(coords, 1))[0]
|
|
||||||
if (!pixel) return null
|
|
||||||
finalValue = pixel
|
|
||||||
return pixel.every(
|
|
||||||
(channel, index) => Math.abs(channel - colour[index]) < diff
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.toBeTruthy()
|
|
||||||
.catch((cause) => {
|
|
||||||
throw new Error(
|
|
||||||
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
|
|
||||||
{ cause }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get gizmo() {
|
get gizmo() {
|
||||||
@ -246,3 +230,28 @@ export class SceneFixture {
|
|||||||
await buttonToTest.click()
|
await buttonToTest.click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function expectPixelColor(
|
||||||
|
page: Page,
|
||||||
|
colour: [number, number, number],
|
||||||
|
coords: { x: number; y: number },
|
||||||
|
diff: number
|
||||||
|
) {
|
||||||
|
let finalValue = colour
|
||||||
|
await expect
|
||||||
|
.poll(async () => {
|
||||||
|
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
|
||||||
|
if (!pixel) return null
|
||||||
|
finalValue = pixel
|
||||||
|
return pixel.every(
|
||||||
|
(channel, index) => Math.abs(channel - colour[index]) < diff
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.toBeTruthy()
|
||||||
|
.catch((cause) => {
|
||||||
|
throw new Error(
|
||||||
|
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
|
||||||
|
{ cause }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
TEST_SETTINGS_ONBOARDING_USER_MENU,
|
TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||||
} from './storageStates'
|
} from './storageStates'
|
||||||
import * as TOML from '@iarna/toml'
|
import * as TOML from '@iarna/toml'
|
||||||
|
import { expectPixelColor } from './fixtures/sceneFixture'
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||||
if (testInfo.tags.includes('@electron')) {
|
if (testInfo.tags.includes('@electron')) {
|
||||||
@ -45,7 +46,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
{ settingsKey: TEST_SETTINGS_KEY }
|
{ settingsKey: TEST_SETTINGS_KEY }
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
@ -54,6 +55,12 @@ test.describe('Onboarding tests', () => {
|
|||||||
|
|
||||||
// *and* that the code is shown in the editor
|
// *and* that the code is shown in the editor
|
||||||
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||||
|
|
||||||
|
// Make sure the model loaded
|
||||||
|
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||||
|
const modelColor: [number, number, number] = [45, 45, 45]
|
||||||
|
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
|
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(8)
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test(
|
||||||
@ -72,7 +79,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
const viewportSize = { width: 1200, height: 500 }
|
const viewportSize = { width: 1200, height: 1000 }
|
||||||
await page.setViewportSize(viewportSize)
|
await page.setViewportSize(viewportSize)
|
||||||
|
|
||||||
await test.step(`Create a project and open to the onboarding`, async () => {
|
await test.step(`Create a project and open to the onboarding`, async () => {
|
||||||
@ -92,6 +99,14 @@ test.describe('Onboarding tests', () => {
|
|||||||
await expect(page.locator('.cm-content')).toContainText(
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
'// Shelf Bracket'
|
'// Shelf Bracket'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: jess make less shit
|
||||||
|
// Make sure the model loaded
|
||||||
|
//const XYPlanePoint = { x: 986, y: 522 } as const
|
||||||
|
//const modelColor: [number, number, number] = [76, 76, 76]
|
||||||
|
//await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
|
|
||||||
|
//await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
@ -108,7 +123,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
}, initialCode)
|
}, initialCode)
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
// Replay the onboarding
|
// Replay the onboarding
|
||||||
@ -140,6 +155,12 @@ test.describe('Onboarding tests', () => {
|
|||||||
return localStorage.getItem('persistCode')
|
return localStorage.getItem('persistCode')
|
||||||
})
|
})
|
||||||
).toContain('// Shelf Bracket')
|
).toContain('// Shelf Bracket')
|
||||||
|
|
||||||
|
// Make sure the model loaded
|
||||||
|
const XYPlanePoint = { x: 986, y: 522 } as const
|
||||||
|
const modelColor: [number, number, number] = [76, 76, 76]
|
||||||
|
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
|
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Click through each onboarding step', async ({ page }) => {
|
test('Click through each onboarding step', async ({ page }) => {
|
||||||
@ -179,6 +200,17 @@ test.describe('Onboarding tests', () => {
|
|||||||
// Test that the onboarding pane is gone
|
// Test that the onboarding pane is gone
|
||||||
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
||||||
await expect(page.url()).not.toContain('onboarding')
|
await expect(page.url()).not.toContain('onboarding')
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// TODO: jess to fix
|
||||||
|
// Make sure the model loaded
|
||||||
|
//const XYPlanePoint = { x: 774, y: 516 } as const
|
||||||
|
// const modelColor: [number, number, number] = [129, 129, 129]
|
||||||
|
// await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
|
// await expectPixelColor(page, modelColor, XYPlanePoint, 20)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Onboarding redirects and code updating', async ({ page }) => {
|
test('Onboarding redirects and code updating', async ({ page }) => {
|
||||||
@ -393,7 +425,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test.fixme(
|
||||||
'Restarting onboarding on desktop takes one attempt',
|
'Restarting onboarding on desktop takes one attempt',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
@ -439,7 +471,7 @@ test(
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Navigate into project', async () => {
|
await test.step('Navigate into project', async () => {
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
@ -462,7 +494,15 @@ test(
|
|||||||
await test.step('Confirm that the onboarding has restarted', async () => {
|
await test.step('Confirm that the onboarding has restarted', async () => {
|
||||||
await expect(tutorialProjectIndicator).toBeVisible()
|
await expect(tutorialProjectIndicator).toBeVisible()
|
||||||
await expect(tutorialModalText).toBeVisible()
|
await expect(tutorialModalText).toBeVisible()
|
||||||
|
// Make sure the model loaded
|
||||||
|
const XYPlanePoint = { x: 988, y: 523 } as const
|
||||||
|
const modelColor: [number, number, number] = [76, 76, 76]
|
||||||
|
|
||||||
|
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
|
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||||
await tutorialDismissButton.click()
|
await tutorialDismissButton.click()
|
||||||
|
// Make sure model still there.
|
||||||
|
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Clear code and restart onboarding from settings', async () => {
|
await test.step('Clear code and restart onboarding from settings', async () => {
|
||||||
|
@ -1164,3 +1164,109 @@ test.fixme('theme persists', async ({ page, context }) => {
|
|||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test.describe('code color goober', { tag: '@snapshot' }, () => {
|
||||||
|
test('code color goober', async ({ page, context }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await context.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`// Create a pipe using a sweep.
|
||||||
|
|
||||||
|
// Create a path for the sweep.
|
||||||
|
sweepPath = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0.05, 0.05], %)
|
||||||
|
|> line([0, 7], %)
|
||||||
|
|> tangentialArc({ offset = 90, radius = 5 }, %)
|
||||||
|
|> line([-3, 0], %)
|
||||||
|
|> tangentialArc({ offset = -90, radius = 5 }, %)
|
||||||
|
|> line([0, 7], %)
|
||||||
|
|
||||||
|
sweepSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([2, 0], %)
|
||||||
|
|> arc({
|
||||||
|
angleEnd = 360,
|
||||||
|
angleStart = 0,
|
||||||
|
radius = 2
|
||||||
|
}, %)
|
||||||
|
|> sweep({
|
||||||
|
path = sweepPath,
|
||||||
|
}, %)
|
||||||
|
|> appearance({
|
||||||
|
color = "#bb00ff",
|
||||||
|
metalness = 90,
|
||||||
|
roughness = 90
|
||||||
|
}, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
await expect(page, 'expect small color widget').toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('code color goober opening window', async ({ page, context }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await context.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`// Create a pipe using a sweep.
|
||||||
|
|
||||||
|
// Create a path for the sweep.
|
||||||
|
sweepPath = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0.05, 0.05], %)
|
||||||
|
|> line([0, 7], %)
|
||||||
|
|> tangentialArc({ offset = 90, radius = 5 }, %)
|
||||||
|
|> line([-3, 0], %)
|
||||||
|
|> tangentialArc({ offset = -90, radius = 5 }, %)
|
||||||
|
|> line([0, 7], %)
|
||||||
|
|
||||||
|
sweepSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([2, 0], %)
|
||||||
|
|> arc({
|
||||||
|
angleEnd = 360,
|
||||||
|
angleStart = 0,
|
||||||
|
radius = 2
|
||||||
|
}, %)
|
||||||
|
|> sweep({
|
||||||
|
path = sweepPath,
|
||||||
|
}, %)
|
||||||
|
|> appearance({
|
||||||
|
color = "#bb00ff",
|
||||||
|
metalness = 90,
|
||||||
|
roughness = 90
|
||||||
|
}, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible()
|
||||||
|
|
||||||
|
// Click the color widget
|
||||||
|
await page.locator('.cm-css-color-picker-wrapper input').click()
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page,
|
||||||
|
'expect small color widget to have window open'
|
||||||
|
).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 130 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB |
@ -14,7 +14,7 @@ export const TEST_SETTINGS = {
|
|||||||
},
|
},
|
||||||
modeling: {
|
modeling: {
|
||||||
defaultUnit: 'in',
|
defaultUnit: 'in',
|
||||||
mouseControls: 'KittyCAD',
|
mouseControls: 'Zoo',
|
||||||
cameraProjection: 'perspective',
|
cameraProjection: 'perspective',
|
||||||
showDebugPanel: true,
|
showDebugPanel: true,
|
||||||
},
|
},
|
||||||
|
@ -479,4 +479,26 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Right-click opens context menu when not dragged', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await test.step(`The menu should not show if we drag the mouse`, async () => {
|
||||||
|
await page.mouse.move(900, 200)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.move(900, 300)
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
|
||||||
|
await expect(page.getByTestId('view-controls-menu')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`The menu should show if we don't drag the mouse`, async () => {
|
||||||
|
await page.mouse.move(900, 200)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
|
||||||
|
await expect(page.getByTestId('view-controls-menu')).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -119,6 +119,11 @@
|
|||||||
"title": "Pipe and Flange Assembly",
|
"title": "Pipe and Flange Assembly",
|
||||||
"description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint."
|
"description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "pipe-with-bend.kcl",
|
||||||
|
"title": "Pipe with bend",
|
||||||
|
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "poopy-shoe.kcl",
|
"file": "poopy-shoe.kcl",
|
||||||
"title": "Poopy Shoe",
|
"title": "Poopy Shoe",
|
||||||
|
@ -105,7 +105,7 @@ export class CameraControls {
|
|||||||
pendingZoom: number | null = null
|
pendingZoom: number | null = null
|
||||||
pendingRotation: Vector2 | null = null
|
pendingRotation: Vector2 | null = null
|
||||||
pendingPan: Vector2 | null = null
|
pendingPan: Vector2 | null = null
|
||||||
interactionGuards: MouseGuard = cameraMouseDragGuards.KittyCAD
|
interactionGuards: MouseGuard = cameraMouseDragGuards.Zoo
|
||||||
isFovAnimationInProgress = false
|
isFovAnimationInProgress = false
|
||||||
perspectiveFovBeforeOrtho = 45
|
perspectiveFovBeforeOrtho = 45
|
||||||
get isPerspective() {
|
get isPerspective() {
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
||||||
import { RefObject, useEffect, useMemo, useRef, useState } from 'react'
|
import {
|
||||||
|
MouseEvent,
|
||||||
|
RefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { Dialog } from '@headlessui/react'
|
import { Dialog } from '@headlessui/react'
|
||||||
|
|
||||||
interface ContextMenuProps
|
export interface ContextMenuProps
|
||||||
extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
|
extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
|
||||||
items?: React.ReactElement[]
|
items?: React.ReactElement[]
|
||||||
menuTargetElement?: RefObject<HTMLElement>
|
menuTargetElement?: RefObject<HTMLElement>
|
||||||
|
guard?: (e: globalThis.MouseEvent) => boolean
|
||||||
|
event?: 'contextmenu' | 'mouseup'
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultContextMenuItems = [
|
const DefaultContextMenuItems = [
|
||||||
@ -20,6 +30,8 @@ export function ContextMenu({
|
|||||||
items = DefaultContextMenuItems,
|
items = DefaultContextMenuItems,
|
||||||
menuTargetElement,
|
menuTargetElement,
|
||||||
className,
|
className,
|
||||||
|
guard,
|
||||||
|
event = 'contextmenu',
|
||||||
...props
|
...props
|
||||||
}: ContextMenuProps) {
|
}: ContextMenuProps) {
|
||||||
const dialogRef = useRef<HTMLDivElement>(null)
|
const dialogRef = useRef<HTMLDivElement>(null)
|
||||||
@ -32,6 +44,15 @@ export function ContextMenu({
|
|||||||
useHotkeys('esc', () => setOpen(false), {
|
useHotkeys('esc', () => setOpen(false), {
|
||||||
enabled: open,
|
enabled: open,
|
||||||
})
|
})
|
||||||
|
const handleContextMenu = useCallback(
|
||||||
|
(e: globalThis.MouseEvent) => {
|
||||||
|
if (guard && !guard(e)) return
|
||||||
|
e.preventDefault()
|
||||||
|
setPosition({ x: e.clientX, y: e.clientY })
|
||||||
|
setOpen(true)
|
||||||
|
},
|
||||||
|
[guard, setPosition, setOpen]
|
||||||
|
)
|
||||||
|
|
||||||
const dialogPositionStyle = useMemo(() => {
|
const dialogPositionStyle = useMemo(() => {
|
||||||
if (!dialogRef.current)
|
if (!dialogRef.current)
|
||||||
@ -78,21 +99,9 @@ export function ContextMenu({
|
|||||||
|
|
||||||
// Add context menu listener to target once mounted
|
// Add context menu listener to target once mounted
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleContextMenu = (e: MouseEvent) => {
|
menuTargetElement?.current?.addEventListener(event, handleContextMenu)
|
||||||
console.log('context menu', e)
|
|
||||||
e.preventDefault()
|
|
||||||
setPosition({ x: e.x, y: e.y })
|
|
||||||
setOpen(true)
|
|
||||||
}
|
|
||||||
menuTargetElement?.current?.addEventListener(
|
|
||||||
'contextmenu',
|
|
||||||
handleContextMenu
|
|
||||||
)
|
|
||||||
return () => {
|
return () => {
|
||||||
menuTargetElement?.current?.removeEventListener(
|
menuTargetElement?.current?.removeEventListener(event, handleContextMenu)
|
||||||
'contextmenu',
|
|
||||||
handleContextMenu
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}, [menuTargetElement?.current])
|
}, [menuTargetElement?.current])
|
||||||
|
|
||||||
@ -100,7 +109,10 @@ export function ContextMenu({
|
|||||||
<Dialog open={open} onClose={() => setOpen(false)}>
|
<Dialog open={open} onClose={() => setOpen(false)}>
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-50 w-screen h-screen"
|
className="fixed inset-0 z-50 w-screen h-screen"
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setPosition({ x: e.clientX, y: e.clientY })
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Dialog.Backdrop className="fixed z-10 inset-0" />
|
<Dialog.Backdrop className="fixed z-10 inset-0" />
|
||||||
<Dialog.Panel
|
<Dialog.Panel
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
||||||
import { sceneInfra } from 'lib/singletons'
|
import { sceneInfra } from 'lib/singletons'
|
||||||
import { MutableRefObject, useEffect, useMemo, useRef } from 'react'
|
import { MutableRefObject, useEffect, useRef } from 'react'
|
||||||
import {
|
import {
|
||||||
WebGLRenderer,
|
WebGLRenderer,
|
||||||
Scene,
|
Scene,
|
||||||
@ -19,16 +19,14 @@ import {
|
|||||||
Intersection,
|
Intersection,
|
||||||
Object3D,
|
Object3D,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
import {
|
|
||||||
ContextMenu,
|
|
||||||
ContextMenuDivider,
|
|
||||||
ContextMenuItem,
|
|
||||||
ContextMenuItemRefresh,
|
|
||||||
} from './ContextMenu'
|
|
||||||
import { Popover } from '@headlessui/react'
|
import { Popover } from '@headlessui/react'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import {
|
||||||
|
useViewControlMenuItems,
|
||||||
|
ViewControlContextMenu,
|
||||||
|
} from './ViewControlMenu'
|
||||||
|
import { AxisNames } from 'lib/constants'
|
||||||
|
|
||||||
const CANVAS_SIZE = 80
|
const CANVAS_SIZE = 80
|
||||||
const FRUSTUM_SIZE = 0.5
|
const FRUSTUM_SIZE = 0.5
|
||||||
@ -40,64 +38,14 @@ enum AxisColors {
|
|||||||
Z = '#6689ef',
|
Z = '#6689ef',
|
||||||
Gray = '#c6c7c2',
|
Gray = '#c6c7c2',
|
||||||
}
|
}
|
||||||
enum AxisNames {
|
|
||||||
X = 'x',
|
|
||||||
Y = 'y',
|
|
||||||
Z = 'z',
|
|
||||||
NEG_X = '-x',
|
|
||||||
NEG_Y = '-y',
|
|
||||||
NEG_Z = '-z',
|
|
||||||
}
|
|
||||||
const axisNamesSemantic: Record<AxisNames, string> = {
|
|
||||||
[AxisNames.X]: 'Right',
|
|
||||||
[AxisNames.Y]: 'Back',
|
|
||||||
[AxisNames.Z]: 'Top',
|
|
||||||
[AxisNames.NEG_X]: 'Left',
|
|
||||||
[AxisNames.NEG_Y]: 'Front',
|
|
||||||
[AxisNames.NEG_Z]: 'Bottom',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Gizmo() {
|
export default function Gizmo() {
|
||||||
|
const menuItems = useViewControlMenuItems()
|
||||||
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||||
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
||||||
const cameraPassiveUpdateTimer = useRef(0)
|
const cameraPassiveUpdateTimer = useRef(0)
|
||||||
const raycasterPassiveUpdateTimer = useRef(0)
|
const raycasterPassiveUpdateTimer = useRef(0)
|
||||||
const { send: modelingSend } = useModelingContext()
|
|
||||||
const menuItems = useMemo(
|
|
||||||
() => [
|
|
||||||
...Object.entries(axisNamesSemantic).map(([axisName, axisSemantic]) => (
|
|
||||||
<ContextMenuItem
|
|
||||||
key={axisName}
|
|
||||||
onClick={() => {
|
|
||||||
sceneInfra.camControls
|
|
||||||
.updateCameraToAxis(axisName as AxisNames)
|
|
||||||
.catch(reportRejection)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{axisSemantic} view
|
|
||||||
</ContextMenuItem>
|
|
||||||
)),
|
|
||||||
<ContextMenuDivider />,
|
|
||||||
<ContextMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reset view
|
|
||||||
</ContextMenuItem>,
|
|
||||||
<ContextMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
modelingSend({ type: 'Center camera on selection' })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Center view on selection
|
|
||||||
</ContextMenuItem>,
|
|
||||||
<ContextMenuDivider />,
|
|
||||||
<ContextMenuItemRefresh />,
|
|
||||||
],
|
|
||||||
[axisNamesSemantic]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canvasRef.current) return
|
if (!canvasRef.current) return
|
||||||
@ -161,7 +109,7 @@ export default function Gizmo() {
|
|||||||
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
|
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
|
||||||
>
|
>
|
||||||
<canvas ref={canvasRef} />
|
<canvas ref={canvasRef} />
|
||||||
<ContextMenu menuTargetElement={wrapperRef} items={menuItems} />
|
<ViewControlContextMenu menuTargetElement={wrapperRef} />
|
||||||
</div>
|
</div>
|
||||||
<GizmoDropdown items={menuItems} />
|
<GizmoDropdown items={menuItems} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,7 @@ import Tooltip from 'components/Tooltip'
|
|||||||
import { CustomIconName } from 'components/CustomIcon'
|
import { CustomIconName } from 'components/CustomIcon'
|
||||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionIcon } from 'components/ActionIcon'
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
|
|
||||||
export interface ModelingPaneProps {
|
export interface ModelingPaneProps {
|
||||||
id: string
|
id: string
|
||||||
@ -70,7 +71,7 @@ export const ModelingPane = ({
|
|||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const onboardingStatus = settings.context.app.onboardingStatus
|
const onboardingStatus = settings.context.app.onboardingStatus
|
||||||
const pointerEventsCssClass =
|
const pointerEventsCssClass =
|
||||||
onboardingStatus.current === 'camera'
|
onboardingStatus.current === onboardingPaths.CAMERA
|
||||||
? 'pointer-events-none '
|
? 'pointer-events-none '
|
||||||
: 'pointer-events-auto '
|
: 'pointer-events-auto '
|
||||||
return (
|
return (
|
||||||
|
@ -19,6 +19,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
|||||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||||
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
|
|
||||||
interface ModelingSidebarProps {
|
interface ModelingSidebarProps {
|
||||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||||
@ -41,7 +42,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
const onboardingStatus = settings.context.app.onboardingStatus
|
const onboardingStatus = settings.context.app.onboardingStatus
|
||||||
const { send, context } = useModelingContext()
|
const { send, context } = useModelingContext()
|
||||||
const pointerEventsCssClass =
|
const pointerEventsCssClass =
|
||||||
onboardingStatus.current === 'camera' ||
|
onboardingStatus.current === onboardingPaths.CAMERA ||
|
||||||
context.store?.openPanes.length === 0
|
context.store?.openPanes.length === 0
|
||||||
? 'pointer-events-none '
|
? 'pointer-events-none '
|
||||||
: 'pointer-events-auto '
|
: 'pointer-events-auto '
|
||||||
|
@ -20,6 +20,7 @@ import { IndexLoaderData } from 'lib/types'
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { err, reportRejection } from 'lib/trap'
|
import { err, reportRejection } from 'lib/trap'
|
||||||
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
||||||
|
import { ViewControlContextMenu } from './ViewControlMenu'
|
||||||
|
|
||||||
enum StreamState {
|
enum StreamState {
|
||||||
Playing = 'playing',
|
Playing = 'playing',
|
||||||
@ -30,6 +31,7 @@ enum StreamState {
|
|||||||
|
|
||||||
export const Stream = () => {
|
export const Stream = () => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
const videoWrapperRef = useRef<HTMLDivElement>(null)
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const { state, send } = useModelingContext()
|
const { state, send } = useModelingContext()
|
||||||
@ -258,7 +260,7 @@ export const Stream = () => {
|
|||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}, [mediaStream])
|
}, [mediaStream])
|
||||||
|
|
||||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
// If we've got no stream or connection, don't do anything
|
// If we've got no stream or connection, don't do anything
|
||||||
if (!isNetworkOkay) return
|
if (!isNetworkOkay) return
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
@ -320,10 +322,11 @@ export const Stream = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={videoWrapperRef}
|
||||||
className="absolute inset-0 z-0"
|
className="absolute inset-0 z-0"
|
||||||
id="stream"
|
id="stream"
|
||||||
data-testid="stream"
|
data-testid="stream"
|
||||||
onClick={handleMouseUp}
|
onClick={handleClick}
|
||||||
onDoubleClick={enterSketchModeIfSelectingSketch}
|
onDoubleClick={enterSketchModeIfSelectingSketch}
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
onContextMenuCapture={(e) => e.preventDefault()}
|
onContextMenuCapture={(e) => e.preventDefault()}
|
||||||
@ -384,6 +387,14 @@ export const Stream = () => {
|
|||||||
</Loading>
|
</Loading>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<ViewControlContextMenu
|
||||||
|
event="mouseup"
|
||||||
|
guard={(e) =>
|
||||||
|
sceneInfra.camControls.wasDragging === false &&
|
||||||
|
btnName(e).right === true
|
||||||
|
}
|
||||||
|
menuTargetElement={videoWrapperRef}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
66
src/components/ViewControlMenu.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { reportRejection } from 'lib/trap'
|
||||||
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuDivider,
|
||||||
|
ContextMenuItem,
|
||||||
|
ContextMenuItemRefresh,
|
||||||
|
ContextMenuProps,
|
||||||
|
} from './ContextMenu'
|
||||||
|
import { AxisNames, VIEW_NAMES_SEMANTIC } from 'lib/constants'
|
||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { sceneInfra } from 'lib/singletons'
|
||||||
|
|
||||||
|
export function useViewControlMenuItems() {
|
||||||
|
const { send: modelingSend } = useModelingContext()
|
||||||
|
const menuItems = useMemo(
|
||||||
|
() => [
|
||||||
|
...Object.entries(VIEW_NAMES_SEMANTIC).map(([axisName, axisSemantic]) => (
|
||||||
|
<ContextMenuItem
|
||||||
|
key={axisName}
|
||||||
|
onClick={() => {
|
||||||
|
sceneInfra.camControls
|
||||||
|
.updateCameraToAxis(axisName as AxisNames)
|
||||||
|
.catch(reportRejection)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{axisSemantic} view
|
||||||
|
</ContextMenuItem>
|
||||||
|
)),
|
||||||
|
<ContextMenuDivider />,
|
||||||
|
<ContextMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset view
|
||||||
|
</ContextMenuItem>,
|
||||||
|
<ContextMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
modelingSend({ type: 'Center camera on selection' })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Center view on selection
|
||||||
|
</ContextMenuItem>,
|
||||||
|
<ContextMenuDivider />,
|
||||||
|
<ContextMenuItemRefresh />,
|
||||||
|
],
|
||||||
|
[VIEW_NAMES_SEMANTIC]
|
||||||
|
)
|
||||||
|
return menuItems
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ViewControlContextMenu({
|
||||||
|
menuTargetElement: wrapperRef,
|
||||||
|
...props
|
||||||
|
}: ContextMenuProps) {
|
||||||
|
const menuItems = useViewControlMenuItems()
|
||||||
|
return (
|
||||||
|
<ContextMenu
|
||||||
|
data-testid="view-controls-menu"
|
||||||
|
menuTargetElement={wrapperRef}
|
||||||
|
items={menuItems}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
327
src/editor/plugins/lsp/kcl/colors.ts
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
import {
|
||||||
|
EditorView,
|
||||||
|
WidgetType,
|
||||||
|
ViewUpdate,
|
||||||
|
ViewPlugin,
|
||||||
|
DecorationSet,
|
||||||
|
Decoration,
|
||||||
|
} from '@codemirror/view'
|
||||||
|
import { Range, Extension, Text } from '@codemirror/state'
|
||||||
|
import { NodeProp, Tree } from '@lezer/common'
|
||||||
|
import { language, syntaxTree } from '@codemirror/language'
|
||||||
|
|
||||||
|
interface PickerState {
|
||||||
|
from: number
|
||||||
|
to: number
|
||||||
|
alpha: string
|
||||||
|
colorType: ColorType
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WidgetOptions extends PickerState {
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ColorData = Omit<WidgetOptions, 'from' | 'to'>
|
||||||
|
|
||||||
|
const pickerState = new WeakMap<HTMLInputElement, PickerState>()
|
||||||
|
|
||||||
|
export enum ColorType {
|
||||||
|
hex = 'HEX',
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexRegex = /(^|\b)(#[0-9a-f]{3,9})(\b|$)/i
|
||||||
|
|
||||||
|
function discoverColorsInKCL(
|
||||||
|
syntaxTree: Tree,
|
||||||
|
from: number,
|
||||||
|
to: number,
|
||||||
|
typeName: string,
|
||||||
|
doc: Text,
|
||||||
|
language?: string
|
||||||
|
): WidgetOptions | Array<WidgetOptions> | null {
|
||||||
|
switch (typeName) {
|
||||||
|
case 'Program':
|
||||||
|
case 'VariableDeclaration':
|
||||||
|
case 'CallExpression':
|
||||||
|
case 'ObjectExpression':
|
||||||
|
case 'ObjectProperty':
|
||||||
|
case 'ArgumentList':
|
||||||
|
case 'PipeExpression': {
|
||||||
|
let innerTree = syntaxTree.resolveInner(from, 0).tree
|
||||||
|
|
||||||
|
if (!innerTree) {
|
||||||
|
innerTree = syntaxTree.resolveInner(from, 1).tree
|
||||||
|
if (!innerTree) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const overlayTree = innerTree.prop(NodeProp.mounted)?.tree
|
||||||
|
|
||||||
|
if (overlayTree?.type.name !== 'Styles') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const ret: Array<WidgetOptions> = []
|
||||||
|
overlayTree.iterate({
|
||||||
|
from: 0,
|
||||||
|
to: overlayTree.length,
|
||||||
|
enter: ({ type, from: overlayFrom, to: overlayTo }) => {
|
||||||
|
const maybeWidgetOptions = discoverColorsInKCL(
|
||||||
|
syntaxTree,
|
||||||
|
// We add one because the tree doesn't include the
|
||||||
|
// quotation mark from the style tag
|
||||||
|
from + 1 + overlayFrom,
|
||||||
|
from + 1 + overlayTo,
|
||||||
|
type.name,
|
||||||
|
doc,
|
||||||
|
language
|
||||||
|
)
|
||||||
|
|
||||||
|
if (maybeWidgetOptions) {
|
||||||
|
if (Array.isArray(maybeWidgetOptions)) {
|
||||||
|
console.error('Unexpected nested overlays')
|
||||||
|
ret.push(...maybeWidgetOptions)
|
||||||
|
} else {
|
||||||
|
ret.push(maybeWidgetOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'String': {
|
||||||
|
const result = parseColorLiteral(doc.sliceString(from, to))
|
||||||
|
if (!result) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseColorLiteral(colorLiteral: string): ColorData | null {
|
||||||
|
const literal = colorLiteral.replace(/"/g, '')
|
||||||
|
const match = hexRegex.exec(literal)
|
||||||
|
if (!match) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const [color, alpha] = toFullHex(literal)
|
||||||
|
|
||||||
|
return {
|
||||||
|
colorType: ColorType.hex,
|
||||||
|
color,
|
||||||
|
alpha,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorPickersDecorations(
|
||||||
|
view: EditorView,
|
||||||
|
discoverColors: typeof discoverColorsInKCL
|
||||||
|
) {
|
||||||
|
const widgets: Array<Range<Decoration>> = []
|
||||||
|
|
||||||
|
const st = syntaxTree(view.state)
|
||||||
|
|
||||||
|
for (const range of view.visibleRanges) {
|
||||||
|
st.iterate({
|
||||||
|
from: range.from,
|
||||||
|
to: range.to,
|
||||||
|
enter: ({ type, from, to }) => {
|
||||||
|
const maybeWidgetOptions = discoverColors(
|
||||||
|
st,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
type.name,
|
||||||
|
view.state.doc,
|
||||||
|
view.state.facet(language)?.name
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!maybeWidgetOptions) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(maybeWidgetOptions)) {
|
||||||
|
widgets.push(
|
||||||
|
Decoration.widget({
|
||||||
|
widget: new ColorPickerWidget(maybeWidgetOptions),
|
||||||
|
side: 1,
|
||||||
|
}).range(maybeWidgetOptions.from)
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const wo of maybeWidgetOptions) {
|
||||||
|
widgets.push(
|
||||||
|
Decoration.widget({
|
||||||
|
widget: new ColorPickerWidget(wo),
|
||||||
|
side: 1,
|
||||||
|
}).range(wo.from)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decoration.set(widgets)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFullHex(color: string): string[] {
|
||||||
|
if (color.length === 4) {
|
||||||
|
// 3-char hex
|
||||||
|
return [
|
||||||
|
`#${color[1].repeat(2)}${color[2].repeat(2)}${color[3].repeat(2)}`,
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.length === 5) {
|
||||||
|
// 4-char hex (alpha)
|
||||||
|
return [
|
||||||
|
`#${color[1].repeat(2)}${color[2].repeat(2)}${color[3].repeat(2)}`,
|
||||||
|
color[4].repeat(2),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.length === 9) {
|
||||||
|
// 8-char hex (alpha)
|
||||||
|
return [`#${color.slice(1, -2)}`, color.slice(-2)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return [color, '']
|
||||||
|
}
|
||||||
|
|
||||||
|
export const wrapperClassName = 'cm-css-color-picker-wrapper'
|
||||||
|
|
||||||
|
class ColorPickerWidget extends WidgetType {
|
||||||
|
private readonly state: PickerState
|
||||||
|
private readonly color: string
|
||||||
|
|
||||||
|
constructor({ color, ...state }: WidgetOptions) {
|
||||||
|
super()
|
||||||
|
this.state = state
|
||||||
|
this.color = color
|
||||||
|
}
|
||||||
|
|
||||||
|
eq(other: ColorPickerWidget) {
|
||||||
|
return (
|
||||||
|
other.state.colorType === this.state.colorType &&
|
||||||
|
other.color === this.color &&
|
||||||
|
other.state.from === this.state.from &&
|
||||||
|
other.state.to === this.state.to &&
|
||||||
|
other.state.alpha === this.state.alpha
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
toDOM() {
|
||||||
|
const picker = document.createElement('input')
|
||||||
|
pickerState.set(picker, this.state)
|
||||||
|
picker.type = 'color'
|
||||||
|
picker.value = this.color
|
||||||
|
|
||||||
|
const wrapper = document.createElement('span')
|
||||||
|
wrapper.appendChild(picker)
|
||||||
|
wrapper.className = wrapperClassName
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreEvent() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const colorPickerTheme = EditorView.baseTheme({
|
||||||
|
[`.${wrapperClassName}`]: {
|
||||||
|
display: 'inline-block',
|
||||||
|
outline: '1px solid #eee',
|
||||||
|
marginRight: '0.6ch',
|
||||||
|
height: '1em',
|
||||||
|
width: '1em',
|
||||||
|
transform: 'translateY(1px)',
|
||||||
|
},
|
||||||
|
[`.${wrapperClassName} input[type="color"]`]: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
padding: 0,
|
||||||
|
border: 'none',
|
||||||
|
'&::-webkit-color-swatch-wrapper': {
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
'&::-webkit-color-swatch': {
|
||||||
|
border: 'none',
|
||||||
|
},
|
||||||
|
'&::-moz-color-swatch': {
|
||||||
|
border: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface IFactoryOptions {
|
||||||
|
discoverColors: typeof discoverColorsInKCL
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeColorPicker = (options: IFactoryOptions) =>
|
||||||
|
ViewPlugin.fromClass(
|
||||||
|
class ColorPickerViewPlugin {
|
||||||
|
decorations: DecorationSet
|
||||||
|
|
||||||
|
constructor(view: EditorView) {
|
||||||
|
this.decorations = colorPickersDecorations(view, options.discoverColors)
|
||||||
|
}
|
||||||
|
|
||||||
|
update(update: ViewUpdate) {
|
||||||
|
if (update.docChanged || update.viewportChanged) {
|
||||||
|
this.decorations = colorPickersDecorations(
|
||||||
|
update.view,
|
||||||
|
options.discoverColors
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
decorations: (v) => v.decorations,
|
||||||
|
eventHandlers: {
|
||||||
|
change: (e, view) => {
|
||||||
|
const target = e.target as HTMLInputElement
|
||||||
|
if (
|
||||||
|
target.nodeName !== 'INPUT' ||
|
||||||
|
!target.parentElement ||
|
||||||
|
!target.parentElement.classList.contains(wrapperClassName)
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = pickerState.get(target)!
|
||||||
|
|
||||||
|
let converted = '"' + target.value + data.alpha + '"'
|
||||||
|
|
||||||
|
view.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: data.from,
|
||||||
|
to: data.to,
|
||||||
|
insert: converted,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const colorPicker: Extension = [
|
||||||
|
makeColorPicker({ discoverColors: discoverColorsInKCL }),
|
||||||
|
colorPickerTheme,
|
||||||
|
]
|
@ -17,6 +17,7 @@ import { kclPlugin } from '.'
|
|||||||
import type * as LSP from 'vscode-languageserver-protocol'
|
import type * as LSP from 'vscode-languageserver-protocol'
|
||||||
// @ts-ignore: No types available
|
// @ts-ignore: No types available
|
||||||
import { parser } from './kcl.grammar'
|
import { parser } from './kcl.grammar'
|
||||||
|
import { colorPicker } from './colors'
|
||||||
|
|
||||||
export interface LanguageOptions {
|
export interface LanguageOptions {
|
||||||
workspaceFolders: LSP.WorkspaceFolder[]
|
workspaceFolders: LSP.WorkspaceFolder[]
|
||||||
@ -54,14 +55,14 @@ export const KclLanguage = LRLanguage.define({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export function kcl(options: LanguageOptions) {
|
export function kcl(options: LanguageOptions) {
|
||||||
return new LanguageSupport(
|
return new LanguageSupport(KclLanguage, [
|
||||||
KclLanguage,
|
colorPicker,
|
||||||
kclPlugin({
|
kclPlugin({
|
||||||
documentUri: options.documentUri,
|
documentUri: options.documentUri,
|
||||||
workspaceFolders: options.workspaceFolders,
|
workspaceFolders: options.workspaceFolders,
|
||||||
allowHTMLContent: true,
|
allowHTMLContent: true,
|
||||||
client: options.client,
|
client: options.client,
|
||||||
processLspNotification: options.processLspNotification,
|
processLspNotification: options.processLspNotification,
|
||||||
})
|
}),
|
||||||
)
|
])
|
||||||
}
|
}
|
||||||
|
@ -311,8 +311,6 @@ export class KclManager {
|
|||||||
// Do not send send scene commands if the program was interrupted, go to clean up
|
// Do not send send scene commands if the program was interrupted, go to clean up
|
||||||
if (!isInterrupted) {
|
if (!isInterrupted) {
|
||||||
this.addDiagnostics(await lintAst({ ast: ast }))
|
this.addDiagnostics(await lintAst({ ast: ast }))
|
||||||
|
|
||||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
|
||||||
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
|
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
|
||||||
|
|
||||||
if (args.zoomToFit) {
|
if (args.zoomToFit) {
|
||||||
@ -358,7 +356,13 @@ export class KclManager {
|
|||||||
this.lastSuccessfulProgramMemory = execState.memory
|
this.lastSuccessfulProgramMemory = execState.memory
|
||||||
}
|
}
|
||||||
this.ast = { ...ast }
|
this.ast = { ...ast }
|
||||||
|
// updateArtifactGraph relies on updated executeState/programMemory
|
||||||
|
await this.engineCommandManager.updateArtifactGraph(this.ast)
|
||||||
this._executeCallback()
|
this._executeCallback()
|
||||||
|
if (!isInterrupted) {
|
||||||
|
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||||
|
}
|
||||||
|
|
||||||
this.engineCommandManager.addCommandLog({
|
this.engineCommandManager.addCommandLog({
|
||||||
type: 'execution-done',
|
type: 'execution-done',
|
||||||
data: null,
|
data: null,
|
||||||
|
@ -66,9 +66,7 @@ export async function executeAst({
|
|||||||
? enginelessExecutor(ast, programMemoryOverride)
|
? enginelessExecutor(ast, programMemoryOverride)
|
||||||
: _executor(ast, engineCommandManager))
|
: _executor(ast, engineCommandManager))
|
||||||
|
|
||||||
await engineCommandManager.waitForAllCommands(
|
await engineCommandManager.waitForAllCommands()
|
||||||
programMemoryOverride !== undefined
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
logs: [],
|
logs: [],
|
||||||
|
@ -247,7 +247,7 @@ extrude003 = extrude(-15, sketch003)`
|
|||||||
selectedSegmentSnippet,
|
selectedSegmentSnippet,
|
||||||
expectedExtrudeSnippet
|
expectedExtrudeSnippet
|
||||||
)
|
)
|
||||||
})
|
}, 5_000)
|
||||||
})
|
})
|
||||||
|
|
||||||
const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
||||||
@ -477,7 +477,7 @@ extrude001 = extrude(-15, sketch001)
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(-15, sketch001)
|
extrude001 = extrude(-15, sketch001)
|
||||||
|> chamfer({ length: 5, tags: [seg01] }, %)`
|
|> chamfer({ length = 5, tags = [seg01] }, %)`
|
||||||
const segmentSnippets = ['line([-20, 0], %)']
|
const segmentSnippets = ['line([-20, 0], %)']
|
||||||
const expectedCode = `sketch001 = startSketchOn('XY')
|
const expectedCode = `sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, 10], %)
|
|> startProfileAt([-10, 10], %)
|
||||||
@ -487,8 +487,8 @@ extrude001 = extrude(-15, sketch001)
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(-15, sketch001)
|
extrude001 = extrude(-15, sketch001)
|
||||||
|> chamfer({ length: 5, tags: [seg01] }, %)
|
|> chamfer({ length = 5, tags = [seg01] }, %)
|
||||||
|> ${edgeTreatmentType}({ ${parameterName}: 3, tags: [seg02] }, %)`
|
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg02] }, %)`
|
||||||
|
|
||||||
await runModifyAstCloneWithEdgeTreatmentAndTag(
|
await runModifyAstCloneWithEdgeTreatmentAndTag(
|
||||||
code,
|
code,
|
||||||
|
@ -13,7 +13,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
12,
|
12,
|
||||||
31,
|
31,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
@ -33,7 +33,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
37,
|
37,
|
||||||
64,
|
64,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
@ -60,7 +60,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
70,
|
70,
|
||||||
86,
|
86,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -83,7 +83,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
92,
|
92,
|
||||||
119,
|
119,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeCutId": "UUID",
|
"edgeCutId": "UUID",
|
||||||
@ -107,7 +107,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
125,
|
125,
|
||||||
150,
|
150,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -130,7 +130,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
156,
|
156,
|
||||||
203,
|
203,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -153,7 +153,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
209,
|
209,
|
||||||
217,
|
217,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [],
|
"edgeIds": [],
|
||||||
@ -177,7 +177,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
231,
|
231,
|
||||||
254,
|
254,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -320,7 +320,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
260,
|
260,
|
||||||
299,
|
299,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"consumedEdgeId": "UUID",
|
"consumedEdgeId": "UUID",
|
||||||
@ -340,7 +340,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
350,
|
350,
|
||||||
377,
|
377,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
@ -366,7 +366,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
383,
|
383,
|
||||||
398,
|
398,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -389,7 +389,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
404,
|
404,
|
||||||
420,
|
420,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -412,7 +412,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
426,
|
426,
|
||||||
473,
|
473,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -435,7 +435,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
479,
|
479,
|
||||||
487,
|
487,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [],
|
"edgeIds": [],
|
||||||
@ -459,7 +459,7 @@ Map {
|
|||||||
"range": [
|
"range": [
|
||||||
501,
|
501,
|
||||||
522,
|
522,
|
||||||
0,
|
true,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"edgeIds": [
|
"edgeIds": [
|
||||||
@ -478,7 +478,6 @@ Map {
|
|||||||
"UUID",
|
"UUID",
|
||||||
"UUID",
|
"UUID",
|
||||||
"UUID",
|
"UUID",
|
||||||
"UUID",
|
|
||||||
],
|
],
|
||||||
"type": "sweep",
|
"type": "sweep",
|
||||||
},
|
},
|
||||||
@ -507,14 +506,6 @@ Map {
|
|||||||
"type": "wall",
|
"type": "wall",
|
||||||
},
|
},
|
||||||
"UUID-34" => {
|
"UUID-34" => {
|
||||||
"edgeCutEdgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [],
|
|
||||||
"subType": "start",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "cap",
|
|
||||||
},
|
|
||||||
"UUID-35" => {
|
|
||||||
"edgeCutEdgeIds": [],
|
"edgeCutEdgeIds": [],
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"pathIds": [],
|
"pathIds": [],
|
||||||
@ -522,42 +513,42 @@ Map {
|
|||||||
"sweepId": "UUID",
|
"sweepId": "UUID",
|
||||||
"type": "cap",
|
"type": "cap",
|
||||||
},
|
},
|
||||||
"UUID-36" => {
|
"UUID-35" => {
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"segId": "UUID",
|
"segId": "UUID",
|
||||||
"subType": "opposite",
|
"subType": "opposite",
|
||||||
"sweepId": "UUID",
|
"sweepId": "UUID",
|
||||||
"type": "sweepEdge",
|
"type": "sweepEdge",
|
||||||
},
|
},
|
||||||
|
"UUID-36" => {
|
||||||
|
"id": "UUID",
|
||||||
|
"segId": "UUID",
|
||||||
|
"subType": "adjacent",
|
||||||
|
"sweepId": "UUID",
|
||||||
|
"type": "sweepEdge",
|
||||||
|
},
|
||||||
"UUID-37" => {
|
"UUID-37" => {
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"segId": "UUID",
|
"segId": "UUID",
|
||||||
"subType": "adjacent",
|
"subType": "opposite",
|
||||||
"sweepId": "UUID",
|
"sweepId": "UUID",
|
||||||
"type": "sweepEdge",
|
"type": "sweepEdge",
|
||||||
},
|
},
|
||||||
"UUID-38" => {
|
"UUID-38" => {
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"segId": "UUID",
|
"segId": "UUID",
|
||||||
"subType": "opposite",
|
"subType": "adjacent",
|
||||||
"sweepId": "UUID",
|
"sweepId": "UUID",
|
||||||
"type": "sweepEdge",
|
"type": "sweepEdge",
|
||||||
},
|
},
|
||||||
"UUID-39" => {
|
"UUID-39" => {
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "adjacent",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-40" => {
|
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"segId": "UUID",
|
"segId": "UUID",
|
||||||
"subType": "opposite",
|
"subType": "opposite",
|
||||||
"sweepId": "UUID",
|
"sweepId": "UUID",
|
||||||
"type": "sweepEdge",
|
"type": "sweepEdge",
|
||||||
},
|
},
|
||||||
"UUID-41" => {
|
"UUID-40" => {
|
||||||
"id": "UUID",
|
"id": "UUID",
|
||||||
"segId": "UUID",
|
"segId": "UUID",
|
||||||
"subType": "adjacent",
|
"subType": "adjacent",
|
||||||
|
@ -259,11 +259,13 @@ describe('testing createArtifactGraph', () => {
|
|||||||
if (err(extrusion)) throw extrusion
|
if (err(extrusion)) throw extrusion
|
||||||
expect(extrusion.type).toBe('sweep')
|
expect(extrusion.type).toBe('sweep')
|
||||||
const firstExtrusionIsACubeIE6Sides = 6
|
const firstExtrusionIsACubeIE6Sides = 6
|
||||||
const secondExtrusionIsATriangularPrismIE5Sides = 5
|
// Each face of the triangular prism (5), but without the bottom cap.
|
||||||
|
// The engine doesn't generate that.
|
||||||
|
const secondExtrusionIsATriangularPrism = 4
|
||||||
expect(extrusion.surfaces.length).toBe(
|
expect(extrusion.surfaces.length).toBe(
|
||||||
!index
|
!index
|
||||||
? firstExtrusionIsACubeIE6Sides
|
? firstExtrusionIsACubeIE6Sides
|
||||||
: secondExtrusionIsATriangularPrismIE5Sides
|
: secondExtrusionIsATriangularPrism
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -659,7 +661,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
sweepId: '',
|
sweepId: '',
|
||||||
codeRef: {
|
codeRef: {
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
range: [37, 64, 0],
|
range: [37, 64, true],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@ -672,7 +674,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: [],
|
surfaceIds: [],
|
||||||
edgeIds: [],
|
edgeIds: [],
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, 0],
|
range: [231, 254, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -683,7 +685,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
planeId: expect.any(String),
|
planeId: expect.any(String),
|
||||||
sweepId: expect.any(String),
|
sweepId: expect.any(String),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [37, 64, 0],
|
range: [37, 64, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
solid2dId: expect.any(String),
|
solid2dId: expect.any(String),
|
||||||
@ -697,7 +699,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: '',
|
surfaceId: '',
|
||||||
edgeIds: [],
|
edgeIds: [],
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [70, 86, 0],
|
range: [70, 86, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -708,7 +710,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
planeId: expect.any(String),
|
planeId: expect.any(String),
|
||||||
sweepId: expect.any(String),
|
sweepId: expect.any(String),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [37, 64, 0],
|
range: [37, 64, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
solid2dId: expect.any(String),
|
solid2dId: expect.any(String),
|
||||||
@ -723,7 +725,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
edgeIds: [],
|
edgeIds: [],
|
||||||
surfaceId: '',
|
surfaceId: '',
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [260, 299, 0],
|
range: [260, 299, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -734,7 +736,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: expect.any(String),
|
surfaceId: expect.any(String),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [92, 119, 0],
|
range: [92, 119, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
edgeCutId: expect.any(String),
|
edgeCutId: expect.any(String),
|
||||||
@ -756,7 +758,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: expect.any(String),
|
surfaceId: expect.any(String),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [156, 203, 0],
|
range: [156, 203, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -768,7 +770,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, 0],
|
range: [231, 254, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -787,7 +789,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: expect.any(String),
|
surfaceId: expect.any(String),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [125, 150, 0],
|
range: [125, 150, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -799,7 +801,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, 0],
|
range: [231, 254, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -818,7 +820,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: expect.any(String),
|
surfaceId: expect.any(String),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [92, 119, 0],
|
range: [92, 119, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
edgeCutId: expect.any(String),
|
edgeCutId: expect.any(String),
|
||||||
@ -831,7 +833,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, 0],
|
range: [231, 254, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -850,7 +852,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceId: expect.any(String),
|
surfaceId: expect.any(String),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [70, 86, 0],
|
range: [70, 86, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -862,7 +864,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, 0],
|
range: [231, 254, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -882,7 +884,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, 0],
|
range: [231, 254, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -902,7 +904,7 @@ describe('testing getArtifactsToUpdate', () => {
|
|||||||
surfaceIds: expect.any(Array),
|
surfaceIds: expect.any(Array),
|
||||||
edgeIds: expect.any(Array),
|
edgeIds: expect.any(Array),
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [231, 254, 0],
|
range: [231, 254, true],
|
||||||
pathToNode: [['body', '']],
|
pathToNode: [['body', '']],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -871,15 +871,3 @@ export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
|
|||||||
pathToNode: getNodePathFromSourceRange(ast, range),
|
pathToNode: getNodePathFromSourceRange(ast, range),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSolid2D(artifact: Artifact): artifact is solid2D {
|
|
||||||
return (artifact as solid2D).pathId !== undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSegment(artifact: Artifact): artifact is SegmentArtifact {
|
|
||||||
return (artifact as SegmentArtifact).pathId !== undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSweep(artifact: Artifact): artifact is SweepArtifact {
|
|
||||||
return (artifact as SweepArtifact).pathId !== undefined
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 326 KiB |
Before Width: | Height: | Size: 577 KiB After Width: | Height: | Size: 568 KiB |
@ -1,4 +1,11 @@
|
|||||||
import { defaultSourceRange, SourceRange } from 'lang/wasm'
|
import {
|
||||||
|
defaultRustSourceRange,
|
||||||
|
defaultSourceRange,
|
||||||
|
Program,
|
||||||
|
RustSourceRange,
|
||||||
|
SourceRange,
|
||||||
|
sourceRangeFromRust,
|
||||||
|
} from 'lang/wasm'
|
||||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { exportSave } from 'lib/exportSave'
|
import { exportSave } from 'lib/exportSave'
|
||||||
@ -1302,8 +1309,8 @@ export enum EngineCommandManagerEvents {
|
|||||||
|
|
||||||
interface PendingMessage {
|
interface PendingMessage {
|
||||||
command: EngineCommand
|
command: EngineCommand
|
||||||
range: SourceRange
|
range: RustSourceRange
|
||||||
idToRangeMap: { [key: string]: SourceRange }
|
idToRangeMap: { [key: string]: RustSourceRange }
|
||||||
resolve: (data: [Models['WebSocketResponse_type']]) => void
|
resolve: (data: [Models['WebSocketResponse_type']]) => void
|
||||||
reject: (reason: string) => void
|
reject: (reason: string) => void
|
||||||
promise: Promise<[Models['WebSocketResponse_type']]>
|
promise: Promise<[Models['WebSocketResponse_type']]>
|
||||||
@ -1993,7 +2000,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
{
|
{
|
||||||
command,
|
command,
|
||||||
idToRangeMap: {},
|
idToRangeMap: {},
|
||||||
range: defaultSourceRange(),
|
range: defaultRustSourceRange(),
|
||||||
},
|
},
|
||||||
true // isSceneCommand
|
true // isSceneCommand
|
||||||
)
|
)
|
||||||
@ -2024,9 +2031,9 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
return Promise.reject(new Error('rangeStr is undefined'))
|
return Promise.reject(new Error('rangeStr is undefined'))
|
||||||
if (commandStr === undefined)
|
if (commandStr === undefined)
|
||||||
return Promise.reject(new Error('commandStr is undefined'))
|
return Promise.reject(new Error('commandStr is undefined'))
|
||||||
const range: SourceRange = JSON.parse(rangeStr)
|
const range: RustSourceRange = JSON.parse(rangeStr)
|
||||||
const command: EngineCommand = JSON.parse(commandStr)
|
const command: EngineCommand = JSON.parse(commandStr)
|
||||||
const idToRangeMap: { [key: string]: SourceRange } =
|
const idToRangeMap: { [key: string]: RustSourceRange } =
|
||||||
JSON.parse(idToRangeStr)
|
JSON.parse(idToRangeStr)
|
||||||
|
|
||||||
// Current executeAst is stale, going to interrupt, a new executeAst will trigger
|
// Current executeAst is stale, going to interrupt, a new executeAst will trigger
|
||||||
@ -2069,10 +2076,14 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
if (message.command.type === 'modeling_cmd_req') {
|
if (message.command.type === 'modeling_cmd_req') {
|
||||||
this.orderedCommands.push({
|
this.orderedCommands.push({
|
||||||
command: message.command,
|
command: message.command,
|
||||||
range: message.range,
|
range: sourceRangeFromRust(message.range),
|
||||||
})
|
})
|
||||||
} else if (message.command.type === 'modeling_cmd_batch_req') {
|
} else if (message.command.type === 'modeling_cmd_batch_req') {
|
||||||
message.command.requests.forEach((req) => {
|
message.command.requests.forEach((req) => {
|
||||||
|
const cmdId = req.cmd_id || ''
|
||||||
|
const range = cmdId
|
||||||
|
? sourceRangeFromRust(message.idToRangeMap[cmdId])
|
||||||
|
: defaultSourceRange()
|
||||||
const cmd: EngineCommand = {
|
const cmd: EngineCommand = {
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: req.cmd_id,
|
cmd_id: req.cmd_id,
|
||||||
@ -2080,7 +2091,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
}
|
}
|
||||||
this.orderedCommands.push({
|
this.orderedCommands.push({
|
||||||
command: cmd,
|
command: cmd,
|
||||||
range: message.idToRangeMap[req.cmd_id || ''],
|
range,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -2099,30 +2110,23 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
* When an execution takes place we want to wait until we've got replies for all of the commands
|
* When an execution takes place we want to wait until we've got replies for all of the commands
|
||||||
* When this is done when we build the artifact map synchronously.
|
* When this is done when we build the artifact map synchronously.
|
||||||
*/
|
*/
|
||||||
async waitForAllCommands(useFakeExecutor = false) {
|
waitForAllCommands() {
|
||||||
await Promise.all(Object.values(this.pendingCommands).map((a) => a.promise))
|
return Promise.all(
|
||||||
setTimeout(() => {
|
Object.values(this.pendingCommands).map((a) => a.promise)
|
||||||
// the ast is wrong without this one tick timeout.
|
)
|
||||||
// an example is `Solids should be select and deletable` e2e test will fail
|
}
|
||||||
// because the out of date ast messes with selections
|
updateArtifactGraph(ast: Program) {
|
||||||
// TODO: race condition
|
this.artifactGraph = createArtifactGraph({
|
||||||
if (!this?.kclManager) return
|
orderedCommands: this.orderedCommands,
|
||||||
this.artifactGraph = createArtifactGraph({
|
responseMap: this.responseMap,
|
||||||
orderedCommands: this.orderedCommands,
|
ast,
|
||||||
responseMap: this.responseMap,
|
|
||||||
ast: this.kclManager.ast,
|
|
||||||
})
|
|
||||||
if (useFakeExecutor) {
|
|
||||||
// mock executions don't produce an artifactGraph, so this will always be empty
|
|
||||||
// skipping the below logic to wait for the next real execution
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.artifactGraph.size) {
|
|
||||||
this.deferredArtifactEmptied(null)
|
|
||||||
} else {
|
|
||||||
this.deferredArtifactPopulated(null)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
// TODO check if these still need to be deferred once e2e tests are working again.
|
||||||
|
if (this.artifactGraph.size) {
|
||||||
|
this.deferredArtifactEmptied(null)
|
||||||
|
} else {
|
||||||
|
this.deferredArtifactPopulated(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,6 +65,7 @@ export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
|
|||||||
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
|
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
|
||||||
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
|
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
|
||||||
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
|
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
|
||||||
|
export type { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
||||||
|
|
||||||
export type SyntaxType =
|
export type SyntaxType =
|
||||||
| 'Program'
|
| 'Program'
|
||||||
@ -117,6 +118,13 @@ export function defaultSourceRange(): SourceRange {
|
|||||||
return [0, 0, true]
|
return [0, 0, true]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a default RustSourceRange for testing or as a placeholder.
|
||||||
|
*/
|
||||||
|
export function defaultRustSourceRange(): RustSourceRange {
|
||||||
|
return [0, 0, 0]
|
||||||
|
}
|
||||||
|
|
||||||
export const wasmUrl = () => {
|
export const wasmUrl = () => {
|
||||||
// For when we're in electron (file based) or web server (network based)
|
// For when we're in electron (file based) or web server (network based)
|
||||||
// For some reason relative paths don't work as expected. Otherwise we would
|
// For some reason relative paths don't work as expected. Otherwise we would
|
||||||
|
@ -10,7 +10,7 @@ const noModifiersPressed = (e: MouseEvent) =>
|
|||||||
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
|
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
|
||||||
|
|
||||||
export type CameraSystem =
|
export type CameraSystem =
|
||||||
| 'KittyCAD'
|
| 'Zoo'
|
||||||
| 'OnShape'
|
| 'OnShape'
|
||||||
| 'Trackpad Friendly'
|
| 'Trackpad Friendly'
|
||||||
| 'Solidworks'
|
| 'Solidworks'
|
||||||
@ -19,7 +19,7 @@ export type CameraSystem =
|
|||||||
| 'AutoCAD'
|
| 'AutoCAD'
|
||||||
|
|
||||||
export const cameraSystems: CameraSystem[] = [
|
export const cameraSystems: CameraSystem[] = [
|
||||||
'KittyCAD',
|
'Zoo',
|
||||||
'OnShape',
|
'OnShape',
|
||||||
'Trackpad Friendly',
|
'Trackpad Friendly',
|
||||||
'Solidworks',
|
'Solidworks',
|
||||||
@ -34,9 +34,8 @@ export function mouseControlsToCameraSystem(
|
|||||||
switch (mouseControl) {
|
switch (mouseControl) {
|
||||||
// TODO: understand why the values come back without underscores and fix the root cause
|
// TODO: understand why the values come back without underscores and fix the root cause
|
||||||
// @ts-ignore: TS2678
|
// @ts-ignore: TS2678
|
||||||
case 'kittycad':
|
case 'zoo':
|
||||||
case 'kitty_cad':
|
return 'Zoo'
|
||||||
return 'KittyCAD'
|
|
||||||
// TODO: understand why the values come back without underscores and fix the root cause
|
// TODO: understand why the values come back without underscores and fix the root cause
|
||||||
// @ts-ignore: TS2678
|
// @ts-ignore: TS2678
|
||||||
case 'onshape':
|
case 'onshape':
|
||||||
@ -86,7 +85,7 @@ export const btnName = (e: MouseEvent) => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||||
KittyCAD: {
|
Zoo: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Shift + Right click drag or middle click drag',
|
description: 'Shift + Right click drag or middle click drag',
|
||||||
callback: (e) =>
|
callback: (e) =>
|
||||||
|
@ -3,7 +3,6 @@ import { engineCommandManager } from 'lib/singletons'
|
|||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { CommandBarContext } from 'machines/commandBarMachine'
|
import { CommandBarContext } from 'machines/commandBarMachine'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { isSolid2D, isSegment, isSweep } from 'lang/std/artifactGraph'
|
|
||||||
|
|
||||||
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
||||||
for (let tries = 0; tries < numberOfRetries; tries++) {
|
for (let tries = 0; tries < numberOfRetries; tries++) {
|
||||||
@ -64,7 +63,7 @@ export const revolveAxisValidator = async ({
|
|||||||
return 'Unable to revolve, sketch not found'
|
return 'Unable to revolve, sketch not found'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(isSolid2D(artifact) || isSegment(artifact) || isSweep(artifact))) {
|
if (!('pathId' in artifact)) {
|
||||||
return 'Unable to revolve, sketch has no path'
|
return 'Unable to revolve, sketch has no path'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,3 +118,21 @@ export const KCL_AXIS_Y = 'Y'
|
|||||||
export const KCL_AXIS_NEG_X = '-X'
|
export const KCL_AXIS_NEG_X = '-X'
|
||||||
export const KCL_AXIS_NEG_Y = '-Y'
|
export const KCL_AXIS_NEG_Y = '-Y'
|
||||||
export const KCL_DEFAULT_AXIS = 'X'
|
export const KCL_DEFAULT_AXIS = 'X'
|
||||||
|
|
||||||
|
export enum AxisNames {
|
||||||
|
X = 'x',
|
||||||
|
Y = 'y',
|
||||||
|
Z = 'z',
|
||||||
|
NEG_X = '-x',
|
||||||
|
NEG_Y = '-y',
|
||||||
|
NEG_Z = '-z',
|
||||||
|
}
|
||||||
|
/** Semantic names of views from AxisNames */
|
||||||
|
export const VIEW_NAMES_SEMANTIC = {
|
||||||
|
[AxisNames.X]: 'Right',
|
||||||
|
[AxisNames.Y]: 'Back',
|
||||||
|
[AxisNames.Z]: 'Top',
|
||||||
|
[AxisNames.NEG_X]: 'Left',
|
||||||
|
[AxisNames.NEG_Y]: 'Front',
|
||||||
|
[AxisNames.NEG_Z]: 'Bottom',
|
||||||
|
} as const
|
||||||
|
@ -15,6 +15,7 @@ import { fileSystemManager } from 'lang/std/fileSystemManager'
|
|||||||
import { getProjectInfo } from './desktop'
|
import { getProjectInfo } from './desktop'
|
||||||
import { createSettings } from './settings/initialSettings'
|
import { createSettings } from './settings/initialSettings'
|
||||||
import { normalizeLineEndings } from 'lib/codeEditor'
|
import { normalizeLineEndings } from 'lib/codeEditor'
|
||||||
|
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
|
||||||
|
|
||||||
// The root loader simply resolves the settings and any errors that
|
// The root loader simply resolves the settings and any errors that
|
||||||
// occurred during the settings load
|
// occurred during the settings load
|
||||||
@ -53,14 +54,15 @@ export const telemetryLoader: LoaderFunction = async ({
|
|||||||
// Redirect users to the appropriate onboarding page if they haven't completed it
|
// Redirect users to the appropriate onboarding page if they haven't completed it
|
||||||
export const onboardingRedirectLoader: ActionFunction = async (args) => {
|
export const onboardingRedirectLoader: ActionFunction = async (args) => {
|
||||||
const { settings } = await loadAndValidateSettings()
|
const { settings } = await loadAndValidateSettings()
|
||||||
const onboardingStatus = settings.app.onboardingStatus.current || ''
|
const onboardingStatus: OnboardingStatus =
|
||||||
|
settings.app.onboardingStatus.current || ''
|
||||||
const notEnRouteToOnboarding = !args.request.url.includes(
|
const notEnRouteToOnboarding = !args.request.url.includes(
|
||||||
PATHS.ONBOARDING.INDEX
|
PATHS.ONBOARDING.INDEX
|
||||||
)
|
)
|
||||||
// '' is the initial state, 'done' and 'dismissed' are the final states
|
// '' is the initial state, 'completed' and 'dismissed' are the final states
|
||||||
const hasValidOnboardingStatus =
|
const hasValidOnboardingStatus =
|
||||||
onboardingStatus.length === 0 ||
|
onboardingStatus.length === 0 ||
|
||||||
!(onboardingStatus === 'done' || onboardingStatus === 'dismissed')
|
!(onboardingStatus === 'completed' || onboardingStatus === 'dismissed')
|
||||||
const shouldRedirectToOnboarding =
|
const shouldRedirectToOnboarding =
|
||||||
notEnRouteToOnboarding && hasValidOnboardingStatus
|
notEnRouteToOnboarding && hasValidOnboardingStatus
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import Tooltip from 'components/Tooltip'
|
|||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
||||||
|
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A setting that can be set at the user or project level
|
* A setting that can be set at the user or project level
|
||||||
@ -189,8 +190,10 @@ export function createSettings() {
|
|||||||
inputType: 'boolean',
|
inputType: 'boolean',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
onboardingStatus: new Setting<string>({
|
onboardingStatus: new Setting<OnboardingStatus>({
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
|
// TODO: this could be better but we don't have a TS side real enum
|
||||||
|
// for this yet
|
||||||
validate: (v) => typeof v === 'string',
|
validate: (v) => typeof v === 'string',
|
||||||
hideOnPlatform: 'both',
|
hideOnPlatform: 'both',
|
||||||
}),
|
}),
|
||||||
@ -283,7 +286,7 @@ export function createSettings() {
|
|||||||
* The controls for how to navigate the 3D view
|
* The controls for how to navigate the 3D view
|
||||||
*/
|
*/
|
||||||
mouseControls: new Setting<CameraSystem>({
|
mouseControls: new Setting<CameraSystem>({
|
||||||
defaultValue: 'KittyCAD',
|
defaultValue: 'Zoo',
|
||||||
description: 'The controls for how to navigate the 3D view',
|
description: 'The controls for how to navigate the 3D view',
|
||||||
validate: (v) => cameraSystems.includes(v as CameraSystem),
|
validate: (v) => cameraSystems.includes(v as CameraSystem),
|
||||||
hideOnLevel: 'project',
|
hideOnLevel: 'project',
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
export const onboardingPaths = {
|
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
|
||||||
|
|
||||||
|
export const onboardingPaths: Record<string, OnboardingStatus> = {
|
||||||
INDEX: '/',
|
INDEX: '/',
|
||||||
CAMERA: '/camera',
|
CAMERA: '/camera',
|
||||||
STREAMING: '/streaming',
|
STREAMING: '/streaming',
|
||||||
|
95
src/wasm-lib/Cargo.lock
generated
@ -121,9 +121,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.93"
|
version = "1.0.94"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
|
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
]
|
]
|
||||||
@ -401,9 +401,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.38"
|
version = "0.4.39"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
@ -723,7 +723,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
version = "0.1.32"
|
version = "0.1.33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -1112,7 +1112,7 @@ dependencies = [
|
|||||||
"fnv",
|
"fnv",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"indexmap 2.7.0",
|
"indexmap 2.7.0",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -1215,9 +1215,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@ -1242,7 +1242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1253,7 +1253,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
@ -1303,7 +1303,7 @@ dependencies = [
|
|||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"h2",
|
"h2",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"httparse",
|
"httparse",
|
||||||
"itoa",
|
"itoa",
|
||||||
@ -1320,7 +1320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
|
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"hyper 1.4.1",
|
"hyper 1.4.1",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"rustls",
|
"rustls",
|
||||||
@ -1340,7 +1340,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"hyper 1.4.1",
|
"hyper 1.4.1",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@ -1674,16 +1674,17 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.72"
|
version = "0.3.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
|
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.2.28"
|
version = "0.2.29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx 0.5.1",
|
"approx 0.5.1",
|
||||||
@ -1705,7 +1706,7 @@ dependencies = [
|
|||||||
"git_rev",
|
"git_rev",
|
||||||
"gltf-json",
|
"gltf-json",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"iai",
|
"iai",
|
||||||
"image",
|
"image",
|
||||||
"indexmap 2.7.0",
|
"indexmap 2.7.0",
|
||||||
@ -1751,7 +1752,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
version = "0.1.18"
|
version = "0.1.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"hyper 0.14.30",
|
"hyper 0.14.30",
|
||||||
@ -1792,7 +1793,7 @@ dependencies = [
|
|||||||
"data-encoding",
|
"data-encoding",
|
||||||
"format_serde_error",
|
"format_serde_error",
|
||||||
"futures",
|
"futures",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
"log",
|
"log",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
@ -1818,9 +1819,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad-modeling-cmds"
|
name = "kittycad-modeling-cmds"
|
||||||
version = "0.2.77"
|
version = "0.2.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b77259b37acafa360d98af27431ac394bc8899eeed7037513832ddbee856811"
|
checksum = "10a9cab4476455be70ea57643c31444068b056d091bd348cab6044c0d8ad7fcc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -1828,7 +1829,7 @@ dependencies = [
|
|||||||
"enum-iterator",
|
"enum-iterator",
|
||||||
"enum-iterator-derive",
|
"enum-iterator-derive",
|
||||||
"euler",
|
"euler",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"kittycad-modeling-cmds-macros",
|
"kittycad-modeling-cmds-macros",
|
||||||
"kittycad-unit-conversion-derive",
|
"kittycad-unit-conversion-derive",
|
||||||
"measurements",
|
"measurements",
|
||||||
@ -2862,7 +2863,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"h2",
|
"h2",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.4.1",
|
"hyper 1.4.1",
|
||||||
@ -2904,7 +2905,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b"
|
checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"reqwest-middleware",
|
"reqwest-middleware",
|
||||||
]
|
]
|
||||||
@ -2917,7 +2918,7 @@ checksum = "d1ccd3b55e711f91a9885a2fa6fbbb2e39db1776420b062efc058c6410f7e5e3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 1.0.68",
|
"thiserror 1.0.68",
|
||||||
@ -2934,7 +2935,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"futures",
|
"futures",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"hyper 1.4.1",
|
"hyper 1.4.1",
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@ -2955,7 +2956,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"matchit",
|
"matchit",
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@ -3199,9 +3200,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.215"
|
version = "1.0.216"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@ -3217,9 +3218,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.215"
|
version = "1.0.216"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -4027,7 +4028,7 @@ dependencies = [
|
|||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"http 1.1.0",
|
"http 1.2.0",
|
||||||
"httparse",
|
"httparse",
|
||||||
"log",
|
"log",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
@ -4235,9 +4236,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.95"
|
version = "0.2.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
|
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -4246,13 +4247,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.95"
|
version = "0.2.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
|
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.87",
|
"syn 2.0.87",
|
||||||
@ -4261,22 +4261,23 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.44"
|
version = "0.4.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "65471f79c1022ffa5291d33520cbbb53b7687b01c2f8e83b57d102eed7ed479d"
|
checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.95"
|
version = "0.2.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
|
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@ -4284,9 +4285,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.95"
|
version = "0.2.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -4297,9 +4298,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.95"
|
version = "0.2.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-lib"
|
name = "wasm-lib"
|
||||||
@ -4361,9 +4362,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.72"
|
version = "0.3.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
|
checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
@ -20,8 +20,8 @@ serde_json = "1.0.128"
|
|||||||
tokio = { version = "1.41.1", features = ["sync"] }
|
tokio = { version = "1.41.1", features = ["sync"] }
|
||||||
toml = "0.8.19"
|
toml = "0.8.19"
|
||||||
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
||||||
wasm-bindgen = "0.2.91"
|
wasm-bindgen = "0.2.99"
|
||||||
wasm-bindgen-futures = "0.4.44"
|
wasm-bindgen-futures = "0.4.49"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
@ -43,7 +43,7 @@ wasm-bindgen-futures = { version = "0.4.44", features = ["futures-core-03-stream
|
|||||||
wasm-streams = "0.4.1"
|
wasm-streams = "0.4.1"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
|
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
|
||||||
version = "0.3.72"
|
version = "0.3.76"
|
||||||
features = [
|
features = [
|
||||||
"console",
|
"console",
|
||||||
"HtmlTextAreaElement",
|
"HtmlTextAreaElement",
|
||||||
@ -76,7 +76,7 @@ members = [
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
http = "1"
|
http = "1"
|
||||||
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
|
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
|
||||||
kittycad-modeling-cmds = { version = "0.2.77", features = ["websocket"] }
|
kittycad-modeling-cmds = { version = "0.2.79", features = ["websocket"] }
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
assertions_on_result_states = "warn"
|
assertions_on_result_states = "warn"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
description = "A tool for generating documentation from Rust derive macros"
|
description = "A tool for generating documentation from Rust derive macros"
|
||||||
version = "0.1.32"
|
version = "0.1.33"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
mod tests;
|
mod tests;
|
||||||
mod unbox;
|
mod unbox;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use convert_case::Casing;
|
use convert_case::Casing;
|
||||||
use inflector::Inflector;
|
use inflector::Inflector;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
@ -47,6 +49,10 @@ struct StdlibMetadata {
|
|||||||
/// If false, all arguments require labels.
|
/// If false, all arguments require labels.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
unlabeled_first: bool,
|
unlabeled_first: bool,
|
||||||
|
|
||||||
|
/// Key = argument name, value = argument doc.
|
||||||
|
#[serde(default)]
|
||||||
|
arg_docs: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
@ -282,6 +288,17 @@ fn do_stdlib_inner(
|
|||||||
|
|
||||||
let ty_string = rust_type_to_openapi_type(&ty_string);
|
let ty_string = rust_type_to_openapi_type(&ty_string);
|
||||||
let required = !ty_ident.to_string().starts_with("Option <");
|
let required = !ty_ident.to_string().starts_with("Option <");
|
||||||
|
let description = if let Some(s) = metadata.arg_docs.get(&arg_name) {
|
||||||
|
quote! { #s }
|
||||||
|
} else if metadata.keywords && ty_string != "Args" && ty_string != "ExecState" {
|
||||||
|
errors.push(Error::new_spanned(
|
||||||
|
&arg,
|
||||||
|
"Argument was not documented in the arg_docs block",
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
quote! { String::new() }
|
||||||
|
};
|
||||||
let label_required = !(i == 0 && metadata.unlabeled_first);
|
let label_required = !(i == 0 && metadata.unlabeled_first);
|
||||||
if ty_string != "ExecState" && ty_string != "Args" {
|
if ty_string != "ExecState" && ty_string != "Args" {
|
||||||
let schema = quote! {
|
let schema = quote! {
|
||||||
@ -294,6 +311,7 @@ fn do_stdlib_inner(
|
|||||||
schema: #schema,
|
schema: #schema,
|
||||||
required: #required,
|
required: #required,
|
||||||
label_required: #label_required,
|
label_required: #label_required,
|
||||||
|
description: #description.to_string(),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -355,6 +373,7 @@ fn do_stdlib_inner(
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -116,6 +116,9 @@ fn test_stdlib_line_to() {
|
|||||||
let (item, errors) = do_stdlib(
|
let (item, errors) = do_stdlib(
|
||||||
quote! {
|
quote! {
|
||||||
name = "lineTo",
|
name = "lineTo",
|
||||||
|
arg_docs = {
|
||||||
|
sketch = "the sketch you're adding the line to"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
quote! {
|
quote! {
|
||||||
/// This is some function.
|
/// This is some function.
|
||||||
|
@ -91,6 +91,7 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
schema: generator.root_schema_for::<Foo>(),
|
schema: generator.root_schema_for::<Foo>(),
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +106,7 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +91,7 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
schema: generator.root_schema_for::<str>(),
|
schema: generator.root_schema_for::<str>(),
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +106,7 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +129,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
schema: generator.root_schema_for::<[f64; 2usize]>(),
|
schema: generator.root_schema_for::<[f64; 2usize]>(),
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +144,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
schema: generator.root_schema_for::<f64>(),
|
schema: generator.root_schema_for::<f64>(),
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +130,7 @@ impl crate::docs::StdLibFn for MyFunc {
|
|||||||
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
||||||
required: false,
|
required: false,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +145,7 @@ impl crate::docs::StdLibFn for MyFunc {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +131,7 @@ impl crate::docs::StdLibFn for LineTo {
|
|||||||
schema: generator.root_schema_for::<LineToData>(),
|
schema: generator.root_schema_for::<LineToData>(),
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
},
|
},
|
||||||
crate::docs::StdLibFnArg {
|
crate::docs::StdLibFnArg {
|
||||||
name: "sketch".to_string(),
|
name: "sketch".to_string(),
|
||||||
@ -138,6 +139,7 @@ impl crate::docs::StdLibFn for LineTo {
|
|||||||
schema: generator.root_schema_for::<Sketch>(),
|
schema: generator.root_schema_for::<Sketch>(),
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: "the sketch you're adding the line to".to_string(),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -153,6 +155,7 @@ impl crate::docs::StdLibFn for LineTo {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +129,7 @@ impl crate::docs::StdLibFn for Min {
|
|||||||
schema: generator.root_schema_for::<Vec<f64>>(),
|
schema: generator.root_schema_for::<Vec<f64>>(),
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +144,7 @@ impl crate::docs::StdLibFn for Min {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
schema: generator.root_schema_for::<Option<f64>>(),
|
schema: generator.root_schema_for::<Option<f64>>(),
|
||||||
required: false,
|
required: false,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
||||||
required: false,
|
required: false,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
||||||
required: false,
|
required: false,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
||||||
required: false,
|
required: false,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
schema: generator.root_schema_for::<Vec<f64>>(),
|
schema: generator.root_schema_for::<Vec<f64>>(),
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new().to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +99,7 @@ impl crate::docs::StdLibFn for SomeFunction {
|
|||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
label_required: true,
|
label_required: true,
|
||||||
|
description: String::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
description = "A test server for KCL"
|
description = "A test server for KCL"
|
||||||
version = "0.1.18"
|
version = "0.1.19"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
@ -189,7 +189,11 @@ impl EngineConnection {
|
|||||||
uuid_to_cpp(path_id)
|
uuid_to_cpp(path_id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
kcmc::ModelingCmd::Extrude(kcmc::Extrude { distance, target }) => {
|
kcmc::ModelingCmd::Extrude(kcmc::Extrude {
|
||||||
|
distance,
|
||||||
|
target,
|
||||||
|
faces: _, // Engine team: start using this once the frontend and engine both use it.
|
||||||
|
}) => {
|
||||||
format!(
|
format!(
|
||||||
r#"
|
r#"
|
||||||
scene->getSceneObject(Utils::UUID("{target}"))->extrudeToSolid3D({} * scaleFactor, true);
|
scene->getSceneObject(Utils::UUID("{target}"))->extrudeToSolid3D({} * scaleFactor, true);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.2.28"
|
version = "0.2.29"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
@ -22,7 +22,7 @@ clap = { version = "4.5.21", default-features = false, optional = true, features
|
|||||||
] }
|
] }
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
dashmap = "6.1.0"
|
dashmap = "6.1.0"
|
||||||
derive-docs = { version = "0.1.32", path = "../derive-docs" }
|
derive-docs = { version = "0.1.33", path = "../derive-docs" }
|
||||||
dhat = { version = "0.3", optional = true }
|
dhat = { version = "0.3", optional = true }
|
||||||
fnv = "1.0.7"
|
fnv = "1.0.7"
|
||||||
form_urlencoded = "1.2.1"
|
form_urlencoded = "1.2.1"
|
||||||
@ -82,9 +82,9 @@ tokio = { version = "1.41.1", features = ["sync", "time"] }
|
|||||||
tower-lsp = { version = "0.20.0", default-features = false, features = [
|
tower-lsp = { version = "0.20.0", default-features = false, features = [
|
||||||
"runtime-agnostic",
|
"runtime-agnostic",
|
||||||
] }
|
] }
|
||||||
wasm-bindgen = "0.2.91"
|
wasm-bindgen = "0.2.99"
|
||||||
wasm-bindgen-futures = "0.4.44"
|
wasm-bindgen-futures = "0.4.49"
|
||||||
web-sys = { version = "0.3.72", features = ["console"] }
|
web-sys = { version = "0.3.76", features = ["console"] }
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
approx = "0.5"
|
approx = "0.5"
|
||||||
|
@ -597,6 +597,8 @@ fn clean_function_name(name: &str) -> String {
|
|||||||
fn_name = fn_name.replace("seg_", "segment_");
|
fn_name = fn_name.replace("seg_", "segment_");
|
||||||
} else if fn_name.starts_with("log_") {
|
} else if fn_name.starts_with("log_") {
|
||||||
fn_name = fn_name.replace("log_", "log");
|
fn_name = fn_name.replace("log_", "log");
|
||||||
|
} else if fn_name.ends_with("tan_2") {
|
||||||
|
fn_name = fn_name.replace("tan_2", "tan2");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn_name
|
fn_name
|
||||||
|
@ -13,6 +13,8 @@ use tower_lsp::lsp_types::{
|
|||||||
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
|
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::execution::Sketch;
|
||||||
|
|
||||||
use crate::std::Primitive;
|
use crate::std::Primitive;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
|
||||||
@ -57,6 +59,12 @@ pub struct StdLibFnArg {
|
|||||||
pub schema: schemars::schema::RootSchema,
|
pub schema: schemars::schema::RootSchema,
|
||||||
/// If the argument is required.
|
/// If the argument is required.
|
||||||
pub required: bool,
|
pub required: bool,
|
||||||
|
/// Additional information that could be used instead of the type's description.
|
||||||
|
/// This is helpful if the type is really basic, like "u32" -- that won't tell the user much about
|
||||||
|
/// how this argument is meant to be used.
|
||||||
|
/// Empty string means this has no docs.
|
||||||
|
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||||
|
pub description: String,
|
||||||
/// Even in functions that use keyword arguments, not every parameter requires a label (most do though).
|
/// Even in functions that use keyword arguments, not every parameter requires a label (most do though).
|
||||||
/// Some functions allow one unlabeled parameter, which has to be first in the
|
/// Some functions allow one unlabeled parameter, which has to be first in the
|
||||||
/// argument list.
|
/// argument list.
|
||||||
@ -104,6 +112,11 @@ impl StdLibFnArg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn description(&self) -> Option<String> {
|
pub fn description(&self) -> Option<String> {
|
||||||
|
// Check if we explicitly gave this stdlib arg a description.
|
||||||
|
if !self.description.is_empty() {
|
||||||
|
return Some(self.description.clone());
|
||||||
|
}
|
||||||
|
// If not, then try to get something meaningful from the schema.
|
||||||
get_description_string_from_schema(&self.schema.clone())
|
get_description_string_from_schema(&self.schema.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,6 +245,11 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn to_autocomplete_snippet(&self) -> Result<String> {
|
fn to_autocomplete_snippet(&self) -> Result<String> {
|
||||||
|
if self.name() == "loft" {
|
||||||
|
return Ok("loft([${0:sketch000}, ${1:sketch001}])${}".to_string());
|
||||||
|
} else if self.name() == "hole" {
|
||||||
|
return Ok("hole(${0:holeSketch}, ${1:%})${}".to_string());
|
||||||
|
}
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
for arg in self.args(true).iter() {
|
for arg in self.args(true).iter() {
|
||||||
@ -451,6 +469,16 @@ fn get_autocomplete_snippet_from_schema(
|
|||||||
) -> Result<Option<(usize, String)>> {
|
) -> Result<Option<(usize, String)>> {
|
||||||
match schema {
|
match schema {
|
||||||
schemars::schema::Schema::Object(o) => {
|
schemars::schema::Schema::Object(o) => {
|
||||||
|
// Check if the schema is the same as a Sketch.
|
||||||
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
|
// We set this so we can recurse them later.
|
||||||
|
settings.inline_subschemas = true;
|
||||||
|
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
||||||
|
let sketch_schema = generator.root_schema_for::<Sketch>().schema;
|
||||||
|
if sketch_schema.object == o.object {
|
||||||
|
return Ok(Some((index, format!("${{{}:sketch{}}}", index, "000"))));
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(serde_json::Value::Bool(nullable)) = o.extensions.get("nullable") {
|
if let Some(serde_json::Value::Bool(nullable)) = o.extensions.get("nullable") {
|
||||||
if *nullable {
|
if *nullable {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@ -967,6 +995,32 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_autocomplete_snippet_loft() {
|
||||||
|
let loft_fn: Box<dyn StdLibFn> = Box::new(crate::std::loft::Loft);
|
||||||
|
let snippet = loft_fn.to_autocomplete_snippet().unwrap();
|
||||||
|
assert_eq!(snippet, r#"loft([${0:sketch000}, ${1:sketch001}])${}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_autocomplete_snippet_sweep() {
|
||||||
|
let sweep_fn: Box<dyn StdLibFn> = Box::new(crate::std::sweep::Sweep);
|
||||||
|
let snippet = sweep_fn.to_autocomplete_snippet().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
snippet,
|
||||||
|
r#"sweep({
|
||||||
|
path: ${0:sketch000},
|
||||||
|
}, ${1:%})${}"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_autocomplete_snippet_hole() {
|
||||||
|
let hole_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Hole);
|
||||||
|
let snippet = hole_fn.to_autocomplete_snippet().unwrap();
|
||||||
|
assert_eq!(snippet, r#"hole(${0:holeSketch}, ${1:%})${}"#);
|
||||||
|
}
|
||||||
|
|
||||||
// We want to test the snippets we compile at lsp start.
|
// We want to test the snippets we compile at lsp start.
|
||||||
#[test]
|
#[test]
|
||||||
fn get_all_stdlib_autocomplete_snippets() {
|
fn get_all_stdlib_autocomplete_snippets() {
|
||||||
|
@ -353,7 +353,6 @@ impl Node<CallExpressionKw> {
|
|||||||
|
|
||||||
// Build a hashmap from argument labels to the final evaluated values.
|
// Build a hashmap from argument labels to the final evaluated values.
|
||||||
let mut fn_args = HashMap::with_capacity(self.arguments.len());
|
let mut fn_args = HashMap::with_capacity(self.arguments.len());
|
||||||
let mut tag_declarator_args = Vec::new();
|
|
||||||
for arg_expr in &self.arguments {
|
for arg_expr in &self.arguments {
|
||||||
let source_range = SourceRange::from(arg_expr.arg.clone());
|
let source_range = SourceRange::from(arg_expr.arg.clone());
|
||||||
let metadata = Metadata { source_range };
|
let metadata = Metadata { source_range };
|
||||||
@ -361,12 +360,8 @@ impl Node<CallExpressionKw> {
|
|||||||
.execute_expr(&arg_expr.arg, exec_state, &metadata, StatementKind::Expression)
|
.execute_expr(&arg_expr.arg, exec_state, &metadata, StatementKind::Expression)
|
||||||
.await?;
|
.await?;
|
||||||
fn_args.insert(arg_expr.label.name.clone(), Arg::new(value, source_range));
|
fn_args.insert(arg_expr.label.name.clone(), Arg::new(value, source_range));
|
||||||
if let Expr::TagDeclarator(td) = &arg_expr.arg {
|
|
||||||
tag_declarator_args.push((td.inner.clone(), source_range));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let fn_args = fn_args; // remove mutability
|
let fn_args = fn_args; // remove mutability
|
||||||
let tag_declarator_args = tag_declarator_args; // remove mutability
|
|
||||||
|
|
||||||
// Evaluate the unlabeled first param, if any exists.
|
// Evaluate the unlabeled first param, if any exists.
|
||||||
let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
|
let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
|
||||||
@ -392,7 +387,7 @@ impl Node<CallExpressionKw> {
|
|||||||
FunctionKind::Core(func) => {
|
FunctionKind::Core(func) => {
|
||||||
// Attempt to call the function.
|
// Attempt to call the function.
|
||||||
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
||||||
update_memory_for_tags_of_geometry(&mut result, &tag_declarator_args, exec_state)?;
|
update_memory_for_tags_of_geometry(&mut result, exec_state)?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
FunctionKind::UserDefined => {
|
FunctionKind::UserDefined => {
|
||||||
@ -440,7 +435,6 @@ impl Node<CallExpression> {
|
|||||||
let fn_name = &self.callee.name;
|
let fn_name = &self.callee.name;
|
||||||
|
|
||||||
let mut fn_args: Vec<Arg> = Vec::with_capacity(self.arguments.len());
|
let mut fn_args: Vec<Arg> = Vec::with_capacity(self.arguments.len());
|
||||||
let mut tag_declarator_args = Vec::new();
|
|
||||||
|
|
||||||
for arg_expr in &self.arguments {
|
for arg_expr in &self.arguments {
|
||||||
let metadata = Metadata {
|
let metadata = Metadata {
|
||||||
@ -450,19 +444,15 @@ impl Node<CallExpression> {
|
|||||||
.execute_expr(arg_expr, exec_state, &metadata, StatementKind::Expression)
|
.execute_expr(arg_expr, exec_state, &metadata, StatementKind::Expression)
|
||||||
.await?;
|
.await?;
|
||||||
let arg = Arg::new(value, SourceRange::from(arg_expr));
|
let arg = Arg::new(value, SourceRange::from(arg_expr));
|
||||||
if let Expr::TagDeclarator(td) = arg_expr {
|
|
||||||
tag_declarator_args.push((td.inner.clone(), arg.source_range));
|
|
||||||
}
|
|
||||||
fn_args.push(arg);
|
fn_args.push(arg);
|
||||||
}
|
}
|
||||||
let tag_declarator_args = tag_declarator_args; // remove mutability
|
|
||||||
|
|
||||||
match ctx.stdlib.get_either(fn_name) {
|
match ctx.stdlib.get_either(fn_name) {
|
||||||
FunctionKind::Core(func) => {
|
FunctionKind::Core(func) => {
|
||||||
// Attempt to call the function.
|
// Attempt to call the function.
|
||||||
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
||||||
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
||||||
update_memory_for_tags_of_geometry(&mut result, &tag_declarator_args, exec_state)?;
|
update_memory_for_tags_of_geometry(&mut result, exec_state)?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
FunctionKind::UserDefined => {
|
FunctionKind::UserDefined => {
|
||||||
@ -501,24 +491,7 @@ impl Node<CallExpression> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `tag_declarator_args` should only contain tag declarator literals, which
|
fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut ExecState) -> Result<(), KclError> {
|
||||||
/// will be defined as local variables. Non-literals that evaluate to tag
|
|
||||||
/// declarators should not be defined.
|
|
||||||
fn update_memory_for_tags_of_geometry(
|
|
||||||
result: &mut KclValue,
|
|
||||||
tag_declarator_args: &[(TagDeclarator, SourceRange)],
|
|
||||||
exec_state: &mut ExecState,
|
|
||||||
) -> Result<(), KclError> {
|
|
||||||
// Define all the tags in the memory.
|
|
||||||
for (tag_declarator, arg_sr) in tag_declarator_args {
|
|
||||||
let tag = TagIdentifier {
|
|
||||||
value: tag_declarator.name.clone(),
|
|
||||||
info: None,
|
|
||||||
meta: vec![Metadata { source_range: *arg_sr }],
|
|
||||||
};
|
|
||||||
|
|
||||||
exec_state.memory.add_tag(&tag.value, tag.clone(), *arg_sr)?;
|
|
||||||
}
|
|
||||||
// If the return result is a sketch or solid, we want to update the
|
// If the return result is a sketch or solid, we want to update the
|
||||||
// memory for the tags of the group.
|
// memory for the tags of the group.
|
||||||
// TODO: This could probably be done in a better way, but as of now this was my only idea
|
// TODO: This could probably be done in a better way, but as of now this was my only idea
|
||||||
@ -526,7 +499,7 @@ fn update_memory_for_tags_of_geometry(
|
|||||||
match result {
|
match result {
|
||||||
KclValue::Sketch { value: ref mut sketch } => {
|
KclValue::Sketch { value: ref mut sketch } => {
|
||||||
for (_, tag) in sketch.tags.iter() {
|
for (_, tag) in sketch.tags.iter() {
|
||||||
exec_state.memory.update_tag_if_defined(&tag.value, tag.clone());
|
exec_state.memory.update_tag(&tag.value, tag.clone())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KclValue::Solid(ref mut solid) => {
|
KclValue::Solid(ref mut solid) => {
|
||||||
@ -564,7 +537,7 @@ fn update_memory_for_tags_of_geometry(
|
|||||||
info.sketch = solid.id;
|
info.sketch = solid.id;
|
||||||
t.info = Some(info);
|
t.info = Some(info);
|
||||||
|
|
||||||
exec_state.memory.update_tag_if_defined(&tag.name, t.clone());
|
exec_state.memory.update_tag(&tag.name, t.clone())?;
|
||||||
|
|
||||||
// update the sketch tags.
|
// update the sketch tags.
|
||||||
solid.sketch.tags.insert(tag.name.clone(), t);
|
solid.sketch.tags.insert(tag.name.clone(), t);
|
||||||
@ -585,6 +558,22 @@ fn update_memory_for_tags_of_geometry(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Node<TagDeclarator> {
|
||||||
|
pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
||||||
|
let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
|
||||||
|
value: self.name.clone(),
|
||||||
|
info: None,
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: self.into(),
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
|
||||||
|
exec_state.memory.add(&self.name, memory_item.clone(), self.into())?;
|
||||||
|
|
||||||
|
Ok(self.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Node<ArrayExpression> {
|
impl Node<ArrayExpression> {
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
@ -124,16 +124,10 @@ impl ProgramMemory {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_tag(&mut self, tag: &str, value: TagIdentifier, source_range: SourceRange) -> Result<(), KclError> {
|
pub fn update_tag(&mut self, tag: &str, value: TagIdentifier) -> Result<(), KclError> {
|
||||||
self.add(tag, KclValue::TagIdentifier(Box::new(value)), source_range)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_tag_if_defined(&mut self, tag: &str, value: TagIdentifier) {
|
|
||||||
if !self.environments[self.current_env.index()].contains_key(tag) {
|
|
||||||
// Do nothing if the tag isn't defined.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.environments[self.current_env.index()].insert(tag.to_string(), KclValue::TagIdentifier(Box::new(value)));
|
self.environments[self.current_env.index()].insert(tag.to_string(), KclValue::TagIdentifier(Box::new(value)));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a value from the program memory.
|
/// Get a value from the program memory.
|
||||||
@ -850,7 +844,7 @@ impl GetTangentialInfoFromPathsResult {
|
|||||||
|
|
||||||
impl Sketch {
|
impl Sketch {
|
||||||
pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path) {
|
pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path) {
|
||||||
let mut tag_identifier = TagIdentifier::from(tag);
|
let mut tag_identifier: TagIdentifier = tag.into();
|
||||||
let base = current_path.get_base();
|
let base = current_path.get_base();
|
||||||
tag_identifier.info = Some(TagEngineInfo {
|
tag_identifier.info = Some(TagEngineInfo {
|
||||||
id: base.geo_meta.id,
|
id: base.geo_meta.id,
|
||||||
@ -1895,11 +1889,19 @@ impl ExecutorContext {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if cache_result.clear_scene && !self.is_mock() {
|
if cache_result.clear_scene && !self.is_mock() {
|
||||||
|
// Pop the execution state, since we are starting fresh.
|
||||||
|
let mut id_generator = exec_state.id_generator.clone();
|
||||||
|
// We do not pop the ids, since we want to keep the same id generator.
|
||||||
|
// This is for the front end to keep track of the ids.
|
||||||
|
id_generator.next_id = 0;
|
||||||
|
*exec_state = ExecState {
|
||||||
|
id_generator,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
// We don't do this in mock mode since there is no engine connection
|
// We don't do this in mock mode since there is no engine connection
|
||||||
// anyways and from the TS side we override memory and don't want to clear it.
|
// anyways and from the TS side we override memory and don't want to clear it.
|
||||||
self.reset_scene(exec_state, Default::default()).await?;
|
self.reset_scene(exec_state, Default::default()).await?;
|
||||||
// Pop the execution state, since we are starting fresh.
|
|
||||||
*exec_state = Default::default();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use the top-level file's path.
|
// TODO: Use the top-level file's path.
|
||||||
@ -2128,7 +2130,7 @@ impl ExecutorContext {
|
|||||||
let item = match init {
|
let item = match init {
|
||||||
Expr::None(none) => KclValue::from(none),
|
Expr::None(none) => KclValue::from(none),
|
||||||
Expr::Literal(literal) => KclValue::from(literal),
|
Expr::Literal(literal) => KclValue::from(literal),
|
||||||
Expr::TagDeclarator(tag) => KclValue::from(tag),
|
Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
|
||||||
Expr::Identifier(identifier) => {
|
Expr::Identifier(identifier) => {
|
||||||
let value = exec_state.memory.get(&identifier.name, identifier.into())?;
|
let value = exec_state.memory.get(&identifier.name, identifier.into())?;
|
||||||
value.clone()
|
value.clone()
|
||||||
@ -2808,6 +2810,28 @@ const answer = returnX()"#;
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_cannot_shebang_in_fn() {
|
||||||
|
let ast = r#"
|
||||||
|
fn foo () {
|
||||||
|
#!hello
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
foo
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = parse_execute(ast).await;
|
||||||
|
let err = result.unwrap_err().downcast::<KclError>().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
message: "Unexpected token: #".to_owned(),
|
||||||
|
source_ranges: vec![SourceRange::new(15, 16, ModuleId::default())],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_pattern_transform_function_cannot_access_future_definitions() {
|
async fn test_pattern_transform_function_cannot_access_future_definitions() {
|
||||||
let ast = r#"
|
let ast = r#"
|
||||||
@ -3024,10 +3048,8 @@ let notTagDeclarator = !myTagDeclarator";
|
|||||||
);
|
);
|
||||||
|
|
||||||
let code9 = "
|
let code9 = "
|
||||||
sk = startSketchOn('XY')
|
let myTagDeclarator = $myTag
|
||||||
|> startProfileAt([0, 0], %)
|
let notTagIdentifier = !myTag";
|
||||||
|> line([5, 0], %, $myTag)
|
|
||||||
notTagIdentifier = !myTag";
|
|
||||||
let tag_identifier_err = parse_execute(code9).await.unwrap_err().downcast::<KclError>().unwrap();
|
let tag_identifier_err = parse_execute(code9).await.unwrap_err().downcast::<KclError>().unwrap();
|
||||||
// These are currently printed out as JSON objects, so we don't want to
|
// These are currently printed out as JSON objects, so we don't want to
|
||||||
// check the full error.
|
// check the full error.
|
||||||
@ -3497,12 +3519,7 @@ shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_some());
|
assert!(result.is_none());
|
||||||
|
|
||||||
let result = result.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(result.program, program_new.ast);
|
|
||||||
assert!(result.clear_scene);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changing the units with the exact same file should bust the cache.
|
// Changing the units with the exact same file should bust the cache.
|
||||||
|
@ -3,11 +3,10 @@ use sha2::{Digest as DigestTrait, Sha256};
|
|||||||
use super::types::{DefaultParamVal, ItemVisibility, LabelledExpression, VariableKind};
|
use super::types::{DefaultParamVal, ItemVisibility, LabelledExpression, VariableKind};
|
||||||
use crate::parsing::ast::types::{
|
use crate::parsing::ast::types::{
|
||||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw,
|
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw,
|
||||||
CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression,
|
ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem,
|
||||||
ImportItem, ImportSelector, ImportStatement, Literal, LiteralIdentifier, MemberExpression, MemberObject,
|
ImportSelector, ImportStatement, KclNone, Literal, LiteralIdentifier, MemberExpression, MemberObject,
|
||||||
NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression,
|
ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement,
|
||||||
PipeSubstitution, Program, ReturnStatement, TagDeclarator, UnaryExpression, VariableDeclaration,
|
TagDeclarator, UnaryExpression, VariableDeclaration, VariableDeclarator,
|
||||||
VariableDeclarator,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Position-independent digest of the AST node.
|
/// Position-independent digest of the AST node.
|
||||||
@ -82,7 +81,6 @@ impl Program {
|
|||||||
if let Some(shebang) = &slf.shebang {
|
if let Some(shebang) = &slf.shebang {
|
||||||
hasher.update(&shebang.inner.content);
|
hasher.update(&shebang.inner.content);
|
||||||
}
|
}
|
||||||
hasher.update(slf.non_code_meta.compute_digest());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,6 +201,12 @@ impl Parameter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl KclNone {
|
||||||
|
compute_digest!(|slf, hasher| {
|
||||||
|
hasher.update(b"KclNone");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
impl FunctionExpression {
|
impl FunctionExpression {
|
||||||
compute_digest!(|slf, hasher| {
|
compute_digest!(|slf, hasher| {
|
||||||
hasher.update(slf.params.len().to_ne_bytes());
|
hasher.update(slf.params.len().to_ne_bytes());
|
||||||
@ -228,53 +232,6 @@ impl ReturnStatement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommentStyle {
|
|
||||||
fn digestable_id(&self) -> [u8; 2] {
|
|
||||||
match &self {
|
|
||||||
CommentStyle::Line => *b"//",
|
|
||||||
CommentStyle::Block => *b"/*",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NonCodeNode {
|
|
||||||
compute_digest!(|slf, hasher| {
|
|
||||||
match &slf.value {
|
|
||||||
NonCodeValue::InlineComment { value, style } => {
|
|
||||||
hasher.update(value);
|
|
||||||
hasher.update(style.digestable_id());
|
|
||||||
}
|
|
||||||
NonCodeValue::BlockComment { value, style } => {
|
|
||||||
hasher.update(value);
|
|
||||||
hasher.update(style.digestable_id());
|
|
||||||
}
|
|
||||||
NonCodeValue::NewLineBlockComment { value, style } => {
|
|
||||||
hasher.update(value);
|
|
||||||
hasher.update(style.digestable_id());
|
|
||||||
}
|
|
||||||
NonCodeValue::NewLine => {
|
|
||||||
hasher.update(b"\r\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NonCodeMeta {
|
|
||||||
compute_digest!(|slf, hasher| {
|
|
||||||
let mut keys = slf.non_code_nodes.keys().copied().collect::<Vec<_>>();
|
|
||||||
keys.sort();
|
|
||||||
|
|
||||||
for key in keys.into_iter() {
|
|
||||||
hasher.update(key.to_ne_bytes());
|
|
||||||
let nodes = slf.non_code_nodes.get_mut(&key).unwrap();
|
|
||||||
hasher.update(nodes.len().to_ne_bytes());
|
|
||||||
for node in nodes.iter_mut() {
|
|
||||||
hasher.update(node.compute_digest());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExpressionStatement {
|
impl ExpressionStatement {
|
||||||
compute_digest!(|slf, hasher| {
|
compute_digest!(|slf, hasher| {
|
||||||
hasher.update(slf.expression.compute_digest());
|
hasher.update(slf.expression.compute_digest());
|
||||||
@ -410,7 +367,6 @@ impl PipeExpression {
|
|||||||
for value in slf.body.iter_mut() {
|
for value in slf.body.iter_mut() {
|
||||||
hasher.update(value.compute_digest());
|
hasher.update(value.compute_digest());
|
||||||
}
|
}
|
||||||
hasher.update(slf.non_code_meta.compute_digest());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::Node;
|
use super::{super::digest::Digest, Node};
|
||||||
use crate::{execution::KclValue, parsing::ast::types::ConstraintLevel};
|
use crate::{execution::KclValue, parsing::ast::types::ConstraintLevel};
|
||||||
|
|
||||||
const KCL_NONE_ID: &str = "KCL_NONE_ID";
|
const KCL_NONE_ID: &str = "KCL_NONE_ID";
|
||||||
@ -19,11 +19,18 @@ pub struct KclNone {
|
|||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[schemars(skip)]
|
#[schemars(skip)]
|
||||||
__private: Private,
|
__private: Private,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
#[ts(optional)]
|
||||||
|
pub digest: Option<Digest>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KclNone {
|
impl KclNone {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { __private: Private {} }
|
Self {
|
||||||
|
__private: Private {},
|
||||||
|
digest: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2256,12 +2256,16 @@ fn arguments(i: &mut TokenSlice) -> PResult<Vec<Expr>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
|
fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
|
||||||
separated_pair(identifier, (one_of(TokenType::Colon), opt(whitespace)), expression)
|
separated_pair(
|
||||||
.map(|(label, arg)| LabeledArg {
|
terminated(identifier, opt(whitespace)),
|
||||||
label: label.inner,
|
terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
|
||||||
arg,
|
expression,
|
||||||
})
|
)
|
||||||
.parse_next(i)
|
.map(|(label, arg)| LabeledArg {
|
||||||
|
label: label.inner,
|
||||||
|
arg,
|
||||||
|
})
|
||||||
|
.parse_next(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Arguments are passed into a function,
|
/// Arguments are passed into a function,
|
||||||
@ -2535,6 +2539,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
|
|||||||
let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?;
|
let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?;
|
||||||
let args = labeled_arguments(i)?;
|
let args = labeled_arguments(i)?;
|
||||||
ignore_whitespace(i);
|
ignore_whitespace(i);
|
||||||
|
opt(comma_sep).parse_next(i)?;
|
||||||
let end = close_paren.parse_next(i)?.end;
|
let end = close_paren.parse_next(i)?.end;
|
||||||
|
|
||||||
Ok(Node {
|
Ok(Node {
|
||||||
@ -4057,7 +4062,7 @@ let myBox = box([0,0], -3, -16, -10)
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn kw_fn() {
|
fn kw_fn() {
|
||||||
for input in ["val = foo(x, y: z)", "val = foo(y: z)"] {
|
for input in ["val = foo(x, y = z)", "val = foo(y = z)"] {
|
||||||
let module_id = ModuleId::default();
|
let module_id = ModuleId::default();
|
||||||
let tokens = crate::parsing::token::lex(input, module_id).unwrap();
|
let tokens = crate::parsing::token::lex(input, module_id).unwrap();
|
||||||
super::program.parse(tokens.as_slice()).unwrap();
|
super::program.parse(tokens.as_slice()).unwrap();
|
||||||
@ -4497,8 +4502,8 @@ my14 = 4 ^ 2 - 3 ^ 2 * 2
|
|||||||
r#"x = 3
|
r#"x = 3
|
||||||
obj = { x, y: 4}"#
|
obj = { x, y: 4}"#
|
||||||
);
|
);
|
||||||
snapshot_test!(kw_function_unnamed_first, r#"val = foo(x, y: z)"#);
|
snapshot_test!(kw_function_unnamed_first, r#"val = foo(x, y = z)"#);
|
||||||
snapshot_test!(kw_function_all_named, r#"val = foo(x: a, y: b)"#);
|
snapshot_test!(kw_function_all_named, r#"val = foo(x = a, y = b)"#);
|
||||||
snapshot_test!(kw_function_decl_all_labeled, r#"fn foo(x, y) { return 1 }"#);
|
snapshot_test!(kw_function_decl_all_labeled, r#"fn foo(x, y) { return 1 }"#);
|
||||||
snapshot_test!(kw_function_decl_first_unlabeled, r#"fn foo(@x, y) { return 1 }"#);
|
snapshot_test!(kw_function_decl_first_unlabeled, r#"fn foo(@x, y) { return 1 }"#);
|
||||||
snapshot_test!(kw_function_decl_with_default_no_type, r#"fn foo(x? = 2) { return 1 }"#);
|
snapshot_test!(kw_function_decl_with_default_no_type, r#"fn foo(x? = 2) { return 1 }"#);
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
---
|
---
|
||||||
source: kcl/src/parsing/parser.rs
|
source: kcl/src/parsing/parser.rs
|
||||||
expression: actual
|
expression: actual
|
||||||
|
snapshot_kind: text
|
||||||
---
|
---
|
||||||
{
|
{
|
||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
"declaration": {
|
"declaration": {
|
||||||
"end": 21,
|
"end": 23,
|
||||||
"id": {
|
"id": {
|
||||||
"end": 3,
|
"end": 3,
|
||||||
"name": "val",
|
"name": "val",
|
||||||
@ -22,9 +23,9 @@ expression: actual
|
|||||||
"name": "x"
|
"name": "x"
|
||||||
},
|
},
|
||||||
"arg": {
|
"arg": {
|
||||||
"end": 14,
|
"end": 15,
|
||||||
"name": "a",
|
"name": "a",
|
||||||
"start": 13,
|
"start": 14,
|
||||||
"type": "Identifier",
|
"type": "Identifier",
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
}
|
}
|
||||||
@ -36,9 +37,9 @@ expression: actual
|
|||||||
"name": "y"
|
"name": "y"
|
||||||
},
|
},
|
||||||
"arg": {
|
"arg": {
|
||||||
"end": 20,
|
"end": 22,
|
||||||
"name": "b",
|
"name": "b",
|
||||||
"start": 19,
|
"start": 21,
|
||||||
"type": "Identifier",
|
"type": "Identifier",
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
}
|
}
|
||||||
@ -50,7 +51,7 @@ expression: actual
|
|||||||
"start": 6,
|
"start": 6,
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
},
|
},
|
||||||
"end": 21,
|
"end": 23,
|
||||||
"start": 6,
|
"start": 6,
|
||||||
"type": "CallExpressionKw",
|
"type": "CallExpressionKw",
|
||||||
"type": "CallExpressionKw",
|
"type": "CallExpressionKw",
|
||||||
@ -59,13 +60,13 @@ expression: actual
|
|||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "VariableDeclarator"
|
"type": "VariableDeclarator"
|
||||||
},
|
},
|
||||||
"end": 21,
|
"end": 23,
|
||||||
"kind": "const",
|
"kind": "const",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "VariableDeclaration",
|
"type": "VariableDeclaration",
|
||||||
"type": "VariableDeclaration"
|
"type": "VariableDeclaration"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"end": 21,
|
"end": 23,
|
||||||
"start": 0
|
"start": 0
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
---
|
---
|
||||||
source: kcl/src/parsing/parser.rs
|
source: kcl/src/parsing/parser.rs
|
||||||
expression: actual
|
expression: actual
|
||||||
|
snapshot_kind: text
|
||||||
---
|
---
|
||||||
{
|
{
|
||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
"declaration": {
|
"declaration": {
|
||||||
"end": 18,
|
"end": 19,
|
||||||
"id": {
|
"id": {
|
||||||
"end": 3,
|
"end": 3,
|
||||||
"name": "val",
|
"name": "val",
|
||||||
@ -22,9 +23,9 @@ expression: actual
|
|||||||
"name": "y"
|
"name": "y"
|
||||||
},
|
},
|
||||||
"arg": {
|
"arg": {
|
||||||
"end": 17,
|
"end": 18,
|
||||||
"name": "z",
|
"name": "z",
|
||||||
"start": 16,
|
"start": 17,
|
||||||
"type": "Identifier",
|
"type": "Identifier",
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
}
|
}
|
||||||
@ -36,7 +37,7 @@ expression: actual
|
|||||||
"start": 6,
|
"start": 6,
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
},
|
},
|
||||||
"end": 18,
|
"end": 19,
|
||||||
"start": 6,
|
"start": 6,
|
||||||
"type": "CallExpressionKw",
|
"type": "CallExpressionKw",
|
||||||
"type": "CallExpressionKw",
|
"type": "CallExpressionKw",
|
||||||
@ -51,13 +52,13 @@ expression: actual
|
|||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "VariableDeclarator"
|
"type": "VariableDeclarator"
|
||||||
},
|
},
|
||||||
"end": 18,
|
"end": 19,
|
||||||
"kind": "const",
|
"kind": "const",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "VariableDeclaration",
|
"type": "VariableDeclaration",
|
||||||
"type": "VariableDeclaration"
|
"type": "VariableDeclaration"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"end": 18,
|
"end": 19,
|
||||||
"start": 0
|
"start": 0
|
||||||
}
|
}
|
||||||
|
@ -380,9 +380,9 @@ impl From<UnitLength> for kittycad_modeling_cmds::units::UnitLength {
|
|||||||
#[display(style = "snake_case")]
|
#[display(style = "snake_case")]
|
||||||
pub enum MouseControlType {
|
pub enum MouseControlType {
|
||||||
#[default]
|
#[default]
|
||||||
#[display("kittycad")]
|
#[display("zoo")]
|
||||||
#[serde(rename = "kittycad", alias = "KittyCAD")]
|
#[serde(rename = "zoo", alias = "Zoo", alias = "KittyCAD")]
|
||||||
KittyCad,
|
Zoo,
|
||||||
#[display("onshape")]
|
#[display("onshape")]
|
||||||
#[serde(rename = "onshape", alias = "OnShape")]
|
#[serde(rename = "onshape", alias = "OnShape")]
|
||||||
OnShape,
|
OnShape,
|
||||||
@ -477,6 +477,10 @@ pub struct CommandBarSettings {
|
|||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
#[display(style = "snake_case")]
|
#[display(style = "snake_case")]
|
||||||
pub enum OnboardingStatus {
|
pub enum OnboardingStatus {
|
||||||
|
/// The unset state.
|
||||||
|
#[serde(rename = "")]
|
||||||
|
#[display("")]
|
||||||
|
Unset,
|
||||||
/// The user has completed onboarding.
|
/// The user has completed onboarding.
|
||||||
Completed,
|
Completed,
|
||||||
/// The user has not completed onboarding.
|
/// The user has not completed onboarding.
|
||||||
|
@ -1502,48 +1502,6 @@ mod kw_fn {
|
|||||||
super::execute(TEST_NAME, true).await
|
super::execute(TEST_NAME, true).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mod tag_can_be_proxied_through_parameter {
|
|
||||||
const TEST_NAME: &str = "tag_can_be_proxied_through_parameter";
|
|
||||||
|
|
||||||
/// 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, false).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod tag_proxied_through_function_does_not_define_var {
|
|
||||||
const TEST_NAME: &str = "tag_proxied_through_function_does_not_define_var";
|
|
||||||
|
|
||||||
/// 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, false).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mod kw_fn_too_few_args {
|
mod kw_fn_too_few_args {
|
||||||
const TEST_NAME: &str = "kw_fn_too_few_args";
|
const TEST_NAME: &str = "kw_fn_too_few_args";
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
///
|
///
|
||||||
/// This will work on any solid, including extruded solids, revolved solids, and shelled solids.
|
/// This will work on any solid, including extruded solids, revolved solids, and shelled solids.
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// /// Add color to an extruded solid.
|
/// // Add color to an extruded solid.
|
||||||
/// exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> lineTo([10, 0], %)
|
/// |> lineTo([10, 0], %)
|
||||||
@ -78,7 +78,7 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// /// Add color to a revolved solid.
|
/// // Add color to a revolved solid.
|
||||||
/// sketch001 = startSketchOn('XY')
|
/// sketch001 = startSketchOn('XY')
|
||||||
/// |> circle({ center = [15, 0], radius = 5 }, %)
|
/// |> circle({ center = [15, 0], radius = 5 }, %)
|
||||||
/// |> revolve({ angle = 360, axis = 'y' }, %)
|
/// |> revolve({ angle = 360, axis = 'y' }, %)
|
||||||
@ -90,7 +90,7 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// /// Add color to different solids.
|
/// // Add color to different solids.
|
||||||
/// fn cube(center) {
|
/// fn cube(center) {
|
||||||
/// return startSketchOn('XY')
|
/// return startSketchOn('XY')
|
||||||
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
|
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
|
||||||
@ -110,8 +110,8 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// /// You can set the appearance before or after you shell it will yield the same result.
|
/// // You can set the appearance before or after you shell it will yield the same result.
|
||||||
/// /// This example shows setting the appearance _after_ the shell.
|
/// // This example shows setting the appearance _after_ the shell.
|
||||||
/// firstSketch = startSketchOn('XY')
|
/// firstSketch = startSketchOn('XY')
|
||||||
/// |> startProfileAt([-12, 12], %)
|
/// |> startProfileAt([-12, 12], %)
|
||||||
/// |> line([24, 0], %)
|
/// |> line([24, 0], %)
|
||||||
@ -132,8 +132,8 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// /// You can set the appearance before or after you shell it will yield the same result.
|
/// // You can set the appearance before or after you shell it will yield the same result.
|
||||||
/// /// This example shows setting the appearance _before_ the shell.
|
/// // This example shows setting the appearance _before_ the shell.
|
||||||
/// firstSketch = startSketchOn('XY')
|
/// firstSketch = startSketchOn('XY')
|
||||||
/// |> startProfileAt([-12, 12], %)
|
/// |> startProfileAt([-12, 12], %)
|
||||||
/// |> line([24, 0], %)
|
/// |> line([24, 0], %)
|
||||||
@ -154,8 +154,8 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// /// Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.
|
/// // Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.
|
||||||
/// /// This example shows _before_ the pattern.
|
/// // This example shows _before_ the pattern.
|
||||||
/// exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([0, 2], %)
|
/// |> line([0, 2], %)
|
||||||
@ -177,8 +177,8 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// /// Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.
|
/// // Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.
|
||||||
/// /// This example shows _after_ the pattern.
|
/// // This example shows _after_ the pattern.
|
||||||
/// exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([0, 2], %)
|
/// |> line([0, 2], %)
|
||||||
@ -200,7 +200,7 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// /// Color the result of a 2D pattern that was extruded.
|
/// // Color the result of a 2D pattern that was extruded.
|
||||||
/// exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([.5, 25], %)
|
/// |> startProfileAt([.5, 25], %)
|
||||||
/// |> line([0, 5], %)
|
/// |> line([0, 5], %)
|
||||||
@ -221,6 +221,46 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// roughness = 90
|
/// roughness = 90
|
||||||
/// }, %)
|
/// }, %)
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// // Color the result of a sweep.
|
||||||
|
///
|
||||||
|
/// // Create a path for the sweep.
|
||||||
|
/// sweepPath = startSketchOn('XZ')
|
||||||
|
/// |> startProfileAt([0.05, 0.05], %)
|
||||||
|
/// |> line([0, 7], %)
|
||||||
|
/// |> tangentialArc({
|
||||||
|
/// offset: 90,
|
||||||
|
/// radius: 5
|
||||||
|
/// }, %)
|
||||||
|
/// |> line([-3, 0], %)
|
||||||
|
/// |> tangentialArc({
|
||||||
|
/// offset: -90,
|
||||||
|
/// radius: 5
|
||||||
|
/// }, %)
|
||||||
|
/// |> line([0, 7], %)
|
||||||
|
///
|
||||||
|
/// pipeHole = startSketchOn('XY')
|
||||||
|
/// |> circle({
|
||||||
|
/// center = [0, 0],
|
||||||
|
/// radius = 1.5,
|
||||||
|
/// }, %)
|
||||||
|
///
|
||||||
|
/// sweepSketch = startSketchOn('XY')
|
||||||
|
/// |> circle({
|
||||||
|
/// center = [0, 0],
|
||||||
|
/// radius = 2,
|
||||||
|
/// }, %)
|
||||||
|
/// |> hole(pipeHole, %)
|
||||||
|
/// |> sweep({
|
||||||
|
/// path: sweepPath,
|
||||||
|
/// }, %)
|
||||||
|
/// |> appearance({
|
||||||
|
/// color: "#ff0000",
|
||||||
|
/// metalness: 50,
|
||||||
|
/// roughness: 50
|
||||||
|
/// }, %)
|
||||||
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "appearance",
|
name = "appearance",
|
||||||
}]
|
}]
|
||||||
|
@ -133,12 +133,16 @@ impl Args {
|
|||||||
where
|
where
|
||||||
T: FromKclValue<'a>,
|
T: FromKclValue<'a>,
|
||||||
{
|
{
|
||||||
let Some(ref arg) = self.kw_args.unlabeled else {
|
let arg = self
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
.kw_args
|
||||||
|
.unlabeled
|
||||||
|
.as_ref()
|
||||||
|
.or(self.args.first())
|
||||||
|
.ok_or(KclError::Semantic(KclErrorDetails {
|
||||||
source_ranges: vec![self.source_range],
|
source_ranges: vec![self.source_range],
|
||||||
message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
|
message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
|
||||||
}));
|
}))?;
|
||||||
};
|
|
||||||
T::from_kcl_val(&arg.value).ok_or_else(|| {
|
T::from_kcl_val(&arg.value).ok_or_else(|| {
|
||||||
KclError::Semantic(KclErrorDetails {
|
KclError::Semantic(KclErrorDetails {
|
||||||
source_ranges: arg.source_ranges(),
|
source_ranges: arg.source_ranges(),
|
||||||
@ -411,13 +415,6 @@ impl Args {
|
|||||||
FromArgs::from_args(self, 0)
|
FromArgs::from_args(self, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_sketches_and_data<'a, T>(&'a self) -> Result<(Vec<Sketch>, Option<T>), KclError>
|
|
||||||
where
|
|
||||||
T: FromArgs<'a> + serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
|
|
||||||
{
|
|
||||||
FromArgs::from_args(self, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_data_and_optional_tag<'a, T>(&'a self) -> Result<(T, Option<FaceTag>), KclError>
|
pub(crate) fn get_data_and_optional_tag<'a, T>(&'a self) -> Result<(T, Option<FaceTag>), KclError>
|
||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
|
T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
|
||||||
@ -865,22 +862,6 @@ impl<'a> FromKclValue<'a> for crate::std::polar::PolarCoordsData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FromKclValue<'a> for crate::std::loft::LoftData {
|
|
||||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
|
||||||
let obj = arg.as_object()?;
|
|
||||||
let_field_of!(obj, v_degree?);
|
|
||||||
let_field_of!(obj, bez_approximate_rational?);
|
|
||||||
let_field_of!(obj, base_curve_index?);
|
|
||||||
let_field_of!(obj, tolerance?);
|
|
||||||
Some(Self {
|
|
||||||
v_degree,
|
|
||||||
bez_approximate_rational,
|
|
||||||
base_curve_index,
|
|
||||||
tolerance,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FromKclValue<'a> for crate::std::planes::StandardPlane {
|
impl<'a> FromKclValue<'a> for crate::std::planes::StandardPlane {
|
||||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||||
let s = arg.as_str()?;
|
let s = arg.as_str()?;
|
||||||
@ -1096,6 +1077,20 @@ impl<'a> FromKclValue<'a> for super::fillet::FilletData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> FromKclValue<'a> for super::sweep::SweepData {
|
||||||
|
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||||
|
let obj = arg.as_object()?;
|
||||||
|
let_field_of!(obj, path);
|
||||||
|
let_field_of!(obj, sectional?);
|
||||||
|
let_field_of!(obj, tolerance?);
|
||||||
|
Some(Self {
|
||||||
|
path,
|
||||||
|
sectional,
|
||||||
|
tolerance,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> FromKclValue<'a> for super::appearance::AppearanceData {
|
impl<'a> FromKclValue<'a> for super::appearance::AppearanceData {
|
||||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||||
let obj = arg.as_object()?;
|
let obj = arg.as_object()?;
|
||||||
|
@ -30,7 +30,7 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
/// Given a list like `[a, b, c]`, and a function like `f`, returns
|
/// Given a list like `[a, b, c]`, and a function like `f`, returns
|
||||||
/// `[f(a), f(b), f(c)]`
|
/// `[f(a), f(b), f(c)]`
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const r = 10 // radius
|
/// r = 10 // radius
|
||||||
/// fn drawCircle(id) {
|
/// fn drawCircle(id) {
|
||||||
/// return startSketchOn("XY")
|
/// return startSketchOn("XY")
|
||||||
/// |> circle({ center: [id * 2 * r, 0], radius: r}, %)
|
/// |> circle({ center: [id * 2 * r, 0], radius: r}, %)
|
||||||
@ -39,15 +39,15 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
/// // Call `drawCircle`, passing in each element of the array.
|
/// // Call `drawCircle`, passing in each element of the array.
|
||||||
/// // The outputs from each `drawCircle` form a new array,
|
/// // The outputs from each `drawCircle` form a new array,
|
||||||
/// // which is the return value from `map`.
|
/// // which is the return value from `map`.
|
||||||
/// const circles = map(
|
/// circles = map(
|
||||||
/// [1..3],
|
/// [1..3],
|
||||||
/// drawCircle
|
/// drawCircle
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const r = 10 // radius
|
/// r = 10 // radius
|
||||||
/// // Call `map`, using an anonymous function instead of a named one.
|
/// // Call `map`, using an anonymous function instead of a named one.
|
||||||
/// const circles = map(
|
/// circles = map(
|
||||||
/// [1..3],
|
/// [1..3],
|
||||||
/// fn(id) {
|
/// fn(id) {
|
||||||
/// return startSketchOn("XY")
|
/// return startSketchOn("XY")
|
||||||
@ -106,17 +106,17 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// using the previous value and the element.
|
/// using the previous value and the element.
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // This function adds two numbers.
|
/// // This function adds two numbers.
|
||||||
/// fn add = (a, b) => { return a + b }
|
/// fn add(a, b) { return a + b }
|
||||||
///
|
///
|
||||||
/// // This function adds an array of numbers.
|
/// // This function adds an array of numbers.
|
||||||
/// // It uses the `reduce` function, to call the `add` function on every
|
/// // It uses the `reduce` function, to call the `add` function on every
|
||||||
/// // element of the `arr` parameter. The starting value is 0.
|
/// // element of the `arr` parameter. The starting value is 0.
|
||||||
/// fn sum = (arr) => { return reduce(arr, 0, add) }
|
/// fn sum(arr) { return reduce(arr, 0, add) }
|
||||||
///
|
///
|
||||||
/// /*
|
/// /*
|
||||||
/// The above is basically like this pseudo-code:
|
/// The above is basically like this pseudo-code:
|
||||||
/// fn sum(arr):
|
/// fn sum(arr):
|
||||||
/// let sumSoFar = 0
|
/// sumSoFar = 0
|
||||||
/// for i in arr:
|
/// for i in arr:
|
||||||
/// sumSoFar = add(sumSoFar, i)
|
/// sumSoFar = add(sumSoFar, i)
|
||||||
/// return sumSoFar
|
/// return sumSoFar
|
||||||
@ -139,7 +139,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// ```
|
/// ```
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Declare a function that sketches a decagon.
|
/// // Declare a function that sketches a decagon.
|
||||||
/// fn decagon = (radius) => {
|
/// fn decagon(radius) {
|
||||||
/// // Each side of the decagon is turned this many degrees from the previous angle.
|
/// // Each side of the decagon is turned this many degrees from the previous angle.
|
||||||
/// stepAngle = (1/10) * tau()
|
/// stepAngle = (1/10) * tau()
|
||||||
///
|
///
|
||||||
@ -151,8 +151,8 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// // which takes a partially-sketched decagon and adds one more edge to it.
|
/// // which takes a partially-sketched decagon and adds one more edge to it.
|
||||||
/// fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
|
/// fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
|
||||||
/// // Draw one edge of the decagon.
|
/// // Draw one edge of the decagon.
|
||||||
/// let x = cos(stepAngle * i) * radius
|
/// x = cos(stepAngle * i) * radius
|
||||||
/// let y = sin(stepAngle * i) * radius
|
/// y = sin(stepAngle * i) * radius
|
||||||
/// return lineTo([x, y], partialDecagon)
|
/// return lineTo([x, y], partialDecagon)
|
||||||
/// })
|
/// })
|
||||||
///
|
///
|
||||||
@ -163,14 +163,14 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// /*
|
/// /*
|
||||||
/// The `decagon` above is basically like this pseudo-code:
|
/// The `decagon` above is basically like this pseudo-code:
|
||||||
/// fn decagon(radius):
|
/// fn decagon(radius):
|
||||||
/// let stepAngle = (1/10) * tau()
|
/// stepAngle = (1/10) * tau()
|
||||||
/// let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
|
/// startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
|
||||||
///
|
///
|
||||||
/// // Here's the reduce part.
|
/// // Here's the reduce part.
|
||||||
/// let partialDecagon = startOfDecagonSketch
|
/// partialDecagon = startOfDecagonSketch
|
||||||
/// for i in [1..10]:
|
/// for i in [1..10]:
|
||||||
/// let x = cos(stepAngle * i) * radius
|
/// x = cos(stepAngle * i) * radius
|
||||||
/// let y = sin(stepAngle * i) * radius
|
/// y = sin(stepAngle * i) * radius
|
||||||
/// partialDecagon = lineTo([x, y], partialDecagon)
|
/// partialDecagon = lineTo([x, y], partialDecagon)
|
||||||
/// fullDecagon = partialDecagon // it's now full
|
/// fullDecagon = partialDecagon // it's now full
|
||||||
/// return fullDecagon
|
/// return fullDecagon
|
||||||
@ -224,8 +224,8 @@ async fn call_reduce_closure<'a>(
|
|||||||
/// Returns a new array with the element appended.
|
/// Returns a new array with the element appended.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// let arr = [1, 2, 3]
|
/// arr = [1, 2, 3]
|
||||||
/// let new_arr = push(arr, 4)
|
/// new_arr = push(arr, 4)
|
||||||
/// assertEqual(new_arr[3], 4, 0.00001, "4 was added to the end of the array")
|
/// assertEqual(new_arr[3], 4, 0.00001, "4 was added to the end of the array")
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
|
@ -31,7 +31,7 @@ pub async fn assert(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// is false.
|
/// is false.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const myVar = true
|
/// myVar = true
|
||||||
/// assert(myVar, "should always be true")
|
/// assert(myVar, "should always be true")
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
@ -70,8 +70,8 @@ pub async fn assert_gt(_exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
|||||||
/// otherwise raise an error.
|
/// otherwise raise an error.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// let n = 1.0285
|
/// n = 1.0285
|
||||||
/// let o = 1.0286
|
/// o = 1.0286
|
||||||
/// assertEqual(n, o, 0.01, "n is within the given tolerance for o")
|
/// assertEqual(n, o, 0.01, "n is within the given tolerance for o")
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
|
@ -43,19 +43,19 @@ pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Chamfer a mounting plate.
|
/// // Chamfer a mounting plate.
|
||||||
/// const width = 20
|
/// width = 20
|
||||||
/// const length = 10
|
/// length = 10
|
||||||
/// const thickness = 1
|
/// thickness = 1
|
||||||
/// const chamferLength = 2
|
/// chamferLength = 2
|
||||||
///
|
///
|
||||||
/// const mountingPlateSketch = startSketchOn("XY")
|
/// mountingPlateSketch = startSketchOn("XY")
|
||||||
/// |> startProfileAt([-width/2, -length/2], %)
|
/// |> startProfileAt([-width/2, -length/2], %)
|
||||||
/// |> lineTo([width/2, -length/2], %, $edge1)
|
/// |> lineTo([width/2, -length/2], %, $edge1)
|
||||||
/// |> lineTo([width/2, length/2], %, $edge2)
|
/// |> lineTo([width/2, length/2], %, $edge2)
|
||||||
/// |> lineTo([-width/2, length/2], %, $edge3)
|
/// |> lineTo([-width/2, length/2], %, $edge3)
|
||||||
/// |> close(%, $edge4)
|
/// |> close(%, $edge4)
|
||||||
///
|
///
|
||||||
/// const mountingPlate = extrude(thickness, mountingPlateSketch)
|
/// mountingPlate = extrude(thickness, mountingPlateSketch)
|
||||||
/// |> chamfer({
|
/// |> chamfer({
|
||||||
/// length = chamferLength,
|
/// length = chamferLength,
|
||||||
/// tags = [
|
/// tags = [
|
||||||
@ -69,8 +69,8 @@ pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Sketch on the face of a chamfer.
|
/// // Sketch on the face of a chamfer.
|
||||||
/// fn cube = (pos, scale) => {
|
/// fn cube(pos, scale) {
|
||||||
/// const sg = startSketchOn('XY')
|
/// sg = startSketchOn('XY')
|
||||||
/// |> startProfileAt(pos, %)
|
/// |> startProfileAt(pos, %)
|
||||||
/// |> line([0, scale], %)
|
/// |> line([0, scale], %)
|
||||||
/// |> line([scale, 0], %)
|
/// |> line([scale, 0], %)
|
||||||
@ -79,7 +79,7 @@ pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// return sg
|
/// return sg
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// const part001 = cube([0,0], 20)
|
/// part001 = cube([0,0], 20)
|
||||||
/// |> close(%, $line1)
|
/// |> close(%, $line1)
|
||||||
/// |> extrude(20, %)
|
/// |> extrude(20, %)
|
||||||
/// |> chamfer({
|
/// |> chamfer({
|
||||||
@ -87,7 +87,7 @@ pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// tags = [getOppositeEdge(line1)]
|
/// tags = [getOppositeEdge(line1)]
|
||||||
/// }, %, $chamfer1) // We tag the chamfer to reference it later.
|
/// }, %, $chamfer1) // We tag the chamfer to reference it later.
|
||||||
///
|
///
|
||||||
/// const sketch001 = startSketchOn(part001, chamfer1)
|
/// sketch001 = startSketchOn(part001, chamfer1)
|
||||||
/// |> startProfileAt([10, 10], %)
|
/// |> startProfileAt([10, 10], %)
|
||||||
/// |> line([2, 0], %)
|
/// |> line([2, 0], %)
|
||||||
/// |> line([0, 2], %)
|
/// |> line([0, 2], %)
|
||||||
|
@ -21,7 +21,7 @@ pub async fn int(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// DEPRECATED use floor(), ceil(), or round().
|
/// DEPRECATED use floor(), ceil(), or round().
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// let n = int(ceil(5/2))
|
/// n = int(ceil(5/2))
|
||||||
/// assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
/// assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
||||||
/// // Draw n cylinders.
|
/// // Draw n cylinders.
|
||||||
/// startSketchOn('XZ')
|
/// startSketchOn('XZ')
|
||||||
|
@ -33,7 +33,7 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// cut into an existing solid.
|
/// cut into an existing solid.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const example = startSketchOn('XZ')
|
/// example = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([10, 0], %)
|
/// |> line([10, 0], %)
|
||||||
/// |> arc({
|
/// |> arc({
|
||||||
@ -54,7 +54,7 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([-10, 0], %)
|
/// |> startProfileAt([-10, 0], %)
|
||||||
/// |> arc({
|
/// |> arc({
|
||||||
/// angleStart = 120,
|
/// angleStart = 120,
|
||||||
@ -72,7 +72,7 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// |> line([-5, -2], %)
|
/// |> line([-5, -2], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(10, exampleSketch)
|
/// example = extrude(10, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "extrude"
|
name = "extrude"
|
||||||
@ -113,6 +113,7 @@ async fn inner_extrude(
|
|||||||
ModelingCmd::from(mcmd::Extrude {
|
ModelingCmd::from(mcmd::Extrude {
|
||||||
target: sketch.id.into(),
|
target: sketch.id.into(),
|
||||||
distance: LengthUnit(length),
|
distance: LengthUnit(length),
|
||||||
|
faces: Default::default(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -68,19 +68,19 @@ pub async fn fillet(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// will smoothly blend the transition.
|
/// will smoothly blend the transition.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const width = 20
|
/// width = 20
|
||||||
/// const length = 10
|
/// length = 10
|
||||||
/// const thickness = 1
|
/// thickness = 1
|
||||||
/// const filletRadius = 2
|
/// filletRadius = 2
|
||||||
///
|
///
|
||||||
/// const mountingPlateSketch = startSketchOn("XY")
|
/// mountingPlateSketch = startSketchOn("XY")
|
||||||
/// |> startProfileAt([-width/2, -length/2], %)
|
/// |> startProfileAt([-width/2, -length/2], %)
|
||||||
/// |> lineTo([width/2, -length/2], %, $edge1)
|
/// |> lineTo([width/2, -length/2], %, $edge1)
|
||||||
/// |> lineTo([width/2, length/2], %, $edge2)
|
/// |> lineTo([width/2, length/2], %, $edge2)
|
||||||
/// |> lineTo([-width/2, length/2], %, $edge3)
|
/// |> lineTo([-width/2, length/2], %, $edge3)
|
||||||
/// |> close(%, $edge4)
|
/// |> close(%, $edge4)
|
||||||
///
|
///
|
||||||
/// const mountingPlate = extrude(thickness, mountingPlateSketch)
|
/// mountingPlate = extrude(thickness, mountingPlateSketch)
|
||||||
/// |> fillet({
|
/// |> fillet({
|
||||||
/// radius = filletRadius,
|
/// radius = filletRadius,
|
||||||
/// tags = [
|
/// tags = [
|
||||||
@ -93,19 +93,19 @@ pub async fn fillet(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const width = 20
|
/// width = 20
|
||||||
/// const length = 10
|
/// length = 10
|
||||||
/// const thickness = 1
|
/// thickness = 1
|
||||||
/// const filletRadius = 1
|
/// filletRadius = 1
|
||||||
///
|
///
|
||||||
/// const mountingPlateSketch = startSketchOn("XY")
|
/// mountingPlateSketch = startSketchOn("XY")
|
||||||
/// |> startProfileAt([-width/2, -length/2], %)
|
/// |> startProfileAt([-width/2, -length/2], %)
|
||||||
/// |> lineTo([width/2, -length/2], %, $edge1)
|
/// |> lineTo([width/2, -length/2], %, $edge1)
|
||||||
/// |> lineTo([width/2, length/2], %, $edge2)
|
/// |> lineTo([width/2, length/2], %, $edge2)
|
||||||
/// |> lineTo([-width/2, length/2], %, $edge3)
|
/// |> lineTo([-width/2, length/2], %, $edge3)
|
||||||
/// |> close(%, $edge4)
|
/// |> close(%, $edge4)
|
||||||
///
|
///
|
||||||
/// const mountingPlate = extrude(thickness, mountingPlateSketch)
|
/// mountingPlate = extrude(thickness, mountingPlateSketch)
|
||||||
/// |> fillet({
|
/// |> fillet({
|
||||||
/// radius = filletRadius,
|
/// radius = filletRadius,
|
||||||
/// tolerance = 0.000001,
|
/// tolerance = 0.000001,
|
||||||
@ -195,7 +195,7 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
|
|||||||
/// Get the opposite edge to the edge given.
|
/// Get the opposite edge to the edge given.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([10, 0], %)
|
/// |> line([10, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
@ -213,7 +213,7 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
|
|||||||
/// }, %, $referenceEdge)
|
/// }, %, $referenceEdge)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// |> fillet({
|
/// |> fillet({
|
||||||
/// radius = 3,
|
/// radius = 3,
|
||||||
/// tags = [getOppositeEdge(referenceEdge)],
|
/// tags = [getOppositeEdge(referenceEdge)],
|
||||||
@ -268,7 +268,7 @@ pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> R
|
|||||||
/// Get the next adjacent edge to the edge given.
|
/// Get the next adjacent edge to the edge given.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([10, 0], %)
|
/// |> line([10, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
@ -286,7 +286,7 @@ pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> R
|
|||||||
/// }, %, $referenceEdge)
|
/// }, %, $referenceEdge)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// |> fillet({
|
/// |> fillet({
|
||||||
/// radius = 3,
|
/// radius = 3,
|
||||||
/// tags = [getNextAdjacentEdge(referenceEdge)],
|
/// tags = [getNextAdjacentEdge(referenceEdge)],
|
||||||
@ -353,7 +353,7 @@ pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args)
|
|||||||
/// Get the previous adjacent edge to the edge given.
|
/// Get the previous adjacent edge to the edge given.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([10, 0], %)
|
/// |> line([10, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
@ -371,7 +371,7 @@ pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args)
|
|||||||
/// }, %, $referenceEdge)
|
/// }, %, $referenceEdge)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// |> fillet({
|
/// |> fillet({
|
||||||
/// radius = 3,
|
/// radius = 3,
|
||||||
/// tags = [getPreviousAdjacentEdge(referenceEdge)],
|
/// tags = [getPreviousAdjacentEdge(referenceEdge)],
|
||||||
|
@ -42,7 +42,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// Create a helix on a cylinder.
|
/// Create a helix on a cylinder.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const part001 = startSketchOn('XY')
|
/// part001 = startSketchOn('XY')
|
||||||
/// |> circle({ center: [5, 5], radius: 10 }, %)
|
/// |> circle({ center: [5, 5], radius: 10 }, %)
|
||||||
/// |> extrude(10, %)
|
/// |> extrude(10, %)
|
||||||
/// |> helix({
|
/// |> helix({
|
||||||
|
@ -148,23 +148,23 @@ pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// [KCL modules](/docs/kcl/modules).
|
/// [KCL modules](/docs/kcl/modules).
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const model = import("tests/inputs/cube.obj")
|
/// model = import("tests/inputs/cube.obj")
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const model = import("tests/inputs/cube.obj", {format: "obj", units: "m"})
|
/// model = import("tests/inputs/cube.obj", {format: "obj", units: "m"})
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const model = import("tests/inputs/cube.gltf")
|
/// model = import("tests/inputs/cube.gltf")
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const model = import("tests/inputs/cube.sldprt")
|
/// model = import("tests/inputs/cube.sldprt")
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const model = import("tests/inputs/cube.step")
|
/// model = import("tests/inputs/cube.step")
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
//! Standard library lofts.
|
//! Standard library lofts.
|
||||||
|
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use derive_docs::stdlib;
|
use derive_docs::stdlib;
|
||||||
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
|
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
|
||||||
use kittycad_modeling_cmds as kcmc;
|
use kittycad_modeling_cmds as kcmc;
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
@ -15,45 +15,31 @@ use crate::{
|
|||||||
|
|
||||||
const DEFAULT_V_DEGREE: u32 = 2;
|
const DEFAULT_V_DEGREE: u32 = 2;
|
||||||
|
|
||||||
/// Data for a loft.
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
||||||
#[ts(export)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct LoftData {
|
|
||||||
/// Degree of the interpolation. Must be greater than zero.
|
|
||||||
/// For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction.
|
|
||||||
/// This defaults to 2, if not specified.
|
|
||||||
pub v_degree: Option<std::num::NonZeroU32>,
|
|
||||||
/// Attempt to approximate rational curves (such as arcs) using a bezier.
|
|
||||||
/// This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios
|
|
||||||
/// Over time, this field won't be necessary.
|
|
||||||
#[serde(default)]
|
|
||||||
pub bez_approximate_rational: Option<bool>,
|
|
||||||
/// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
|
|
||||||
#[serde(default)]
|
|
||||||
pub base_curve_index: Option<u32>,
|
|
||||||
/// Tolerance for the loft operation.
|
|
||||||
#[serde(default)]
|
|
||||||
pub tolerance: Option<f64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for LoftData {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
// This unwrap is safe because the default value is always greater than zero.
|
|
||||||
v_degree: Some(std::num::NonZeroU32::new(DEFAULT_V_DEGREE).unwrap()),
|
|
||||||
bez_approximate_rational: None,
|
|
||||||
base_curve_index: None,
|
|
||||||
tolerance: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a 3D surface or solid by interpolating between two or more sketches.
|
/// Create a 3D surface or solid by interpolating between two or more sketches.
|
||||||
pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let (sketches, data): (Vec<Sketch>, Option<LoftData>) = args.get_sketches_and_data()?;
|
let sketches = args.get_unlabeled_kw_arg("sketches")?;
|
||||||
|
let v_degree: NonZeroU32 = args
|
||||||
|
.get_kw_arg_opt("vDegree")
|
||||||
|
.unwrap_or(NonZeroU32::new(DEFAULT_V_DEGREE).unwrap());
|
||||||
|
// Attempt to approximate rational curves (such as arcs) using a bezier.
|
||||||
|
// This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios
|
||||||
|
// Over time, this field won't be necessary.
|
||||||
|
let bez_approximate_rational = args.get_kw_arg_opt("bezApproximateRational").unwrap_or(false);
|
||||||
|
// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
|
||||||
|
let base_curve_index: Option<u32> = args.get_kw_arg_opt("baseCurveIndex");
|
||||||
|
// Tolerance for the loft operation.
|
||||||
|
let tolerance: Option<f64> = args.get_kw_arg_opt("tolerance");
|
||||||
|
|
||||||
let solid = inner_loft(sketches, data, exec_state, args).await?;
|
let solid = inner_loft(
|
||||||
|
sketches,
|
||||||
|
v_degree,
|
||||||
|
bez_approximate_rational,
|
||||||
|
base_curve_index,
|
||||||
|
tolerance,
|
||||||
|
exec_state,
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(KclValue::Solid(solid))
|
Ok(KclValue::Solid(solid))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +49,7 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Loft a square and a triangle.
|
/// // Loft a square and a triangle.
|
||||||
/// const squareSketch = startSketchOn('XY')
|
/// squareSketch = startSketchOn('XY')
|
||||||
/// |> startProfileAt([-100, 200], %)
|
/// |> startProfileAt([-100, 200], %)
|
||||||
/// |> line([200, 0], %)
|
/// |> line([200, 0], %)
|
||||||
/// |> line([0, -200], %)
|
/// |> line([0, -200], %)
|
||||||
@ -71,7 +57,7 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const triangleSketch = startSketchOn(offsetPlane('XY', 75))
|
/// triangleSketch = startSketchOn(offsetPlane('XY', 75))
|
||||||
/// |> startProfileAt([0, 125], %)
|
/// |> startProfileAt([0, 125], %)
|
||||||
/// |> line([-15, -30], %)
|
/// |> line([-15, -30], %)
|
||||||
/// |> line([30, 0], %)
|
/// |> line([30, 0], %)
|
||||||
@ -83,7 +69,7 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Loft a square, a circle, and another circle.
|
/// // Loft a square, a circle, and another circle.
|
||||||
/// const squareSketch = startSketchOn('XY')
|
/// squareSketch = startSketchOn('XY')
|
||||||
/// |> startProfileAt([-100, 200], %)
|
/// |> startProfileAt([-100, 200], %)
|
||||||
/// |> line([200, 0], %)
|
/// |> line([200, 0], %)
|
||||||
/// |> line([0, -200], %)
|
/// |> line([0, -200], %)
|
||||||
@ -91,10 +77,10 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const circleSketch0 = startSketchOn(offsetPlane('XY', 75))
|
/// circleSketch0 = startSketchOn(offsetPlane('XY', 75))
|
||||||
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
||||||
///
|
///
|
||||||
/// const circleSketch1 = startSketchOn(offsetPlane('XY', 150))
|
/// circleSketch1 = startSketchOn(offsetPlane('XY', 150))
|
||||||
/// |> circle({ center = [0, 100], radius = 20 }, %)
|
/// |> circle({ center = [0, 100], radius = 20 }, %)
|
||||||
///
|
///
|
||||||
/// loft([squareSketch, circleSketch0, circleSketch1])
|
/// loft([squareSketch, circleSketch0, circleSketch1])
|
||||||
@ -102,7 +88,7 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Loft a square, a circle, and another circle with options.
|
/// // Loft a square, a circle, and another circle with options.
|
||||||
/// const squareSketch = startSketchOn('XY')
|
/// squareSketch = startSketchOn('XY')
|
||||||
/// |> startProfileAt([-100, 200], %)
|
/// |> startProfileAt([-100, 200], %)
|
||||||
/// |> line([200, 0], %)
|
/// |> line([200, 0], %)
|
||||||
/// |> line([0, -200], %)
|
/// |> line([0, -200], %)
|
||||||
@ -110,34 +96,37 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const circleSketch0 = startSketchOn(offsetPlane('XY', 75))
|
/// circleSketch0 = startSketchOn(offsetPlane('XY', 75))
|
||||||
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
||||||
///
|
///
|
||||||
/// const circleSketch1 = startSketchOn(offsetPlane('XY', 150))
|
/// circleSketch1 = startSketchOn(offsetPlane('XY', 150))
|
||||||
/// |> circle({ center = [0, 100], radius = 20 }, %)
|
/// |> circle({ center = [0, 100], radius = 20 }, %)
|
||||||
///
|
///
|
||||||
/// loft([squareSketch, circleSketch0, circleSketch1], {
|
/// loft([squareSketch, circleSketch0, circleSketch1],
|
||||||
/// // This can be set to override the automatically determined
|
|
||||||
/// // topological base curve, which is usually the first section encountered.
|
|
||||||
/// baseCurveIndex = 0,
|
/// baseCurveIndex = 0,
|
||||||
/// // Attempt to approximate rational curves (such as arcs) using a bezier.
|
|
||||||
/// // This will remove banding around interpolations between arcs and non-arcs.
|
|
||||||
/// // It may produce errors in other scenarios Over time, this field won't be necessary.
|
|
||||||
/// bezApproximateRational = false,
|
/// bezApproximateRational = false,
|
||||||
/// // Tolerance for the loft operation.
|
|
||||||
/// tolerance = 0.000001,
|
/// tolerance = 0.000001,
|
||||||
/// // Degree of the interpolation. Must be greater than zero.
|
|
||||||
/// // For example, use 2 for quadratic, or 3 for cubic interpolation in
|
|
||||||
/// // the V direction. This defaults to 2, if not specified.
|
|
||||||
/// vDegree = 2,
|
/// vDegree = 2,
|
||||||
/// })
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "loft",
|
name = "loft",
|
||||||
|
keywords = true,
|
||||||
|
unlabeled_first = true,
|
||||||
|
arg_docs = {
|
||||||
|
sketches = "Which sketches to loft. Must include at least 2 sketches.",
|
||||||
|
v_degree = "Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. This defaults to 2, if not specified.",
|
||||||
|
bez_approximate_rational = "Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary.",
|
||||||
|
base_curve_index = "This can be set to override the automatically determined topological base curve, which is usually the first section encountered.",
|
||||||
|
tolerance = "Tolerance for the loft operation.",
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
async fn inner_loft(
|
async fn inner_loft(
|
||||||
sketches: Vec<Sketch>,
|
sketches: Vec<Sketch>,
|
||||||
data: Option<LoftData>,
|
v_degree: NonZeroU32,
|
||||||
|
bez_approximate_rational: bool,
|
||||||
|
base_curve_index: Option<u32>,
|
||||||
|
tolerance: Option<f64>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<Solid>, KclError> {
|
) -> Result<Box<Solid>, KclError> {
|
||||||
@ -152,20 +141,15 @@ async fn inner_loft(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the loft data.
|
|
||||||
let data = data.unwrap_or_default();
|
|
||||||
|
|
||||||
let id = exec_state.id_generator.next_uuid();
|
let id = exec_state.id_generator.next_uuid();
|
||||||
args.batch_modeling_cmd(
|
args.batch_modeling_cmd(
|
||||||
id,
|
id,
|
||||||
ModelingCmd::from(mcmd::Loft {
|
ModelingCmd::from(mcmd::Loft {
|
||||||
section_ids: sketches.iter().map(|group| group.id).collect(),
|
section_ids: sketches.iter().map(|group| group.id).collect(),
|
||||||
base_curve_index: data.base_curve_index,
|
base_curve_index,
|
||||||
bez_approximate_rational: data.bez_approximate_rational.unwrap_or(false),
|
bez_approximate_rational,
|
||||||
tolerance: LengthUnit(data.tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
|
tolerance: LengthUnit(tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
|
||||||
v_degree: data
|
v_degree,
|
||||||
.v_degree
|
|
||||||
.unwrap_or_else(|| std::num::NonZeroU32::new(DEFAULT_V_DEGREE).unwrap()),
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -9,6 +9,8 @@ use crate::{
|
|||||||
std::Args,
|
std::Args,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::args::FromArgs;
|
||||||
|
|
||||||
/// Compute the remainder after dividing `num` by `div`.
|
/// Compute the remainder after dividing `num` by `div`.
|
||||||
/// If `num` is negative, the result will be too.
|
/// If `num` is negative, the result will be too.
|
||||||
pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
@ -23,15 +25,19 @@ pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// If `num` is negative, the result will be too.
|
/// If `num` is negative, the result will be too.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// assertEqual(rem(7, divisor: 4), 3, 0.01, "remainder is 3")
|
/// assertEqual(rem( 7, divisor = 4), 3, 0.01, "remainder is 3" )
|
||||||
/// assertEqual(rem(-7, divisor: 4), -3, 0.01, "remainder is 3")
|
/// assertEqual(rem(-7, divisor = 4), -3, 0.01, "remainder is -3")
|
||||||
/// assertEqual(rem(7, divisor: -4), 3, 0.01, "remainder is 3")
|
/// assertEqual(rem( 7, divisor = -4), 3, 0.01, "remainder is 3" )
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "rem",
|
name = "rem",
|
||||||
tags = ["math"],
|
tags = ["math"],
|
||||||
keywords = true,
|
keywords = true,
|
||||||
unlabeled_first = true,
|
unlabeled_first = true,
|
||||||
|
arg_docs = {
|
||||||
|
num = "The number which will be divided by `divisor`.",
|
||||||
|
divisor = "The number which will divide `num`.",
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
fn inner_rem(num: i64, divisor: i64) -> Result<i64, KclError> {
|
fn inner_rem(num: i64, divisor: i64) -> Result<i64, KclError> {
|
||||||
Ok(num % divisor)
|
Ok(num % divisor)
|
||||||
@ -48,7 +54,7 @@ pub async fn cos(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// Compute the cosine of a number (in radians).
|
/// Compute the cosine of a number (in radians).
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 30,
|
/// angle = 30,
|
||||||
@ -57,7 +63,7 @@ pub async fn cos(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "cos",
|
name = "cos",
|
||||||
@ -78,7 +84,7 @@ pub async fn sin(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// Compute the sine of a number (in radians).
|
/// Compute the sine of a number (in radians).
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 50,
|
/// angle = 50,
|
||||||
@ -87,7 +93,7 @@ pub async fn sin(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "sin",
|
name = "sin",
|
||||||
@ -108,7 +114,7 @@ pub async fn tan(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// Compute the tangent of a number (in radians).
|
/// Compute the tangent of a number (in radians).
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 50,
|
/// angle = 50,
|
||||||
@ -117,7 +123,7 @@ pub async fn tan(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "tan",
|
name = "tan",
|
||||||
@ -137,12 +143,12 @@ pub async fn pi(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
/// Return the value of `pi`. Archimedes’ constant (π).
|
/// Return the value of `pi`. Archimedes’ constant (π).
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const circumference = 70
|
/// circumference = 70
|
||||||
///
|
///
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> circle({ center = [0, 0], radius = circumference/ (2 * pi()) }, %)
|
/// |> circle({ center = [0, 0], radius = circumference/ (2 * pi()) }, %)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "pi",
|
name = "pi",
|
||||||
@ -163,7 +169,7 @@ pub async fn sqrt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// Compute the square root of a number.
|
/// Compute the square root of a number.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 50,
|
/// angle = 50,
|
||||||
@ -172,7 +178,7 @@ pub async fn sqrt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "sqrt",
|
name = "sqrt",
|
||||||
@ -193,9 +199,9 @@ pub async fn abs(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// Compute the absolute value of a number.
|
/// Compute the absolute value of a number.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const myAngle = -120
|
/// myAngle = -120
|
||||||
///
|
///
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([8, 0], %)
|
/// |> line([8, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
@ -209,7 +215,7 @@ pub async fn abs(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// }, %)
|
/// }, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const baseExtrusion = extrude(5, sketch001)
|
/// baseExtrusion = extrude(5, sketch001)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "abs",
|
name = "abs",
|
||||||
@ -230,14 +236,14 @@ pub async fn round(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// Round a number to the nearest integer.
|
/// Round a number to the nearest integer.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> lineTo([12, 10], %)
|
/// |> lineTo([12, 10], %)
|
||||||
/// |> line([round(7.02986), 0], %)
|
/// |> line([round(7.02986), 0], %)
|
||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const extrude001 = extrude(5, sketch001)
|
/// extrude001 = extrude(5, sketch001)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "round",
|
name = "round",
|
||||||
@ -258,14 +264,14 @@ pub async fn floor(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// Compute the largest integer less than or equal to a number.
|
/// Compute the largest integer less than or equal to a number.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> lineTo([12, 10], %)
|
/// |> lineTo([12, 10], %)
|
||||||
/// |> line([floor(7.02986), 0], %)
|
/// |> line([floor(7.02986), 0], %)
|
||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const extrude001 = extrude(5, sketch001)
|
/// extrude001 = extrude(5, sketch001)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "floor",
|
name = "floor",
|
||||||
@ -286,14 +292,14 @@ pub async fn ceil(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// Compute the smallest integer greater than or equal to a number.
|
/// Compute the smallest integer greater than or equal to a number.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> lineTo([12, 10], %)
|
/// |> lineTo([12, 10], %)
|
||||||
/// |> line([ceil(7.02986), 0], %)
|
/// |> line([ceil(7.02986), 0], %)
|
||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const extrude001 = extrude(5, sketch001)
|
/// extrude001 = extrude(5, sketch001)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "ceil",
|
name = "ceil",
|
||||||
@ -314,7 +320,7 @@ pub async fn min(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// Compute the minimum of the given arguments.
|
/// Compute the minimum of the given arguments.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 70,
|
/// angle = 70,
|
||||||
@ -323,7 +329,7 @@ pub async fn min(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// |> line([20, 0], %)
|
/// |> line([20, 0], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "min",
|
name = "min",
|
||||||
@ -351,7 +357,7 @@ pub async fn max(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// Compute the maximum of the given arguments.
|
/// Compute the maximum of the given arguments.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 70,
|
/// angle = 70,
|
||||||
@ -360,7 +366,7 @@ pub async fn max(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// |> line([20, 0], %)
|
/// |> line([20, 0], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "max",
|
name = "max",
|
||||||
@ -402,7 +408,7 @@ pub async fn pow(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// Compute the number to a power.
|
/// Compute the number to a power.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 50,
|
/// angle = 50,
|
||||||
@ -411,7 +417,7 @@ pub async fn pow(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "pow",
|
name = "pow",
|
||||||
@ -432,7 +438,7 @@ pub async fn acos(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// Compute the arccosine of a number (in radians).
|
/// Compute the arccosine of a number (in radians).
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = toDegrees(acos(0.5)),
|
/// angle = toDegrees(acos(0.5)),
|
||||||
@ -442,7 +448,7 @@ pub async fn acos(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// |> lineTo([12, 0], %)
|
/// |> lineTo([12, 0], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const extrude001 = extrude(5, sketch001)
|
/// extrude001 = extrude(5, sketch001)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "acos",
|
name = "acos",
|
||||||
@ -463,7 +469,7 @@ pub async fn asin(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// Compute the arcsine of a number (in radians).
|
/// Compute the arcsine of a number (in radians).
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = toDegrees(asin(0.5)),
|
/// angle = toDegrees(asin(0.5)),
|
||||||
@ -472,7 +478,7 @@ pub async fn asin(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const extrude001 = extrude(5, sketch001)
|
/// extrude001 = extrude(5, sketch001)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "asin",
|
name = "asin",
|
||||||
@ -493,7 +499,7 @@ pub async fn atan(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// Compute the arctangent of a number (in radians).
|
/// Compute the arctangent of a number (in radians).
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = toDegrees(atan(1.25)),
|
/// angle = toDegrees(atan(1.25)),
|
||||||
@ -502,7 +508,7 @@ pub async fn atan(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const extrude001 = extrude(5, sketch001)
|
/// extrude001 = extrude(5, sketch001)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "atan",
|
name = "atan",
|
||||||
@ -512,6 +518,36 @@ fn inner_atan(num: f64) -> Result<f64, KclError> {
|
|||||||
Ok(num.atan())
|
Ok(num.atan())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compute the four quadrant arctangent of Y and X (in radians).
|
||||||
|
pub async fn atan2(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
|
let (y, x) = FromArgs::from_args(&args, 0)?;
|
||||||
|
let result = inner_atan2(y, x)?;
|
||||||
|
|
||||||
|
Ok(args.make_user_val_from_f64(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the four quadrant arctangent of Y and X (in radians).
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// sketch001 = startSketchOn('XZ')
|
||||||
|
/// |> startProfileAt([0, 0], %)
|
||||||
|
/// |> angledLine({
|
||||||
|
/// angle = toDegrees(atan2(1.25, 2)),
|
||||||
|
/// length = 20,
|
||||||
|
/// }, %)
|
||||||
|
/// |> yLineTo(0, %)
|
||||||
|
/// |> close(%)
|
||||||
|
///
|
||||||
|
/// extrude001 = extrude(5, sketch001)
|
||||||
|
/// ```
|
||||||
|
#[stdlib {
|
||||||
|
name = "atan2",
|
||||||
|
tags = ["math"],
|
||||||
|
}]
|
||||||
|
fn inner_atan2(y: f64, x: f64) -> Result<f64, KclError> {
|
||||||
|
Ok(y.atan2(x))
|
||||||
|
}
|
||||||
|
|
||||||
/// Compute the logarithm of the number with respect to an arbitrary base.
|
/// Compute the logarithm of the number with respect to an arbitrary base.
|
||||||
///
|
///
|
||||||
/// The result might not be correctly rounded owing to implementation
|
/// The result might not be correctly rounded owing to implementation
|
||||||
@ -544,14 +580,14 @@ pub async fn log(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// and `log10()` can produce more accurate results for base 10.
|
/// and `log10()` can produce more accurate results for base 10.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([log(100, 5), 0], %)
|
/// |> line([log(100, 5), 0], %)
|
||||||
/// |> line([5, 8], %)
|
/// |> line([5, 8], %)
|
||||||
/// |> line([-10, 0], %)
|
/// |> line([-10, 0], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "log",
|
name = "log",
|
||||||
@ -572,14 +608,14 @@ pub async fn log2(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// Compute the base 2 logarithm of the number.
|
/// Compute the base 2 logarithm of the number.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([log2(100), 0], %)
|
/// |> line([log2(100), 0], %)
|
||||||
/// |> line([5, 8], %)
|
/// |> line([5, 8], %)
|
||||||
/// |> line([-10, 0], %)
|
/// |> line([-10, 0], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "log2",
|
name = "log2",
|
||||||
@ -600,14 +636,14 @@ pub async fn log10(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// Compute the base 10 logarithm of the number.
|
/// Compute the base 10 logarithm of the number.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([log10(100), 0], %)
|
/// |> line([log10(100), 0], %)
|
||||||
/// |> line([5, 8], %)
|
/// |> line([5, 8], %)
|
||||||
/// |> line([-10, 0], %)
|
/// |> line([-10, 0], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "log10",
|
name = "log10",
|
||||||
@ -628,14 +664,14 @@ pub async fn ln(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
/// Compute the natural logarithm of the number.
|
/// Compute the natural logarithm of the number.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([ln(100), 15], %)
|
/// |> line([ln(100), 15], %)
|
||||||
/// |> line([5, -6], %)
|
/// |> line([5, -6], %)
|
||||||
/// |> line([-10, -10], %)
|
/// |> line([-10, -10], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "ln",
|
name = "ln",
|
||||||
@ -655,7 +691,7 @@ pub async fn e(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclE
|
|||||||
/// Return the value of Euler’s number `e`.
|
/// Return the value of Euler’s number `e`.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 30,
|
/// angle = 30,
|
||||||
@ -664,7 +700,7 @@ pub async fn e(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclE
|
|||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(10, exampleSketch)
|
/// example = extrude(10, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "e",
|
name = "e",
|
||||||
@ -684,7 +720,7 @@ pub async fn tau(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// Return the value of `tau`. The full circle constant (τ). Equal to 2π.
|
/// Return the value of `tau`. The full circle constant (τ). Equal to 2π.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 50,
|
/// angle = 50,
|
||||||
@ -693,7 +729,7 @@ pub async fn tau(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "tau",
|
name = "tau",
|
||||||
@ -714,7 +750,7 @@ pub async fn to_radians(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// Converts a number from degrees to radians.
|
/// Converts a number from degrees to radians.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 50,
|
/// angle = 50,
|
||||||
@ -723,7 +759,7 @@ pub async fn to_radians(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "toRadians",
|
name = "toRadians",
|
||||||
@ -744,7 +780,7 @@ pub async fn to_degrees(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// Converts a number from radians to degrees.
|
/// Converts a number from radians to degrees.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn("XZ")
|
/// exampleSketch = startSketchOn("XZ")
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 50,
|
/// angle = 50,
|
||||||
@ -753,7 +789,7 @@ pub async fn to_degrees(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// |> yLineTo(0, %)
|
/// |> yLineTo(0, %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "toDegrees",
|
name = "toDegrees",
|
||||||
|
@ -40,7 +40,7 @@ pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Mirror an un-closed sketch across the Y axis.
|
/// // Mirror an un-closed sketch across the Y axis.
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 10], %)
|
/// |> startProfileAt([0, 10], %)
|
||||||
/// |> line([15, 0], %)
|
/// |> line([15, 0], %)
|
||||||
/// |> line([-7, -3], %)
|
/// |> line([-7, -3], %)
|
||||||
@ -52,38 +52,38 @@ pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
|||||||
/// |> line([-19, -0], %)
|
/// |> line([-19, -0], %)
|
||||||
/// |> mirror2d({axis = 'Y'}, %)
|
/// |> mirror2d({axis = 'Y'}, %)
|
||||||
///
|
///
|
||||||
/// const example = extrude(10, sketch001)
|
/// example = extrude(10, sketch001)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Mirror a un-closed sketch across the Y axis.
|
/// // Mirror a un-closed sketch across the Y axis.
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 8.5], %)
|
/// |> startProfileAt([0, 8.5], %)
|
||||||
/// |> line([20, -8.5], %)
|
/// |> line([20, -8.5], %)
|
||||||
/// |> line([-20, -8.5], %)
|
/// |> line([-20, -8.5], %)
|
||||||
/// |> mirror2d({axis = 'Y'}, %)
|
/// |> mirror2d({axis = 'Y'}, %)
|
||||||
///
|
///
|
||||||
/// const example = extrude(10, sketch001)
|
/// example = extrude(10, sketch001)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Mirror a un-closed sketch across an edge.
|
/// // Mirror a un-closed sketch across an edge.
|
||||||
/// const helper001 = startSketchOn('XZ')
|
/// helper001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([0, 10], %, $edge001)
|
/// |> line([0, 10], %, $edge001)
|
||||||
///
|
///
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 8.5], %)
|
/// |> startProfileAt([0, 8.5], %)
|
||||||
/// |> line([20, -8.5], %)
|
/// |> line([20, -8.5], %)
|
||||||
/// |> line([-20, -8.5], %)
|
/// |> line([-20, -8.5], %)
|
||||||
/// |> mirror2d({axis = edge001}, %)
|
/// |> mirror2d({axis = edge001}, %)
|
||||||
///
|
///
|
||||||
/// const example = extrude(10, sketch001)
|
/// example = extrude(10, sketch001)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Mirror an un-closed sketch across a custom axis.
|
/// // Mirror an un-closed sketch across a custom axis.
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 8.5], %)
|
/// |> startProfileAt([0, 8.5], %)
|
||||||
/// |> line([20, -8.5], %)
|
/// |> line([20, -8.5], %)
|
||||||
/// |> line([-20, -8.5], %)
|
/// |> line([-20, -8.5], %)
|
||||||
@ -96,7 +96,7 @@ pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
|||||||
/// }
|
/// }
|
||||||
/// }, %)
|
/// }, %)
|
||||||
///
|
///
|
||||||
/// const example = extrude(10, sketch001)
|
/// example = extrude(10, sketch001)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "mirror2d",
|
name = "mirror2d",
|
||||||
|
@ -21,6 +21,7 @@ pub mod segment;
|
|||||||
pub mod shapes;
|
pub mod shapes;
|
||||||
pub mod shell;
|
pub mod shell;
|
||||||
pub mod sketch;
|
pub mod sketch;
|
||||||
|
pub mod sweep;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod units;
|
pub mod units;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
@ -114,6 +115,7 @@ lazy_static! {
|
|||||||
Box::new(crate::std::shell::Shell),
|
Box::new(crate::std::shell::Shell),
|
||||||
Box::new(crate::std::shell::Hollow),
|
Box::new(crate::std::shell::Hollow),
|
||||||
Box::new(crate::std::revolve::Revolve),
|
Box::new(crate::std::revolve::Revolve),
|
||||||
|
Box::new(crate::std::sweep::Sweep),
|
||||||
Box::new(crate::std::loft::Loft),
|
Box::new(crate::std::loft::Loft),
|
||||||
Box::new(crate::std::planes::OffsetPlane),
|
Box::new(crate::std::planes::OffsetPlane),
|
||||||
Box::new(crate::std::import::Import),
|
Box::new(crate::std::import::Import),
|
||||||
@ -123,6 +125,7 @@ lazy_static! {
|
|||||||
Box::new(crate::std::math::Acos),
|
Box::new(crate::std::math::Acos),
|
||||||
Box::new(crate::std::math::Asin),
|
Box::new(crate::std::math::Asin),
|
||||||
Box::new(crate::std::math::Atan),
|
Box::new(crate::std::math::Atan),
|
||||||
|
Box::new(crate::std::math::Atan2),
|
||||||
Box::new(crate::std::math::Pi),
|
Box::new(crate::std::math::Pi),
|
||||||
Box::new(crate::std::math::E),
|
Box::new(crate::std::math::E),
|
||||||
Box::new(crate::std::math::Tau),
|
Box::new(crate::std::math::Tau),
|
||||||
|
@ -146,12 +146,12 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Each instance will be shifted along the X axis.
|
/// // Each instance will be shifted along the X axis.
|
||||||
/// fn transform = (id) => {
|
/// fn transform(id) {
|
||||||
/// return { translate = [4 * id, 0, 0] }
|
/// return { translate = [4 * id, 0, 0] }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // Sketch 4 cylinders.
|
/// // Sketch 4 cylinders.
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> circle({ center = [0, 0], radius = 2 }, %)
|
/// |> circle({ center = [0, 0], radius = 2 }, %)
|
||||||
/// |> extrude(5, %)
|
/// |> extrude(5, %)
|
||||||
/// |> patternTransform(4, transform, %)
|
/// |> patternTransform(4, transform, %)
|
||||||
@ -160,24 +160,24 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
|
|||||||
/// // Each instance will be shifted along the X axis,
|
/// // Each instance will be shifted along the X axis,
|
||||||
/// // with a gap between the original (at x = 0) and the first replica
|
/// // with a gap between the original (at x = 0) and the first replica
|
||||||
/// // (at x = 8). This is because `id` starts at 1.
|
/// // (at x = 8). This is because `id` starts at 1.
|
||||||
/// fn transform = (id) => {
|
/// fn transform(id) {
|
||||||
/// return { translate: [4 * (1+id), 0, 0] }
|
/// return { translate: [4 * (1+id), 0, 0] }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// const sketch001 = startSketchOn('XZ')
|
/// sketch001 = startSketchOn('XZ')
|
||||||
/// |> circle({ center = [0, 0], radius = 2 }, %)
|
/// |> circle({ center = [0, 0], radius = 2 }, %)
|
||||||
/// |> extrude(5, %)
|
/// |> extrude(5, %)
|
||||||
/// |> patternTransform(4, transform, %)
|
/// |> patternTransform(4, transform, %)
|
||||||
/// ```
|
/// ```
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// fn cube = (length, center) => {
|
/// fn cube(length, center) {
|
||||||
/// let l = length/2
|
/// l = length/2
|
||||||
/// let x = center[0]
|
/// x = center[0]
|
||||||
/// let y = center[1]
|
/// y = center[1]
|
||||||
/// let p0 = [-l + x, -l + y]
|
/// p0 = [-l + x, -l + y]
|
||||||
/// let p1 = [-l + x, l + y]
|
/// p1 = [-l + x, l + y]
|
||||||
/// let p2 = [ l + x, l + y]
|
/// p2 = [ l + x, l + y]
|
||||||
/// let p3 = [ l + x, -l + y]
|
/// p3 = [ l + x, -l + y]
|
||||||
///
|
///
|
||||||
/// return startSketchAt(p0)
|
/// return startSketchAt(p0)
|
||||||
/// |> lineTo(p1, %)
|
/// |> lineTo(p1, %)
|
||||||
@ -188,8 +188,8 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
|
|||||||
/// |> extrude(length, %)
|
/// |> extrude(length, %)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let width = 20
|
/// width = 20
|
||||||
/// fn transform = (i) => {
|
/// fn transform(i) {
|
||||||
/// return {
|
/// return {
|
||||||
/// // Move down each time.
|
/// // Move down each time.
|
||||||
/// translate = [0, 0, -i * width],
|
/// translate = [0, 0, -i * width],
|
||||||
@ -203,20 +203,20 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let myCubes =
|
/// myCubes =
|
||||||
/// cube(width, [100,0])
|
/// cube(width, [100,0])
|
||||||
/// |> patternTransform(25, transform, %)
|
/// |> patternTransform(25, transform, %)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// fn cube = (length, center) => {
|
/// fn cube(length, center) {
|
||||||
/// let l = length/2
|
/// l = length/2
|
||||||
/// let x = center[0]
|
/// x = center[0]
|
||||||
/// let y = center[1]
|
/// y = center[1]
|
||||||
/// let p0 = [-l + x, -l + y]
|
/// p0 = [-l + x, -l + y]
|
||||||
/// let p1 = [-l + x, l + y]
|
/// p1 = [-l + x, l + y]
|
||||||
/// let p2 = [ l + x, l + y]
|
/// p2 = [ l + x, l + y]
|
||||||
/// let p3 = [ l + x, -l + y]
|
/// p3 = [ l + x, -l + y]
|
||||||
///
|
///
|
||||||
/// return startSketchAt(p0)
|
/// return startSketchAt(p0)
|
||||||
/// |> lineTo(p1, %)
|
/// |> lineTo(p1, %)
|
||||||
@ -227,8 +227,8 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
|
|||||||
/// |> extrude(length, %)
|
/// |> extrude(length, %)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let width = 20
|
/// width = 20
|
||||||
/// fn transform = (i) => {
|
/// fn transform(i) {
|
||||||
/// return {
|
/// return {
|
||||||
/// translate = [0, 0, -i * width],
|
/// translate = [0, 0, -i * width],
|
||||||
/// rotation = {
|
/// rotation = {
|
||||||
@ -238,36 +238,36 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
|
|||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// let myCubes =
|
/// myCubes =
|
||||||
/// cube(width, [100,100])
|
/// cube(width, [100,100])
|
||||||
/// |> patternTransform(4, transform, %)
|
/// |> patternTransform(4, transform, %)
|
||||||
/// ```
|
/// ```
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Parameters
|
/// // Parameters
|
||||||
/// const r = 50 // base radius
|
/// r = 50 // base radius
|
||||||
/// const h = 10 // layer height
|
/// h = 10 // layer height
|
||||||
/// const t = 0.005 // taper factor [0-1)
|
/// t = 0.005 // taper factor [0-1)
|
||||||
/// // Defines how to modify each layer of the vase.
|
/// // Defines how to modify each layer of the vase.
|
||||||
/// // Each replica is shifted up the Z axis, and has a smoothly-varying radius
|
/// // Each replica is shifted up the Z axis, and has a smoothly-varying radius
|
||||||
/// fn transform = (replicaId) => {
|
/// fn transform(replicaId) {
|
||||||
/// let scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
|
/// scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
|
||||||
/// return {
|
/// return {
|
||||||
/// translate = [0, 0, replicaId * 10],
|
/// translate = [0, 0, replicaId * 10],
|
||||||
/// scale = [scale, scale, 0],
|
/// scale = [scale, scale, 0],
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// // Each layer is just a pretty thin cylinder.
|
/// // Each layer is just a pretty thin cylinder.
|
||||||
/// fn layer = () => {
|
/// fn layer() {
|
||||||
/// return startSketchOn("XY") // or some other plane idk
|
/// return startSketchOn("XY") // or some other plane idk
|
||||||
/// |> circle({ center = [0, 0], radius = 1 }, %, $tag1)
|
/// |> circle({ center = [0, 0], radius = 1 }, %, $tag1)
|
||||||
/// |> extrude(h, %)
|
/// |> extrude(h, %)
|
||||||
/// }
|
/// }
|
||||||
/// // The vase is 100 layers tall.
|
/// // The vase is 100 layers tall.
|
||||||
/// // The 100 layers are replica of each other, with a slight transformation applied to each.
|
/// // The 100 layers are replica of each other, with a slight transformation applied to each.
|
||||||
/// let vase = layer() |> patternTransform(100, transform, %)
|
/// vase = layer() |> patternTransform(100, transform, %)
|
||||||
/// ```
|
/// ```
|
||||||
/// ```
|
/// ```
|
||||||
/// fn transform = (i) => {
|
/// fn transform(i) {
|
||||||
/// // Transform functions can return multiple transforms. They'll be applied in order.
|
/// // Transform functions can return multiple transforms. They'll be applied in order.
|
||||||
/// return [
|
/// return [
|
||||||
/// { translate: [30 * i, 0, 0] },
|
/// { translate: [30 * i, 0, 0] },
|
||||||
@ -312,7 +312,7 @@ async fn inner_pattern_transform<'a>(
|
|||||||
/// Just like patternTransform, but works on 2D sketches not 3D solids.
|
/// Just like patternTransform, but works on 2D sketches not 3D solids.
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Each instance will be shifted along the X axis.
|
/// // Each instance will be shifted along the X axis.
|
||||||
/// fn transform = (id) => {
|
/// fn transform(id) {
|
||||||
/// return { translate: [4 * id, 0] }
|
/// return { translate: [4 * id, 0] }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -689,7 +689,7 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
|
|||||||
/// of distance between each repetition, some specified number of times.
|
/// of distance between each repetition, some specified number of times.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
||||||
/// |> patternLinear2d({
|
/// |> patternLinear2d({
|
||||||
/// axis = [1, 0],
|
/// axis = [1, 0],
|
||||||
@ -697,7 +697,7 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
|
|||||||
/// distance = 4
|
/// distance = 4
|
||||||
/// }, %)
|
/// }, %)
|
||||||
///
|
///
|
||||||
/// const example = extrude(1, exampleSketch)
|
/// example = extrude(1, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "patternLinear2d",
|
name = "patternLinear2d",
|
||||||
@ -746,14 +746,14 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
|
|||||||
/// of distance between each repetition, some specified number of times.
|
/// of distance between each repetition, some specified number of times.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([0, 2], %)
|
/// |> line([0, 2], %)
|
||||||
/// |> line([3, 1], %)
|
/// |> line([3, 1], %)
|
||||||
/// |> line([0, -4], %)
|
/// |> line([0, -4], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(1, exampleSketch)
|
/// example = extrude(1, exampleSketch)
|
||||||
/// |> patternLinear3d({
|
/// |> patternLinear3d({
|
||||||
/// axis = [1, 0, 1],
|
/// axis = [1, 0, 1],
|
||||||
/// instances = 7,
|
/// instances = 7,
|
||||||
@ -900,7 +900,7 @@ pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Resu
|
|||||||
/// solid with respect to the center of the circle is maintained.
|
/// solid with respect to the center of the circle is maintained.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([.5, 25], %)
|
/// |> startProfileAt([.5, 25], %)
|
||||||
/// |> line([0, 5], %)
|
/// |> line([0, 5], %)
|
||||||
/// |> line([-1, 0], %)
|
/// |> line([-1, 0], %)
|
||||||
@ -913,7 +913,7 @@ pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Resu
|
|||||||
/// rotateDuplicates = true
|
/// rotateDuplicates = true
|
||||||
/// }, %)
|
/// }, %)
|
||||||
///
|
///
|
||||||
/// const example = extrude(1, exampleSketch)
|
/// example = extrude(1, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "patternCircular2d",
|
name = "patternCircular2d",
|
||||||
@ -967,10 +967,10 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
|
|||||||
/// solid with respect to the center of the circle is maintained.
|
/// solid with respect to the center of the circle is maintained.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
||||||
///
|
///
|
||||||
/// const example = extrude(-5, exampleSketch)
|
/// example = extrude(-5, exampleSketch)
|
||||||
/// |> patternCircular3d({
|
/// |> patternCircular3d({
|
||||||
/// axis = [1, -1, 0],
|
/// axis = [1, -1, 0],
|
||||||
/// center = [10, -20, 0],
|
/// center = [10, -20, 0],
|
||||||
|
@ -65,7 +65,7 @@ pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Loft a square and a circle on the `XY` plane using offset.
|
/// // Loft a square and a circle on the `XY` plane using offset.
|
||||||
/// const squareSketch = startSketchOn('XY')
|
/// squareSketch = startSketchOn('XY')
|
||||||
/// |> startProfileAt([-100, 200], %)
|
/// |> startProfileAt([-100, 200], %)
|
||||||
/// |> line([200, 0], %)
|
/// |> line([200, 0], %)
|
||||||
/// |> line([0, -200], %)
|
/// |> line([0, -200], %)
|
||||||
@ -73,7 +73,7 @@ pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
|||||||
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const circleSketch = startSketchOn(offsetPlane('XY', 150))
|
/// circleSketch = startSketchOn(offsetPlane('XY', 150))
|
||||||
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
||||||
///
|
///
|
||||||
/// loft([squareSketch, circleSketch])
|
/// loft([squareSketch, circleSketch])
|
||||||
@ -81,7 +81,7 @@ pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Loft a square and a circle on the `XZ` plane using offset.
|
/// // Loft a square and a circle on the `XZ` plane using offset.
|
||||||
/// const squareSketch = startSketchOn('XZ')
|
/// squareSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([-100, 200], %)
|
/// |> startProfileAt([-100, 200], %)
|
||||||
/// |> line([200, 0], %)
|
/// |> line([200, 0], %)
|
||||||
/// |> line([0, -200], %)
|
/// |> line([0, -200], %)
|
||||||
@ -89,7 +89,7 @@ pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
|||||||
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const circleSketch = startSketchOn(offsetPlane('XZ', 150))
|
/// circleSketch = startSketchOn(offsetPlane('XZ', 150))
|
||||||
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
||||||
///
|
///
|
||||||
/// loft([squareSketch, circleSketch])
|
/// loft([squareSketch, circleSketch])
|
||||||
@ -97,7 +97,7 @@ pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Loft a square and a circle on the `YZ` plane using offset.
|
/// // Loft a square and a circle on the `YZ` plane using offset.
|
||||||
/// const squareSketch = startSketchOn('YZ')
|
/// squareSketch = startSketchOn('YZ')
|
||||||
/// |> startProfileAt([-100, 200], %)
|
/// |> startProfileAt([-100, 200], %)
|
||||||
/// |> line([200, 0], %)
|
/// |> line([200, 0], %)
|
||||||
/// |> line([0, -200], %)
|
/// |> line([0, -200], %)
|
||||||
@ -105,7 +105,7 @@ pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
|||||||
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const circleSketch = startSketchOn(offsetPlane('YZ', 150))
|
/// circleSketch = startSketchOn(offsetPlane('YZ', 150))
|
||||||
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
||||||
///
|
///
|
||||||
/// loft([squareSketch, circleSketch])
|
/// loft([squareSketch, circleSketch])
|
||||||
@ -113,7 +113,7 @@ pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Loft a square and a circle on the `-XZ` plane using offset.
|
/// // Loft a square and a circle on the `-XZ` plane using offset.
|
||||||
/// const squareSketch = startSketchOn('-XZ')
|
/// squareSketch = startSketchOn('-XZ')
|
||||||
/// |> startProfileAt([-100, 200], %)
|
/// |> startProfileAt([-100, 200], %)
|
||||||
/// |> line([200, 0], %)
|
/// |> line([200, 0], %)
|
||||||
/// |> line([0, -200], %)
|
/// |> line([0, -200], %)
|
||||||
@ -121,7 +121,7 @@ pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
|||||||
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
/// |> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const circleSketch = startSketchOn(offsetPlane('-XZ', -150))
|
/// circleSketch = startSketchOn(offsetPlane('-XZ', -150))
|
||||||
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
/// |> circle({ center = [0, 100], radius = 50 }, %)
|
||||||
///
|
///
|
||||||
/// loft([squareSketch, circleSketch])
|
/// loft([squareSketch, circleSketch])
|
||||||
|
@ -34,7 +34,7 @@ pub async fn polar(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// cartesian (x/y/z grid) coordinates.
|
/// cartesian (x/y/z grid) coordinates.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// const exampleSketch = startSketchOn('XZ')
|
/// exampleSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line(polar({angle: 30, length: 5}), %, $thing)
|
/// |> line(polar({angle: 30, length: 5}), %, $thing)
|
||||||
/// |> line([0, 5], %)
|
/// |> line([0, 5], %)
|
||||||
@ -42,7 +42,7 @@ pub async fn polar(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// |> line([-20, 10], %)
|
/// |> line([-20, 10], %)
|
||||||
/// |> close(%)
|
/// |> close(%)
|
||||||
///
|
///
|
||||||
/// const example = extrude(5, exampleSketch)
|
/// example = extrude(5, exampleSketch)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "polar",
|
name = "polar",
|
||||||
|