Merge remote-tracking branch 'origin/main' into paultag/refgraph
2
.github/workflows/build-and-store-wasm.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
# Upload the WASM bundle as an artifact
|
# Upload the WASM bundle as an artifact
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: wasm-bundle
|
name: wasm-bundle
|
||||||
path: src/wasm-lib/pkg
|
path: src/wasm-lib/pkg
|
||||||
|
|||||||
8
.github/workflows/build-apps.yml
vendored
@ -126,7 +126,13 @@ jobs:
|
|||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'yarn' # Set this to npm, yarn or pnpm.
|
cache: 'yarn' # Set this to npm, yarn or pnpm.
|
||||||
|
|
||||||
- run: yarn install
|
- name: yarn install
|
||||||
|
# Windows is picky sometimes and fails on fetch. Step takes about ~30s
|
||||||
|
uses: nick-fields/retry@v3.0.0
|
||||||
|
with:
|
||||||
|
timeout_minutes: 2
|
||||||
|
max_attempts: 3
|
||||||
|
command: yarn install
|
||||||
|
|
||||||
- run: yarn tronb:vite
|
- run: yarn tronb:vite
|
||||||
|
|
||||||
|
|||||||
@ -24,5 +24,3 @@ once fixed in engine will just start working here with no language changes.
|
|||||||
chamfer cases work currently.
|
chamfer cases work currently.
|
||||||
|
|
||||||
- **Appearance**: Changing the appearance on a loft does not work.
|
- **Appearance**: Changing the appearance on a loft does not work.
|
||||||
|
|
||||||
- **Helix**: Currently sweeping a helix does not work.
|
|
||||||
|
|||||||
@ -76961,9 +76961,9 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"examples": [
|
"examples": [
|
||||||
"// Create a helix around the Z axis.\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 16,\n length = 10,\n radius = 5,\n axis = 'Z'\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('YZ')\n |> circle({ center = [0, 0], radius = 1 }, %)\n// |> sweep({ path = helixPath }, %)",
|
"// Create a helix around the Z axis.\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 5,\n length = 10,\n radius = 5,\n axis = 'Z'\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('YZ')\n |> circle({ center = [0, 0], radius = 0.5 }, %)\n |> sweep({ path = helixPath }, %)",
|
||||||
"// Create a helix around an edge.\nhelper001 = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %, $edge001)\n\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 16,\n length = 10,\n radius = 5,\n axis = edge001\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 1 }, %)\n// |> sweep({ path = helixPath }, %)",
|
"// Create a helix around an edge.\nhelper001 = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %, $edge001)\n\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 5,\n length = 10,\n radius = 5,\n axis = edge001\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 0.5 }, %)\n |> sweep({ path = helixPath }, %)",
|
||||||
"// Create a helix around a custom axis.\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 16,\n length = 10,\n radius = 5,\n axis = {\n custom = {\n axis = [0, 0, 1.0],\n origin = [0, 0.25, 0]\n }\n }\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 1 }, %)\n// |> sweep({ path = helixPath }, %)"
|
"// Create a helix around a custom axis.\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 5,\n length = 10,\n radius = 5,\n axis = {\n custom = {\n axis = [0, 0, 1.0],\n origin = [0, 0.25, 0]\n }\n }\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 1 }, %)\n |> sweep({ path = helixPath }, %)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -193684,7 +193684,7 @@
|
|||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"examples": [
|
"examples": [
|
||||||
"// Create a pipe using a sweep.\n\n\n// Create a path for the sweep.\nsweepPath = startSketchOn('XZ')\n |> startProfileAt([0.05, 0.05], %)\n |> line([0, 7], %)\n |> tangentialArc({ offset = 90, radius = 5 }, %)\n |> line([-3, 0], %)\n |> tangentialArc({ offset = -90, radius = 5 }, %)\n |> line([0, 7], %)\n\n// Create a hole for the pipe.\npipeHole = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 1.5 }, %)\n\nsweepSketch = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 2 }, %)\n |> hole(pipeHole, %)\n |> sweep({ path = sweepPath }, %)",
|
"// Create a pipe using a sweep.\n\n\n// Create a path for the sweep.\nsweepPath = startSketchOn('XZ')\n |> startProfileAt([0.05, 0.05], %)\n |> line([0, 7], %)\n |> tangentialArc({ offset = 90, radius = 5 }, %)\n |> line([-3, 0], %)\n |> tangentialArc({ offset = -90, radius = 5 }, %)\n |> line([0, 7], %)\n\n// Create a hole for the pipe.\npipeHole = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 1.5 }, %)\n\nsweepSketch = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 2 }, %)\n |> hole(pipeHole, %)\n |> sweep({ path = sweepPath }, %)",
|
||||||
"// Create a spring by sweeping around a helix path.\n\n\n// Create a helix around the Z axis.\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 16,\n length = 10,\n radius = 5,\n axis = 'Z'\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('YZ')\n |> circle({ center = [0, 0], radius = 1 }, %)\n// |> sweep({ path = helixPath }, %)"
|
"// Create a spring by sweeping around a helix path.\n\n\n// Create a helix around the Z axis.\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 4,\n length = 10,\n radius = 5,\n axis = 'Z'\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('YZ')\n |> circle({ center = [0, 0], radius = 1 }, %)\n |> sweep({ path = helixPath }, %)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -45,46 +45,6 @@ test.describe('Command bar tests', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: fix this test after the electron migration
|
|
||||||
test.fixme('Fillet from command bar', async ({ page, homePage }) => {
|
|
||||||
await page.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'persistCode',
|
|
||||||
`sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-5, -5], %)
|
|
||||||
|> line([0, 10], %)
|
|
||||||
|> line([10, 0], %)
|
|
||||||
|> line([0, -10], %)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
|> close(%)
|
|
||||||
extrude001 = extrude(-10, sketch001)`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
const selectSegment = () => page.getByText(`line([0, -10], %)`).click()
|
|
||||||
|
|
||||||
await selectSegment()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.getByRole('button', { name: 'Fillet' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.keyboard.press('Enter') // skip selection
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.keyboard.press('Enter') // accept default radius
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.keyboard.press('Enter') // submit
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await expect(page.locator('.cm-activeLine')).toContainText(
|
|
||||||
`fillet({ radius = ${KCL_DEFAULT_LENGTH}, tags = [seg01] }, %)`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export class ToolbarFixture {
|
|||||||
extrudeButton!: Locator
|
extrudeButton!: Locator
|
||||||
loftButton!: Locator
|
loftButton!: Locator
|
||||||
sweepButton!: Locator
|
sweepButton!: Locator
|
||||||
|
filletButton!: Locator
|
||||||
chamferButton!: Locator
|
chamferButton!: Locator
|
||||||
shellButton!: Locator
|
shellButton!: Locator
|
||||||
offsetPlaneButton!: Locator
|
offsetPlaneButton!: Locator
|
||||||
@ -43,6 +44,7 @@ export class ToolbarFixture {
|
|||||||
this.extrudeButton = page.getByTestId('extrude')
|
this.extrudeButton = page.getByTestId('extrude')
|
||||||
this.loftButton = page.getByTestId('loft')
|
this.loftButton = page.getByTestId('loft')
|
||||||
this.sweepButton = page.getByTestId('sweep')
|
this.sweepButton = page.getByTestId('sweep')
|
||||||
|
this.filletButton = page.getByTestId('fillet3d')
|
||||||
this.chamferButton = page.getByTestId('chamfer3d')
|
this.chamferButton = page.getByTestId('chamfer3d')
|
||||||
this.shellButton = page.getByTestId('shell')
|
this.shellButton = page.getByTestId('shell')
|
||||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||||
|
|||||||
@ -829,12 +829,6 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
|||||||
})
|
})
|
||||||
await selectSketches()
|
await selectSketches()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'review',
|
|
||||||
headerArguments: { Selection: '2 faces' },
|
|
||||||
commandName: 'Loft',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
await test.step(`Preselect the two sketches`, async () => {
|
await test.step(`Preselect the two sketches`, async () => {
|
||||||
@ -844,12 +838,6 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
|||||||
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
|
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
|
||||||
await toolbar.loftButton.click()
|
await toolbar.loftButton.click()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'review',
|
|
||||||
headerArguments: { Selection: '2 faces' },
|
|
||||||
commandName: 'Loft',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1032,6 +1020,221 @@ sketch002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(`Fillet point-and-click`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
// Code samples
|
||||||
|
const initialCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-12, -6], %)
|
||||||
|
|> line([0, 12], %)
|
||||||
|
|> line([24, 0], %)
|
||||||
|
|> line([0, -12], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-12, sketch001)
|
||||||
|
`
|
||||||
|
const firstFilletDeclaration = 'fillet({ radius = 5, tags = [seg01] }, %)'
|
||||||
|
const secondFilletDeclaration =
|
||||||
|
'fillet({ radius = 5, tags = [getOppositeEdge(seg01)] }, %)'
|
||||||
|
|
||||||
|
// Locators
|
||||||
|
const firstEdgeLocation = { x: 600, y: 193 }
|
||||||
|
const secondEdgeLocation = { x: 600, y: 383 }
|
||||||
|
const bodyLocation = { x: 630, y: 290 }
|
||||||
|
const [clickOnFirstEdge] = scene.makeMouseHelpers(
|
||||||
|
firstEdgeLocation.x,
|
||||||
|
firstEdgeLocation.y
|
||||||
|
)
|
||||||
|
const [clickOnSecondEdge] = scene.makeMouseHelpers(
|
||||||
|
secondEdgeLocation.x,
|
||||||
|
secondEdgeLocation.y
|
||||||
|
)
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const edgeColorWhite: [number, number, number] = [248, 248, 248]
|
||||||
|
const edgeColorYellow: [number, number, number] = [251, 251, 40] // Mac:B=67 Ubuntu:B=12
|
||||||
|
const bodyColor: [number, number, number] = [155, 155, 155]
|
||||||
|
const filletColor: [number, number, number] = [127, 127, 127]
|
||||||
|
const backgroundColor: [number, number, number] = [30, 30, 30]
|
||||||
|
const lowTolerance = 20
|
||||||
|
const highTolerance = 40
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
await test.step(`Initial test setup`, async () => {
|
||||||
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// verify modeling scene is loaded
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
backgroundColor,
|
||||||
|
secondEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
|
||||||
|
// wait for stream to load
|
||||||
|
await scene.expectPixelColor(bodyColor, bodyLocation, highTolerance)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 1: Command bar flow with preselected edges
|
||||||
|
await test.step(`Select first edge`, async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
firstEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
await clickOnFirstEdge()
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
firstEdgeLocation,
|
||||||
|
highTolerance // Ubuntu color mismatch can require high tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Apply fillet to the preselected edge`, async () => {
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await toolbar.filletButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Fillet',
|
||||||
|
highlightedHeaderArg: 'selection',
|
||||||
|
currentArgKey: 'selection',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '',
|
||||||
|
Radius: '',
|
||||||
|
},
|
||||||
|
stage: 'arguments',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Fillet',
|
||||||
|
highlightedHeaderArg: 'radius',
|
||||||
|
currentArgKey: 'radius',
|
||||||
|
currentArgValue: '5',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '1 face',
|
||||||
|
Radius: '',
|
||||||
|
},
|
||||||
|
stage: 'arguments',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Fillet',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '1 face',
|
||||||
|
Radius: '5',
|
||||||
|
},
|
||||||
|
stage: 'review',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Confirm code is added to the editor`, async () => {
|
||||||
|
await editor.expectEditor.toContain(firstFilletDeclaration)
|
||||||
|
await editor.expectState({
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: ['|>fillet({radius=5,tags=[seg01]},%)'],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Confirm scene has changed`, async () => {
|
||||||
|
await scene.expectPixelColor(filletColor, firstEdgeLocation, lowTolerance)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 2: Command bar flow without preselected edges
|
||||||
|
await test.step(`Open fillet UI without selecting edges`, async () => {
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await toolbar.filletButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'selection',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '',
|
||||||
|
Radius: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'selection',
|
||||||
|
commandName: 'Fillet',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Select second edge`, async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
secondEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
await clickOnSecondEdge()
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
secondEdgeLocation,
|
||||||
|
highTolerance // Ubuntu color mismatch can require high tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Apply fillet to the second edge`, async () => {
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Fillet',
|
||||||
|
highlightedHeaderArg: 'selection',
|
||||||
|
currentArgKey: 'selection',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '',
|
||||||
|
Radius: '',
|
||||||
|
},
|
||||||
|
stage: 'arguments',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Fillet',
|
||||||
|
highlightedHeaderArg: 'radius',
|
||||||
|
currentArgKey: 'radius',
|
||||||
|
currentArgValue: '5',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '1 sweepEdge',
|
||||||
|
Radius: '',
|
||||||
|
},
|
||||||
|
stage: 'arguments',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Fillet',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '1 sweepEdge',
|
||||||
|
Radius: '5',
|
||||||
|
},
|
||||||
|
stage: 'review',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Confirm code is added to the editor`, async () => {
|
||||||
|
await editor.expectEditor.toContain(secondFilletDeclaration)
|
||||||
|
await editor.expectState({
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: ['radius=5,'],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Confirm scene has changed`, async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
backgroundColor,
|
||||||
|
secondEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test(`Chamfer point-and-click`, async ({
|
test(`Chamfer point-and-click`, async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -1041,9 +1244,6 @@ test(`Chamfer point-and-click`, async ({
|
|||||||
toolbar,
|
toolbar,
|
||||||
cmdBar,
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: fix this test on windows after the electron migration
|
|
||||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
|
||||||
|
|
||||||
// Code samples
|
// Code samples
|
||||||
const initialCode = `sketch001 = startSketchOn('XY')
|
const initialCode = `sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-12, -6], %)
|
|> startProfileAt([-12, -6], %)
|
||||||
@ -1081,13 +1281,13 @@ extrude001 = extrude(-12, sketch001)
|
|||||||
const highTolerance = 40
|
const highTolerance = 40
|
||||||
|
|
||||||
// Setup
|
// Setup
|
||||||
await context.addInitScript((initialCode) => {
|
await test.step(`Initial test setup`, async () => {
|
||||||
localStorage.setItem('persistCode', initialCode)
|
await context.addInitScript((initialCode) => {
|
||||||
}, initialCode)
|
localStorage.setItem('persistCode', initialCode)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
}, initialCode)
|
||||||
await homePage.goToModelingScene()
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await test.step(`Verify scene is loaded`, async () => {
|
|
||||||
// verify modeling scene is loaded
|
// verify modeling scene is loaded
|
||||||
await scene.expectPixelColor(
|
await scene.expectPixelColor(
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
@ -1115,6 +1315,7 @@ extrude001 = extrude(-12, sketch001)
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Apply chamfer to the preselected edge`, async () => {
|
await test.step(`Apply chamfer to the preselected edge`, async () => {
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await toolbar.chamferButton.click()
|
await toolbar.chamferButton.click()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Chamfer',
|
commandName: 'Chamfer',
|
||||||
@ -1166,6 +1367,7 @@ extrude001 = extrude(-12, sketch001)
|
|||||||
|
|
||||||
// Test 2: Command bar flow without preselected edges
|
// Test 2: Command bar flow without preselected edges
|
||||||
await test.step(`Open chamfer UI without selecting edges`, async () => {
|
await test.step(`Open chamfer UI without selecting edges`, async () => {
|
||||||
|
await page.waitForTimeout(100)
|
||||||
await toolbar.chamferButton.click()
|
await toolbar.chamferButton.click()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
|
|||||||
@ -108,6 +108,8 @@ export class CameraControls {
|
|||||||
interactionGuards: MouseGuard = cameraMouseDragGuards.Zoo
|
interactionGuards: MouseGuard = cameraMouseDragGuards.Zoo
|
||||||
isFovAnimationInProgress = false
|
isFovAnimationInProgress = false
|
||||||
perspectiveFovBeforeOrtho = 45
|
perspectiveFovBeforeOrtho = 45
|
||||||
|
// NOTE: Duplicated state across Provider and singleton. Mapped from settingsMachine
|
||||||
|
_setting_allowOrbitInSketchMode = false
|
||||||
get isPerspective() {
|
get isPerspective() {
|
||||||
return this.camera instanceof PerspectiveCamera
|
return this.camera instanceof PerspectiveCamera
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ export const CommandBar = () => {
|
|||||||
|
|
||||||
// Close the command bar when navigating
|
// Close the command bar when navigating
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (commandBarState.matches('Closed')) return
|
||||||
commandBarSend({ type: 'Close' })
|
commandBarSend({ type: 'Close' })
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
|||||||
import { Command } from 'lib/commandTypes'
|
import { Command } from 'lib/commandTypes'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
|
import { getActorNextEvents } from 'lib/utils'
|
||||||
|
import { sortCommands } from 'lib/commandUtils'
|
||||||
|
|
||||||
function CommandComboBox({
|
function CommandComboBox({
|
||||||
options,
|
options,
|
||||||
@ -18,8 +20,16 @@ function CommandComboBox({
|
|||||||
|
|
||||||
const defaultOption =
|
const defaultOption =
|
||||||
options.find((o) => 'isCurrent' in o && o.isCurrent) || null
|
options.find((o) => 'isCurrent' in o && o.isCurrent) || null
|
||||||
|
// sort disabled commands to the bottom
|
||||||
|
const sortedOptions = options
|
||||||
|
.map((command) => ({
|
||||||
|
command,
|
||||||
|
disabled: optionIsDisabled(command),
|
||||||
|
}))
|
||||||
|
.sort(sortCommands)
|
||||||
|
.map(({ command }) => command)
|
||||||
|
|
||||||
const fuse = new Fuse(options, {
|
const fuse = new Fuse(sortedOptions, {
|
||||||
keys: ['displayName', 'name', 'description'],
|
keys: ['displayName', 'name', 'description'],
|
||||||
threshold: 0.3,
|
threshold: 0.3,
|
||||||
ignoreLocation: true,
|
ignoreLocation: true,
|
||||||
@ -27,7 +37,7 @@ function CommandComboBox({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const results = fuse.search(query).map((result) => result.item)
|
const results = fuse.search(query).map((result) => result.item)
|
||||||
setFilteredOptions(query.length > 0 ? results : options)
|
setFilteredOptions(query.length > 0 ? results : sortedOptions)
|
||||||
}, [query])
|
}, [query])
|
||||||
|
|
||||||
function handleSelection(command: Command) {
|
function handleSelection(command: Command) {
|
||||||
@ -73,7 +83,8 @@ function CommandComboBox({
|
|||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
key={option.groupId + option.name + (option.displayName || '')}
|
key={option.groupId + option.name + (option.displayName || '')}
|
||||||
value={option}
|
value={option}
|
||||||
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90 ui-disabled:!text-chalkboard-50"
|
||||||
|
disabled={optionIsDisabled(option)}
|
||||||
>
|
>
|
||||||
{'icon' in option && option.icon && (
|
{'icon' in option && option.icon && (
|
||||||
<CustomIcon name={option.icon} className="w-5 h-5" />
|
<CustomIcon name={option.icon} className="w-5 h-5" />
|
||||||
@ -96,3 +107,11 @@ function CommandComboBox({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default CommandComboBox
|
export default CommandComboBox
|
||||||
|
|
||||||
|
function optionIsDisabled(option: Command): boolean {
|
||||||
|
return (
|
||||||
|
'machineActor' in option &&
|
||||||
|
option.machineActor !== undefined &&
|
||||||
|
!getActorNextEvents(option.machineActor.getSnapshot()).includes(option.name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@ -111,7 +111,7 @@ export const ModelingMachineProvider = ({
|
|||||||
auth,
|
auth,
|
||||||
settings: {
|
settings: {
|
||||||
context: {
|
context: {
|
||||||
app: { theme, enableSSAO },
|
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
||||||
modeling: {
|
modeling: {
|
||||||
defaultUnit,
|
defaultUnit,
|
||||||
cameraProjection,
|
cameraProjection,
|
||||||
@ -121,6 +121,7 @@ export const ModelingMachineProvider = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
} = useSettingsAuthContext()
|
} = useSettingsAuthContext()
|
||||||
|
const previousAllowOrbitInSketchMode = useRef(allowOrbitInSketchMode.current)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { context, send: fileMachineSend } = useFileContext()
|
const { context, send: fileMachineSend } = useFileContext()
|
||||||
const { file } = useLoaderData() as IndexLoaderData
|
const { file } = useLoaderData() as IndexLoaderData
|
||||||
@ -634,7 +635,8 @@ export const ModelingMachineProvider = ({
|
|||||||
input.plane
|
input.plane
|
||||||
)
|
)
|
||||||
await kclManager.updateAst(modifiedAst, false)
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
sceneInfra.camControls.enableRotate = false
|
sceneInfra.camControls.enableRotate =
|
||||||
|
sceneInfra.camControls._setting_allowOrbitInSketchMode
|
||||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||||
|
|
||||||
await letEngineAnimateAndSyncCamAfter(
|
await letEngineAnimateAndSyncCamAfter(
|
||||||
@ -647,6 +649,7 @@ export const ModelingMachineProvider = ({
|
|||||||
zAxis: input.zAxis,
|
zAxis: input.zAxis,
|
||||||
yAxis: input.yAxis,
|
yAxis: input.yAxis,
|
||||||
origin: [0, 0, 0],
|
origin: [0, 0, 0],
|
||||||
|
animateTargetId: input.planeId,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
'animate-to-sketch': fromPromise(
|
'animate-to-sketch': fromPromise(
|
||||||
@ -671,6 +674,7 @@ export const ModelingMachineProvider = ({
|
|||||||
origin: info.sketchDetails.origin.map(
|
origin: info.sketchDetails.origin.map(
|
||||||
(a) => a / sceneInfra._baseUnitMultiplier
|
(a) => a / sceneInfra._baseUnitMultiplier
|
||||||
) as [number, number, number],
|
) as [number, number, number],
|
||||||
|
animateTargetId: info?.sketchDetails?.faceId || '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@ -1188,6 +1192,41 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
}, [engineCommandManager.engineConnection, modelingSend])
|
}, [engineCommandManager.engineConnection, modelingSend])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Only trigger this if the state actually changes, if it stays the same do not reload the camera
|
||||||
|
if (
|
||||||
|
previousAllowOrbitInSketchMode.current === allowOrbitInSketchMode.current
|
||||||
|
) {
|
||||||
|
//no op
|
||||||
|
previousAllowOrbitInSketchMode.current = allowOrbitInSketchMode.current
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const inSketchMode = modelingState.matches('Sketch')
|
||||||
|
|
||||||
|
// If you are in sketch mode and you disable the orbit, return back to the normal view to the target
|
||||||
|
if (!allowOrbitInSketchMode.current) {
|
||||||
|
const targetId = modelingState.context.sketchDetails?.animateTargetId
|
||||||
|
if (inSketchMode && targetId) {
|
||||||
|
letEngineAnimateAndSyncCamAfter(engineCommandManager, targetId)
|
||||||
|
.then(() => {})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(
|
||||||
|
'failed to sync engine and client scene after disabling allow orbit in sketch mode'
|
||||||
|
)
|
||||||
|
console.error(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// While you are in sketch mode you should be able to control the enable rotate
|
||||||
|
// Once you exit it goes back to normal
|
||||||
|
if (inSketchMode) {
|
||||||
|
sceneInfra.camControls.enableRotate = allowOrbitInSketchMode.current
|
||||||
|
}
|
||||||
|
|
||||||
|
previousAllowOrbitInSketchMode.current = allowOrbitInSketchMode.current
|
||||||
|
}, [allowOrbitInSketchMode])
|
||||||
|
|
||||||
// Allow using the delete key to delete solids
|
// Allow using the delete key to delete solids
|
||||||
useHotkeys(['backspace', 'delete', 'del'], () => {
|
useHotkeys(['backspace', 'delete', 'del'], () => {
|
||||||
modelingSend({ type: 'Delete selection' })
|
modelingSend({ type: 'Delete selection' })
|
||||||
|
|||||||
@ -137,6 +137,11 @@ export const SettingsAuthProviderBase = ({
|
|||||||
sceneInfra.theme = opposingTheme
|
sceneInfra.theme = opposingTheme
|
||||||
sceneEntitiesManager.updateSegmentBaseColor(opposingTheme)
|
sceneEntitiesManager.updateSegmentBaseColor(opposingTheme)
|
||||||
},
|
},
|
||||||
|
setAllowOrbitInSketchMode: ({ context }) => {
|
||||||
|
sceneInfra.camControls._setting_allowOrbitInSketchMode =
|
||||||
|
context.app.allowOrbitInSketchMode.current
|
||||||
|
// ModelingMachineProvider will do a use effect to trigger the camera engine sync
|
||||||
|
},
|
||||||
toastSuccess: ({ event }) => {
|
toastSuccess: ({ event }) => {
|
||||||
if (!('data' in event)) return
|
if (!('data' in event)) return
|
||||||
const eventParts = event.type.replace(/^set./, '').split('.') as [
|
const eventParts = event.type.replace(/^set./, '').split('.') as [
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { AnyStateMachine, Actor, StateFrom } from 'xstate'
|
import { AnyStateMachine, Actor, StateFrom, EventFrom } from 'xstate'
|
||||||
import { createMachineCommand } from '../lib/createMachineCommand'
|
import { createMachineCommand } from '../lib/createMachineCommand'
|
||||||
import { useCommandsContext } from './useCommandsContext'
|
import { useCommandsContext } from './useCommandsContext'
|
||||||
import { modelingMachine } from 'machines/modelingMachine'
|
import { modelingMachine } from 'machines/modelingMachine'
|
||||||
@ -15,7 +15,6 @@ import { useKclContext } from 'lang/KclProvider'
|
|||||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||||
import { useAppState } from 'AppState'
|
import { useAppState } from 'AppState'
|
||||||
import { getActorNextEvents } from 'lib/utils'
|
|
||||||
|
|
||||||
// This might not be necessary, AnyStateMachine from xstate is working
|
// This might not be necessary, AnyStateMachine from xstate is working
|
||||||
export type AllMachines =
|
export type AllMachines =
|
||||||
@ -60,21 +59,21 @@ export default function useStateMachineCommands<
|
|||||||
overallState !== NetworkHealthState.Weak) ||
|
overallState !== NetworkHealthState.Weak) ||
|
||||||
isExecuting ||
|
isExecuting ||
|
||||||
!isStreamReady
|
!isStreamReady
|
||||||
const newCommands = getActorNextEvents(state)
|
const newCommands = Object.keys(commandBarConfig || {})
|
||||||
.filter((_) => !allCommandsRequireNetwork || !disableAllButtons)
|
.filter((_) => !allCommandsRequireNetwork || !disableAllButtons)
|
||||||
.filter((e) => !['done.', 'error.'].some((n) => e.includes(n)))
|
.flatMap((type) => {
|
||||||
.flatMap((type) =>
|
const typeWithProperType = type as EventFrom<T>['type']
|
||||||
createMachineCommand<T, S>({
|
return createMachineCommand<T, S>({
|
||||||
// The group is the owner machine's ID.
|
// The group is the owner machine's ID.
|
||||||
groupId: machineId,
|
groupId: machineId,
|
||||||
type,
|
type: typeWithProperType,
|
||||||
state,
|
state,
|
||||||
send,
|
send,
|
||||||
actor,
|
actor,
|
||||||
commandBarConfig,
|
commandBarConfig,
|
||||||
onCancel,
|
onCancel,
|
||||||
})
|
})
|
||||||
)
|
})
|
||||||
.filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls
|
.filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls
|
||||||
|
|
||||||
commandBarSend({ type: 'Add commands', data: { commands: newCommands } })
|
commandBarSend({ type: 'Add commands', data: { commands: newCommands } })
|
||||||
@ -85,5 +84,5 @@ export default function useStateMachineCommands<
|
|||||||
data: { commands: newCommands },
|
data: { commands: newCommands },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [state, overallState, isExecuting, isStreamReady])
|
}, [overallState, isExecuting, isStreamReady])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -329,7 +329,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
Loft: {
|
Loft: {
|
||||||
description: 'Create a 3D body by blending between two or more sketches',
|
description: 'Create a 3D body by blending between two or more sketches',
|
||||||
icon: 'loft',
|
icon: 'loft',
|
||||||
needsReview: true,
|
needsReview: false,
|
||||||
args: {
|
args: {
|
||||||
selection: {
|
selection: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
|
|||||||
@ -63,12 +63,11 @@ export const projectsCommandBarConfig: StateMachineCommandSetConfig<
|
|||||||
name: {
|
name: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
required: true,
|
required: true,
|
||||||
options: [],
|
options: (_, context) =>
|
||||||
optionsFromContext: (context) =>
|
context?.projects.map((p) => ({
|
||||||
context.projects.map((p) => ({
|
|
||||||
name: p.name!,
|
name: p.name!,
|
||||||
value: p.name!,
|
value: p.name!,
|
||||||
})),
|
})) || [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -80,12 +79,11 @@ export const projectsCommandBarConfig: StateMachineCommandSetConfig<
|
|||||||
oldName: {
|
oldName: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
required: true,
|
required: true,
|
||||||
options: [],
|
options: (_, context) =>
|
||||||
optionsFromContext: (context) =>
|
context?.projects.map((p) => ({
|
||||||
context.projects.map((p) => ({
|
|
||||||
name: p.name!,
|
name: p.name!,
|
||||||
value: p.name!,
|
value: p.name!,
|
||||||
})),
|
})) || [],
|
||||||
},
|
},
|
||||||
newName: {
|
newName: {
|
||||||
inputType: 'string',
|
inputType: 'string',
|
||||||
|
|||||||
@ -76,6 +76,7 @@ export type Command<
|
|||||||
| ((
|
| ((
|
||||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> } // Should be the commandbarMachine's context, but it creates a circular dependency
|
commandBarContext: { argumentsToSubmit: Record<string, unknown> } // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||||
) => string | ReactNode)
|
) => string | ReactNode)
|
||||||
|
machineActor?: Actor<T>
|
||||||
onSubmit: (data?: CommandSchema) => void
|
onSubmit: (data?: CommandSchema) => void
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
args?: {
|
args?: {
|
||||||
@ -95,7 +96,7 @@ export type CommandConfig<
|
|||||||
Command<T, CommandName, CommandSchema>,
|
Command<T, CommandName, CommandSchema>,
|
||||||
'name' | 'groupId' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
|
'name' | 'groupId' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
|
||||||
> & {
|
> & {
|
||||||
needsReview?: true
|
needsReview?: boolean
|
||||||
status?: 'active' | 'development' | 'inactive'
|
status?: 'active' | 'development' | 'inactive'
|
||||||
args?: {
|
args?: {
|
||||||
[ArgName in keyof CommandSchema]: CommandArgumentConfig<
|
[ArgName in keyof CommandSchema]: CommandArgumentConfig<
|
||||||
|
|||||||
49
src/lib/commandUtils.test.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { CommandWithDisabledState, sortCommands } from './commandUtils'
|
||||||
|
|
||||||
|
function commandWithDisabled(
|
||||||
|
name: string,
|
||||||
|
disabled: boolean,
|
||||||
|
groupId = 'modeling'
|
||||||
|
): CommandWithDisabledState {
|
||||||
|
return {
|
||||||
|
command: {
|
||||||
|
name,
|
||||||
|
groupId,
|
||||||
|
needsReview: false,
|
||||||
|
onSubmit: () => {},
|
||||||
|
},
|
||||||
|
disabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Command sorting', () => {
|
||||||
|
it(`Puts modeling commands first`, () => {
|
||||||
|
const initial = [
|
||||||
|
commandWithDisabled('a', false, 'settings'),
|
||||||
|
commandWithDisabled('b', false, 'modeling'),
|
||||||
|
commandWithDisabled('c', false, 'settings'),
|
||||||
|
]
|
||||||
|
const sorted = initial.sort(sortCommands)
|
||||||
|
expect(sorted[0].command.groupId).toBe('modeling')
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`Puts disabled commands last`, () => {
|
||||||
|
const initial = [
|
||||||
|
commandWithDisabled('a', true, 'modeling'),
|
||||||
|
commandWithDisabled('z', false, 'modeling'),
|
||||||
|
commandWithDisabled('a', false, 'settings'),
|
||||||
|
]
|
||||||
|
const sorted = initial.sort(sortCommands)
|
||||||
|
expect(sorted[sorted.length - 1].disabled).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`Puts settings commands second to last`, () => {
|
||||||
|
const initial = [
|
||||||
|
commandWithDisabled('a', true, 'modeling'),
|
||||||
|
commandWithDisabled('z', false, 'modeling'),
|
||||||
|
commandWithDisabled('a', false, 'settings'),
|
||||||
|
]
|
||||||
|
const sorted = initial.sort(sortCommands)
|
||||||
|
expect(sorted[1].command.groupId).toBe('settings')
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -2,6 +2,9 @@
|
|||||||
// That object also contains some metadata about what to do with the KCL expression,
|
// That object also contains some metadata about what to do with the KCL expression,
|
||||||
// such as whether we need to create a new variable for it.
|
// such as whether we need to create a new variable for it.
|
||||||
// This function extracts the value field from those arg payloads and returns
|
// This function extracts the value field from those arg payloads and returns
|
||||||
|
|
||||||
|
import { Command } from './commandTypes'
|
||||||
|
|
||||||
// The arg object with all its field as natural values that the command to be executed will expect.
|
// The arg object with all its field as natural values that the command to be executed will expect.
|
||||||
export function getCommandArgumentKclValuesOnly(args: Record<string, unknown>) {
|
export function getCommandArgumentKclValuesOnly(args: Record<string, unknown>) {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
@ -13,3 +16,42 @@ export function getCommandArgumentKclValuesOnly(args: Record<string, unknown>) {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CommandWithDisabledState {
|
||||||
|
command: Command
|
||||||
|
disabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorting logic for commands in the command combo box.
|
||||||
|
*/
|
||||||
|
export function sortCommands(
|
||||||
|
a: CommandWithDisabledState,
|
||||||
|
b: CommandWithDisabledState
|
||||||
|
) {
|
||||||
|
// Disabled commands should be at the bottom
|
||||||
|
if (a.disabled && !b.disabled) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (b.disabled && !a.disabled) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
// Settings commands should be next-to-last
|
||||||
|
if (a.command.groupId === 'settings' && b.command.groupId !== 'settings') {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (b.command.groupId === 'settings' && a.command.groupId !== 'settings') {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
// Modeling commands should be first
|
||||||
|
if (a.command.groupId === 'modeling' && b.command.groupId !== 'modeling') {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if (b.command.groupId === 'modeling' && a.command.groupId !== 'modeling') {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
// Sort alphabetically
|
||||||
|
return (a.command.displayName || a.command.name).localeCompare(
|
||||||
|
b.command.displayName || b.command.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@ -96,6 +96,7 @@ export function createMachineCommand<
|
|||||||
icon,
|
icon,
|
||||||
description: commandConfig.description,
|
description: commandConfig.description,
|
||||||
needsReview: commandConfig.needsReview || false,
|
needsReview: commandConfig.needsReview || false,
|
||||||
|
machineActor: actor,
|
||||||
onSubmit: (data?: S[typeof type]) => {
|
onSubmit: (data?: S[typeof type]) => {
|
||||||
if (data !== undefined && data !== null) {
|
if (data !== undefined && data !== null) {
|
||||||
send({ type, data })
|
send({ type, data })
|
||||||
|
|||||||
@ -190,6 +190,14 @@ export function createSettings() {
|
|||||||
inputType: 'boolean',
|
inputType: 'boolean',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
allowOrbitInSketchMode: new Setting<boolean>({
|
||||||
|
defaultValue: false,
|
||||||
|
description: 'Toggle free camera while in sketch mode',
|
||||||
|
validate: (v) => typeof v === 'boolean',
|
||||||
|
commandConfig: {
|
||||||
|
inputType: 'boolean',
|
||||||
|
},
|
||||||
|
}),
|
||||||
onboardingStatus: new Setting<OnboardingStatus>({
|
onboardingStatus: new Setting<OnboardingStatus>({
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
// TODO: this could be better but we don't have a TS side real enum
|
// TODO: this could be better but we don't have a TS side real enum
|
||||||
|
|||||||
@ -41,6 +41,8 @@ export function configurationToSettingsPayload(
|
|||||||
onboardingStatus: configuration?.settings?.app?.onboarding_status,
|
onboardingStatus: configuration?.settings?.app?.onboarding_status,
|
||||||
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
|
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
|
||||||
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
||||||
|
allowOrbitInSketchMode:
|
||||||
|
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||||
projectDirectory: configuration?.settings?.project?.directory,
|
projectDirectory: configuration?.settings?.project?.directory,
|
||||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||||
},
|
},
|
||||||
@ -80,6 +82,8 @@ export function projectConfigurationToSettingsPayload(
|
|||||||
onboardingStatus: configuration?.settings?.app?.onboarding_status,
|
onboardingStatus: configuration?.settings?.app?.onboarding_status,
|
||||||
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
|
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
|
||||||
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
||||||
|
allowOrbitInSketchMode:
|
||||||
|
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||||
},
|
},
|
||||||
modeling: {
|
modeling: {
|
||||||
|
|||||||
@ -119,6 +119,9 @@ export const commandBarMachine = setup({
|
|||||||
selectedCommand?.onSubmit()
|
selectedCommand?.onSubmit()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'Clear selected command': assign({
|
||||||
|
selectedCommand: undefined,
|
||||||
|
}),
|
||||||
'Set current argument to first non-skippable': assign({
|
'Set current argument to first non-skippable': assign({
|
||||||
currentArgument: ({ context, event }) => {
|
currentArgument: ({ context, event }) => {
|
||||||
const { selectedCommand } = context
|
const { selectedCommand } = context
|
||||||
@ -246,6 +249,7 @@ export const commandBarMachine = setup({
|
|||||||
context.selectedCommand?.needsReview || false,
|
context.selectedCommand?.needsReview || false,
|
||||||
'Command has no arguments': () => false,
|
'Command has no arguments': () => false,
|
||||||
'All arguments are skippable': () => false,
|
'All arguments are skippable': () => false,
|
||||||
|
'Has selected command': ({ context }) => !!context.selectedCommand,
|
||||||
},
|
},
|
||||||
actors: {
|
actors: {
|
||||||
'Validate argument': fromPromise(
|
'Validate argument': fromPromise(
|
||||||
@ -394,7 +398,7 @@ export const commandBarMachine = setup({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
}).createMachine({
|
}).createMachine({
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22O7JwozosyLUj3KiZZY85YtMUNgx5Ii81JuS5xBtPVCCyuVR0AOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSjPPFpDSQUP0DSQf3-QDgNAiCoJggAROBNw+aMkxNf5cMPHJSjPWRdhvZ9LGcF0b0kVQ8ycDRNkvNRWMbdjfwAoCcCjHjMOgyRyQAdywGITPwB42DA7hYzAgAjdAeDQjCoJw-du3TJE6mnWwoT0NIUTUAs71sMtdGsRYCyUtI9OJDijO49DeKw2BJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MBySy8y-LGPCElSeoy2lZJcwcNRfXHQpBWmWQsRsPYdDPZJUu-QyuPssy+Py5qSt4EyyEAkhUCDNhKF-AAzQ70EkJqiu2tqOt88S926w9UhhLlykWXFHXcTJixsd60hHGxNj2Jag01HVODAKzXI8rzE1+R6U38tN8IQJSuR0OZ0gvHQXHGxBPWmUdppUWwFV0MHJAhqGYcpAh1qwrqDx7ZFajUmVpulEbqlqREsfBTQ0jcPM5P5KmaehshNW1PVvOy7Cka7VHeoYDkyjSPQtAvPMqkRQU1BnX1+UydFkQvCWwEhqWAFEIC8xnFd3ZHntZuTDZG4ob1nGxPTUfXrBnS8nDmEpT2ObxTnXVUALeOrgPanycvKyrqpwWqGqurbWsThXjWd5WesQZYtmBkplCceplIncK0SrXYqnccxxcj5s2OQWP4-s3PzJgiqcCqmr6sa7P2x7vi6AcAvJJ7XEy0dUFMWsFxlnKfn+S5CL3E9Jiuapjv3i7qNx+T-bDskY6zourObpz+6cuZgK0Y5Ua0TqEvXDkJR-YnR0s1xjkIMnDln3uuVsHwOxT1NCjIuR5nCSBUExJi1huRqyooUTYpYnzeyUFkKsy4Tg4FQBAOAgg26Nmga7QKohshSBtNYXGDonQumtPYaQfsSjLBHDefepJICUJZtQ4oMx8wXj6jebGt4JwbC2OiVwig9hh2hLpVu0cOhxjbMBBGeABFPwSA3ERRMlhKWCn9WRVQzZQirMo0BAYNzxn4RJGBh5MRbGXsHawGQFBmLrpUasOQorOAjs0OxaUVrGVMvfaCuiVYEV2KWTYg1yxyA0i6ZYaIPSL3lOkS8wSo6hOWpxCJ8te6WRsnZKMjlnIxNgSNIiiTF6vVSdI9JM1taXlxLzTwqiClBnSqtSJScLIFVvjtKANSXquENqoJwtgyZzRFIUQ4MhqgNACdNTQCpLbWyshMnsjpSwjXRJYHecgcmImsLIz0odNAaGqPiHpDYY6HwTlE+ATiqHPwohYewWQshmGhHrCcJz5Bck5toZwGhMheC8EAA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAIwB2AHTiAHAE45AZjkAmdcoaSArCoA0IAJ6JxDOdJ2SF4gCySHaqZIa2Avm8NpMuAsXJUYKSMLEggHLA8fAJhIgiiOgBssokKTpJqiXK2OsqJaoYm8eJqytKJ9hqq+eJW2h5eGNh4RKRkAGKcLb74tJRgAMbc+ANNviGCEVH8gnEKubK5ympVrrlyhabm0rZ5CtYM4hVq83ININ7NfqTSVDSQZADybGA4E2FTvDOxiGoMDNJMjo9H9lLYGDpKpsEModOJpA5XOIwakwcidOdLj1-LdqLQIGQAIIQAijHx4WDvdhcL4xUBxURqSTw3LzfYKZSSOQZWzQ5S1crMuTAiElRKuTFjFo4u74sgAJTA6FQADcwCMpRBKcxJjTorMxEzkhouftuXCGGpIdCpADmZJxfzJLY5Cp5pLydcSLj7gSqeE9d96Yh+TppKoXfkGKdlHloUyFIDUvMXBzbFl3J4LprWt6AMpgfpDLpQDWesgFovDMlXf2ffU-BBZZKuczWWylRRwvlM6Q2LIMZQ2QdHZQeq65245vqDbgPOuBunCEP88MqPQchwVNLKaHptTSYUuRI6f4npyJcfYm5Ylozobz8ShamRWkGhBmeTSQeJX+JXQ6H88jQnCMiugoELCpypQKJeWa3l6U6er0hazvOajPgGr4NsGH6WhYsHOkycglLCEJ7iclgaIosKSLGGgKFe0o3AA4lg3AABZgCQJb4KQUAAK7oK83CwBQHG4DAIwCSQJAiXxJCCcJODcAu2FBsuH55GGJ7irYHYqHpGzGKYWiWB2nK2Kcv6wWO8E5jibGcdxvH8UJIliQAInAqFDGWtY6h8i7vlkZREbYZg6JuwEmQgfxhnkHbiHYJ7yHYTGIU5XE8TgpZucponSISADuWBRLl+BdGwAncBWAkAEboDwClKSJanTEucS1Bk36DicDCpOYLoKHu-x9tGuhgoozKpBlk5ZS5FX5R50gAGpYJQnAQOxJZkBA-BgNIXQqqgADWh0qhtW3sWAeYlv0hKKe5KntW+YRFGi5RyOKDrZEaUW8pppTguGuwDRFEO-nNjnsdlrlPQVsBrVd228LlZDcSQqDemwlDsQAZtj6DSJdm2o7d91gI9rUvYFL4dYIH0RV9P0Zv9CiA3EwMAmCWgVHYKVwY0yE4oqKqcGAxV1Y1zU1uMdNYQzjbMpYcgQlFxFq66u6xXRALbkyg7-Bz0bQzcYsS1LxIEMttOYfWGldYcCWwQmNiRrU0Jq2GRxJCbHZqC6ahm96FuSwqSqquqtuqQrDudVs4iJhUyZtn9qjQokYKHjk6hSLsZhciH0hh1LACiEDNTHr04ZpJT6bIEXxcKp50YDRT-mGrrJbUgFDp3xfIFxAynbx1PPaJe0HUdOAnedJMozd4+IzXjuIJCLIQqUWjJS4MVFBByS7BzyJq38WTB-ZIs3sPo8VcvHlTzgh3HWdF2L3OD8qZST66upCdxUTLBJOUUHB6GFPpSQXt1CWEGpaOiv4EyD1vmPBGj9MbY2kLjAmRMF5kyXmg7+q8AFJwbjkXQEVsj5FyO3RAiQjjfi5CReYPck7iA8FmHAqAIBwEEAhXMf8la4VEMsWwgJuTTXNGYK0tC4rwkDvsAaSQ-qlAhIPPEkBBFvWEVycoFQhywUHGaHWH04Q7FUNBdc+x2FXwnDiSss5eJyzwFo2ucQIplBWHrcEVhuoFFirCeEuxsj8mWEOFYmZhZ2JvNOXyc4ICuLXggaxCJwG70AiUHQfIzHBKHGYDMJFhTFwWjlPKhDRKJJIRFGQcIFBsiOIHJw8ZIQ7CUGrY+9g9AnmKbDRaZSaaFRKmVNGpYqo1Uqe+FKYZan1PyElbJYjrB6C5CUJQ9DL5ROvN6Ep8MBlI3WvgkZEzGzyAbnkZK-wjan38UzeEzZtBswdADYupdjm4XoTIOwuwHB5HyGkYyRRdBBLosCIE6ZvpnFsVs24KD77lPgEFf+kzxRH32EyFYlpOzQjAZYV2ehTkfI4W4IAA */
|
||||||
context: {
|
context: {
|
||||||
commands: [],
|
commands: [],
|
||||||
selectedCommand: undefined,
|
selectedCommand: undefined,
|
||||||
@ -421,14 +425,6 @@ export const commandBarMachine = setup({
|
|||||||
target: 'Selecting command',
|
target: 'Selecting command',
|
||||||
},
|
},
|
||||||
|
|
||||||
'Find and select command': {
|
|
||||||
target: 'Command selected',
|
|
||||||
actions: [
|
|
||||||
'Find and select command',
|
|
||||||
'Initialize arguments to submit',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
'Add commands': {
|
'Add commands': {
|
||||||
target: 'Closed',
|
target: 'Closed',
|
||||||
|
|
||||||
@ -440,8 +436,6 @@ export const commandBarMachine = setup({
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
||||||
reenter: false,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'Remove commands': {
|
'Remove commands': {
|
||||||
@ -458,10 +452,13 @@ export const commandBarMachine = setup({
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
||||||
reenter: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
always: {
|
||||||
|
target: 'Command selected',
|
||||||
|
guard: 'Has selected command',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
'Selecting command': {
|
'Selecting command': {
|
||||||
@ -478,7 +475,7 @@ export const commandBarMachine = setup({
|
|||||||
{
|
{
|
||||||
target: 'Closed',
|
target: 'Closed',
|
||||||
guard: 'Command has no arguments',
|
guard: 'Command has no arguments',
|
||||||
actions: ['Execute command'],
|
actions: ['Execute command', 'Clear selected command'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: 'Checking Arguments',
|
target: 'Checking Arguments',
|
||||||
@ -548,7 +545,7 @@ export const commandBarMachine = setup({
|
|||||||
on: {
|
on: {
|
||||||
'Submit command': {
|
'Submit command': {
|
||||||
target: 'Closed',
|
target: 'Closed',
|
||||||
actions: ['Execute command'],
|
actions: ['Execute command', 'Clear selected command'],
|
||||||
},
|
},
|
||||||
|
|
||||||
'Add argument': {
|
'Add argument': {
|
||||||
@ -580,7 +577,7 @@ export const commandBarMachine = setup({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: 'Closed',
|
target: 'Closed',
|
||||||
actions: 'Execute command',
|
actions: ['Execute command', 'Clear selected command'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onError: [
|
onError: [
|
||||||
@ -600,6 +597,7 @@ export const commandBarMachine = setup({
|
|||||||
|
|
||||||
Close: {
|
Close: {
|
||||||
target: '.Closed',
|
target: '.Closed',
|
||||||
|
actions: 'Clear selected command',
|
||||||
},
|
},
|
||||||
|
|
||||||
Clear: {
|
Clear: {
|
||||||
@ -607,6 +605,11 @@ export const commandBarMachine = setup({
|
|||||||
reenter: false,
|
reenter: false,
|
||||||
actions: ['Clear argument data'],
|
actions: ['Clear argument data'],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Find and select command': {
|
||||||
|
target: '.Command selected',
|
||||||
|
actions: ['Find and select command', 'Initialize arguments to submit'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -133,6 +133,8 @@ export interface SketchDetails {
|
|||||||
zAxis: [number, number, number]
|
zAxis: [number, number, number]
|
||||||
yAxis: [number, number, number]
|
yAxis: [number, number, number]
|
||||||
origin: [number, number, number]
|
origin: [number, number, number]
|
||||||
|
// face id or plane id, both are strings
|
||||||
|
animateTargetId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SegmentOverlay {
|
export interface SegmentOverlay {
|
||||||
|
|||||||
@ -43,6 +43,7 @@ export const settingsMachine = setup({
|
|||||||
'Execute AST': () => {},
|
'Execute AST': () => {},
|
||||||
toastSuccess: () => {},
|
toastSuccess: () => {},
|
||||||
setClientSideSceneUnits: () => {},
|
setClientSideSceneUnits: () => {},
|
||||||
|
setAllowOrbitInSketchMode: () => {},
|
||||||
persistSettings: () => {},
|
persistSettings: () => {},
|
||||||
resetSettings: assign(({ context, event }) => {
|
resetSettings: assign(({ context, event }) => {
|
||||||
if (!('level' in event)) return {}
|
if (!('level' in event)) return {}
|
||||||
@ -157,6 +158,15 @@ export const settingsMachine = setup({
|
|||||||
actions: ['setSettingAtLevel', 'toastSuccess'],
|
actions: ['setSettingAtLevel', 'toastSuccess'],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'set.app.allowOrbitInSketchMode': {
|
||||||
|
target: 'persisting settings',
|
||||||
|
actions: [
|
||||||
|
'setSettingAtLevel',
|
||||||
|
'toastSuccess',
|
||||||
|
'setAllowOrbitInSketchMode',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
'set.modeling.cameraProjection': {
|
'set.modeling.cameraProjection': {
|
||||||
target: 'persisting settings',
|
target: 'persisting settings',
|
||||||
|
|
||||||
@ -183,6 +193,7 @@ export const settingsMachine = setup({
|
|||||||
'setClientSideSceneUnits',
|
'setClientSideSceneUnits',
|
||||||
'Execute AST',
|
'Execute AST',
|
||||||
'setClientTheme',
|
'setClientTheme',
|
||||||
|
'setAllowOrbitInSketchMode',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -194,6 +205,7 @@ export const settingsMachine = setup({
|
|||||||
'setClientSideSceneUnits',
|
'setClientSideSceneUnits',
|
||||||
'Execute AST',
|
'Execute AST',
|
||||||
'setClientTheme',
|
'setClientTheme',
|
||||||
|
'setAllowOrbitInSketchMode',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -205,6 +205,7 @@ export function OnboardingButtons({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
autoFocus
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={next}
|
onClick={next}
|
||||||
iconStart={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
|
iconStart={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
|
||||||
|
|||||||
@ -815,7 +815,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
|||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new(&ctx.settings)).await {
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", #fn_name, #index),
|
filename: format!("{}{}", #fn_name, #index),
|
||||||
|
|||||||
@ -14,7 +14,10 @@ mod test_examples_someFn {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "someFn", 0usize),
|
filename: format!("{}{}", "someFn", 0usize),
|
||||||
|
|||||||
@ -14,7 +14,10 @@ mod test_examples_someFn {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "someFn", 0usize),
|
filename: format!("{}{}", "someFn", 0usize),
|
||||||
|
|||||||
@ -15,7 +15,10 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "show", 0usize),
|
filename: format!("{}{}", "show", 0usize),
|
||||||
@ -69,7 +72,10 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "show", 1usize),
|
filename: format!("{}{}", "show", 1usize),
|
||||||
|
|||||||
@ -15,7 +15,10 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "show", 0usize),
|
filename: format!("{}{}", "show", 0usize),
|
||||||
|
|||||||
@ -16,7 +16,10 @@ mod test_examples_my_func {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "my_func", 0usize),
|
filename: format!("{}{}", "my_func", 0usize),
|
||||||
@ -70,7 +73,10 @@ mod test_examples_my_func {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "my_func", 1usize),
|
filename: format!("{}{}", "my_func", 1usize),
|
||||||
|
|||||||
@ -16,7 +16,10 @@ mod test_examples_line_to {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "line_to", 0usize),
|
filename: format!("{}{}", "line_to", 0usize),
|
||||||
@ -70,7 +73,10 @@ mod test_examples_line_to {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "line_to", 1usize),
|
filename: format!("{}{}", "line_to", 1usize),
|
||||||
|
|||||||
@ -15,7 +15,10 @@ mod test_examples_min {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "min", 0usize),
|
filename: format!("{}{}", "min", 0usize),
|
||||||
@ -69,7 +72,10 @@ mod test_examples_min {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "min", 1usize),
|
filename: format!("{}{}", "min", 1usize),
|
||||||
|
|||||||
@ -15,7 +15,10 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "show", 0usize),
|
filename: format!("{}{}", "show", 0usize),
|
||||||
|
|||||||
@ -15,7 +15,10 @@ mod test_examples_import {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "import", 0usize),
|
filename: format!("{}{}", "import", 0usize),
|
||||||
|
|||||||
@ -15,7 +15,10 @@ mod test_examples_import {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "import", 0usize),
|
filename: format!("{}{}", "import", 0usize),
|
||||||
|
|||||||
@ -15,7 +15,10 @@ mod test_examples_import {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "import", 0usize),
|
filename: format!("{}{}", "import", 0usize),
|
||||||
|
|||||||
@ -15,7 +15,10 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "show", 0usize),
|
filename: format!("{}{}", "show", 0usize),
|
||||||
|
|||||||
@ -14,7 +14,10 @@ mod test_examples_some_function {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::execution::ContextType::Mock,
|
context_type: crate::execution::ContextType::Mock,
|
||||||
};
|
};
|
||||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
if let Err(e) = ctx
|
||||||
|
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Err(miette::Report::new(crate::errors::Report {
|
return Err(miette::Report::new(crate::errors::Report {
|
||||||
error: e,
|
error: e,
|
||||||
filename: format!("{}{}", "some_function", 0usize),
|
filename: format!("{}{}", "some_function", 0usize),
|
||||||
|
|||||||
@ -164,7 +164,7 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
|
|||||||
};
|
};
|
||||||
|
|
||||||
eprintln!("Executing {test_name}");
|
eprintln!("Executing {test_name}");
|
||||||
let mut exec_state = ExecState::new();
|
let mut exec_state = ExecState::new(&state.settings);
|
||||||
// This is a shitty source range, I don't know what else to use for it though.
|
// This is a shitty source range, I don't know what else to use for it though.
|
||||||
// There's no actual KCL associated with this reset_scene call.
|
// There's no actual KCL associated with this reset_scene call.
|
||||||
if let Err(e) = state
|
if let Err(e) = state
|
||||||
|
|||||||
@ -16,7 +16,7 @@ pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
|
|||||||
let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new(
|
let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new(
|
||||||
crate::conn_mock_core::EngineConnection::new(ref_result).await?,
|
crate::conn_mock_core::EngineConnection::new(ref_result).await?,
|
||||||
)));
|
)));
|
||||||
ctx.run(program.into(), &mut ExecState::new()).await?;
|
ctx.run(program.into(), &mut ExecState::new(&ctx.settings)).await?;
|
||||||
|
|
||||||
let result = result.lock().expect("mutex lock").clone();
|
let result = result.lock().expect("mutex lock").clone();
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|||||||
@ -30,13 +30,16 @@ impl From<KclErrorWithOutputs> for ExecError {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ExecErrorWithState {
|
pub struct ExecErrorWithState {
|
||||||
pub error: ExecError,
|
pub error: ExecError,
|
||||||
pub exec_state: crate::ExecState,
|
pub exec_state: Option<crate::ExecState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecErrorWithState {
|
impl ExecErrorWithState {
|
||||||
#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
|
#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
|
||||||
pub fn new(error: ExecError, exec_state: crate::ExecState) -> Self {
|
pub fn new(error: ExecError, exec_state: crate::ExecState) -> Self {
|
||||||
Self { error, exec_state }
|
Self {
|
||||||
|
error,
|
||||||
|
exec_state: Some(exec_state),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +47,7 @@ impl From<ExecError> for ExecErrorWithState {
|
|||||||
fn from(error: ExecError) -> Self {
|
fn from(error: ExecError) -> Self {
|
||||||
Self {
|
Self {
|
||||||
error,
|
error,
|
||||||
exec_state: Default::default(),
|
exec_state: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +56,7 @@ impl From<ConnectionError> for ExecErrorWithState {
|
|||||||
fn from(error: ConnectionError) -> Self {
|
fn from(error: ConnectionError) -> Self {
|
||||||
Self {
|
Self {
|
||||||
error: error.into(),
|
error: error.into(),
|
||||||
exec_state: Default::default(),
|
exec_state: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -601,10 +601,24 @@ impl TryFrom<NumericSuffix> for UnitLen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
impl From<crate::UnitLength> for UnitLen {
|
||||||
|
fn from(unit: crate::UnitLength) -> Self {
|
||||||
|
match unit {
|
||||||
|
crate::UnitLength::Cm => UnitLen::Cm,
|
||||||
|
crate::UnitLength::Ft => UnitLen::Feet,
|
||||||
|
crate::UnitLength::In => UnitLen::Inches,
|
||||||
|
crate::UnitLength::M => UnitLen::M,
|
||||||
|
crate::UnitLength::Mm => UnitLen::Mm,
|
||||||
|
crate::UnitLength::Yd => UnitLen::Yards,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum UnitAngle {
|
pub enum UnitAngle {
|
||||||
|
#[default]
|
||||||
Degrees,
|
Degrees,
|
||||||
Radians,
|
Radians,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ type Point2D = kcmc::shared::Point2d<f64>;
|
|||||||
type Point3D = kcmc::shared::Point3d<f64>;
|
type Point3D = kcmc::shared::Point3d<f64>;
|
||||||
|
|
||||||
pub use function_param::FunctionParam;
|
pub use function_param::FunctionParam;
|
||||||
pub use kcl_value::{KclObjectFields, KclValue};
|
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
mod annotations;
|
mod annotations;
|
||||||
@ -78,7 +78,7 @@ pub struct GlobalState {
|
|||||||
pub artifact_commands: Vec<ArtifactCommand>,
|
pub artifact_commands: Vec<ArtifactCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ModuleState {
|
pub struct ModuleState {
|
||||||
/// Program variable bindings.
|
/// Program variable bindings.
|
||||||
@ -116,21 +116,15 @@ pub struct ExecOutcome {
|
|||||||
pub artifact_commands: Vec<ArtifactCommand>,
|
pub artifact_commands: Vec<ArtifactCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExecState {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExecState {
|
impl ExecState {
|
||||||
pub fn new() -> Self {
|
pub fn new(exec_settings: &ExecutorSettings) -> Self {
|
||||||
ExecState {
|
ExecState {
|
||||||
global: GlobalState::new(),
|
global: GlobalState::new(),
|
||||||
mod_local: ModuleState::default(),
|
mod_local: ModuleState::new(exec_settings),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self, exec_settings: &ExecutorSettings) {
|
||||||
let mut id_generator = self.global.id_generator.clone();
|
let mut id_generator = self.global.id_generator.clone();
|
||||||
// We do not pop the ids, since we want to keep the same id generator.
|
// 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.
|
// This is for the front end to keep track of the ids.
|
||||||
@ -141,7 +135,7 @@ impl ExecState {
|
|||||||
|
|
||||||
*self = ExecState {
|
*self = ExecState {
|
||||||
global,
|
global,
|
||||||
mod_local: ModuleState::default(),
|
mod_local: ModuleState::new(exec_settings),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +199,14 @@ impl ExecState {
|
|||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn length_unit(&self) -> UnitLen {
|
||||||
|
self.mod_local.settings.default_length_units
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn angle_unit(&self) -> UnitAngle {
|
||||||
|
self.mod_local.settings.default_angle_units
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalState {
|
impl GlobalState {
|
||||||
@ -233,6 +235,23 @@ impl GlobalState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ModuleState {
|
||||||
|
fn new(exec_settings: &ExecutorSettings) -> Self {
|
||||||
|
ModuleState {
|
||||||
|
memory: Default::default(),
|
||||||
|
dynamic_state: Default::default(),
|
||||||
|
pipe_value: Default::default(),
|
||||||
|
module_exports: Default::default(),
|
||||||
|
import_stack: Default::default(),
|
||||||
|
operations: Default::default(),
|
||||||
|
settings: MetaSettings {
|
||||||
|
default_length_units: exec_settings.units.into(),
|
||||||
|
default_angle_units: Default::default(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -241,15 +260,6 @@ pub struct MetaSettings {
|
|||||||
pub default_angle_units: kcl_value::UnitAngle,
|
pub default_angle_units: kcl_value::UnitAngle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MetaSettings {
|
|
||||||
fn default() -> Self {
|
|
||||||
MetaSettings {
|
|
||||||
default_length_units: kcl_value::UnitLen::Mm,
|
|
||||||
default_angle_units: kcl_value::UnitAngle::Degrees,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaSettings {
|
impl MetaSettings {
|
||||||
fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> {
|
fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> {
|
||||||
let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?;
|
let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?;
|
||||||
@ -1712,7 +1722,7 @@ pub struct ExecutorContext {
|
|||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct ExecutorSettings {
|
pub struct ExecutorSettings {
|
||||||
/// The unit to use in modeling dimensions.
|
/// The project-default unit to use in modeling dimensions.
|
||||||
pub units: UnitLength,
|
pub units: UnitLength,
|
||||||
/// Highlight edges of 3D objects?
|
/// Highlight edges of 3D objects?
|
||||||
pub highlight_edges: bool,
|
pub highlight_edges: bool,
|
||||||
@ -2215,7 +2225,7 @@ 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.
|
// Pop the execution state, since we are starting fresh.
|
||||||
exec_state.reset();
|
exec_state.reset(&self.settings);
|
||||||
|
|
||||||
// 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.
|
||||||
@ -2458,7 +2468,7 @@ impl ExecutorContext {
|
|||||||
|
|
||||||
let mut local_state = ModuleState {
|
let mut local_state = ModuleState {
|
||||||
import_stack: exec_state.mod_local.import_stack.clone(),
|
import_stack: exec_state.mod_local.import_stack.clone(),
|
||||||
..Default::default()
|
..ModuleState::new(&self.settings)
|
||||||
};
|
};
|
||||||
local_state.import_stack.push(info.path.clone());
|
local_state.import_stack.push(info.path.clone());
|
||||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||||
@ -2831,7 +2841,7 @@ mod tests {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: ContextType::Mock,
|
context_type: ContextType::Mock,
|
||||||
};
|
};
|
||||||
let mut exec_state = ExecState::default();
|
let mut exec_state = ExecState::new(&ctx.settings);
|
||||||
ctx.run(program.clone().into(), &mut exec_state).await?;
|
ctx.run(program.clone().into(), &mut exec_state).await?;
|
||||||
|
|
||||||
Ok((program, ctx, exec_state))
|
Ok((program, ctx, exec_state))
|
||||||
@ -3303,6 +3313,25 @@ let shape = layer() |> patternTransform(10, transform, %)
|
|||||||
assert_eq!(7.4, mem_get_json(exec_state.memory(), "thing").as_f64().unwrap());
|
assert_eq!(7.4, mem_get_json(exec_state.memory(), "thing").as_f64().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_unit_default() {
|
||||||
|
let ast = r#"const inMm = 25.4 * mm()
|
||||||
|
const inInches = 1.0 * inch()"#;
|
||||||
|
let (_, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||||
|
assert_eq!(25.4, mem_get_json(exec_state.memory(), "inMm").as_f64().unwrap());
|
||||||
|
assert_eq!(25.4, mem_get_json(exec_state.memory(), "inInches").as_f64().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_unit_overriden() {
|
||||||
|
let ast = r#"@settings(defaultLengthUnit = inch)
|
||||||
|
const inMm = 25.4 * mm()
|
||||||
|
const inInches = 1.0 * inch()"#;
|
||||||
|
let (_, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||||
|
assert_eq!(1.0, mem_get_json(exec_state.memory(), "inMm").as_f64().unwrap().round());
|
||||||
|
assert_eq!(1.0, mem_get_json(exec_state.memory(), "inInches").as_f64().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_zero_param_fn() {
|
async fn test_zero_param_fn() {
|
||||||
let ast = r#"const sigmaAllow = 35000 // psi
|
let ast = r#"const sigmaAllow = 35000 // psi
|
||||||
@ -4046,7 +4075,7 @@ shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let old_program = crate::Program::parse_no_errs(code).unwrap();
|
let old_program = crate::Program::parse_no_errs(code).unwrap();
|
||||||
// Execute the program.
|
// Execute the program.
|
||||||
let mut exec_state = Default::default();
|
let mut exec_state = ExecState::new(&ctx.settings);
|
||||||
let cache_info = crate::CacheInformation {
|
let cache_info = crate::CacheInformation {
|
||||||
old: None,
|
old: None,
|
||||||
new_ast: old_program.ast.clone(),
|
new_ast: old_program.ast.clone(),
|
||||||
|
|||||||
@ -49,7 +49,7 @@ use crate::{
|
|||||||
token::TokenStream,
|
token::TokenStream,
|
||||||
PIPE_OPERATOR,
|
PIPE_OPERATOR,
|
||||||
},
|
},
|
||||||
CacheInformation, ModuleId, OldAstState, Program, SourceRange,
|
CacheInformation, ExecState, ModuleId, OldAstState, Program, SourceRange,
|
||||||
};
|
};
|
||||||
const SEMANTIC_TOKEN_TYPES: [SemanticTokenType; 10] = [
|
const SEMANTIC_TOKEN_TYPES: [SemanticTokenType; 10] = [
|
||||||
SemanticTokenType::NUMBER,
|
SemanticTokenType::NUMBER,
|
||||||
@ -693,7 +693,7 @@ impl Backend {
|
|||||||
let mut exec_state = if let Some(last_successful_ast_state) = last_successful_ast_state.clone() {
|
let mut exec_state = if let Some(last_successful_ast_state) = last_successful_ast_state.clone() {
|
||||||
last_successful_ast_state.exec_state
|
last_successful_ast_state.exec_state
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
ExecState::new(&executor_ctx.settings)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = executor_ctx
|
if let Err(err) = executor_ctx
|
||||||
|
|||||||
@ -121,6 +121,9 @@ pub struct AppSettings {
|
|||||||
/// When the user is idle, and this is true, the stream will be torn down.
|
/// When the user is idle, and this is true, the stream will be torn down.
|
||||||
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
|
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
|
||||||
stream_idle_mode: bool,
|
stream_idle_mode: bool,
|
||||||
|
/// When the user is idle, and this is true, the stream will be torn down.
|
||||||
|
#[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")]
|
||||||
|
allow_orbit_in_sketch_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: When we remove backwards compatibility with the old settings file, we can remove this.
|
// TODO: When we remove backwards compatibility with the old settings file, we can remove this.
|
||||||
@ -586,6 +589,7 @@ textWrapping = true
|
|||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: false,
|
||||||
|
allow_orbit_in_sketch_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::In,
|
base_unit: UnitLength::In,
|
||||||
@ -647,6 +651,7 @@ includeSettings = false
|
|||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: false,
|
||||||
|
allow_orbit_in_sketch_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::Yd,
|
base_unit: UnitLength::Yd,
|
||||||
@ -713,6 +718,7 @@ defaultProjectName = "projects-$nnn"
|
|||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: false,
|
||||||
|
allow_orbit_in_sketch_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::Yd,
|
base_unit: UnitLength::Yd,
|
||||||
@ -791,6 +797,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
|
|||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: false,
|
||||||
|
allow_orbit_in_sketch_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::Mm,
|
base_unit: UnitLength::Mm,
|
||||||
|
|||||||
@ -124,6 +124,7 @@ includeSettings = false
|
|||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: false,
|
||||||
|
allow_orbit_in_sketch_mode: false,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::Yd,
|
base_unit: UnitLength::Yd,
|
||||||
|
|||||||
@ -96,25 +96,6 @@ impl Args {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) async fn new_test_args() -> Result<Self> {
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
args: Vec::new(),
|
|
||||||
kw_args: Default::default(),
|
|
||||||
source_range: SourceRange::default(),
|
|
||||||
ctx: ExecutorContext {
|
|
||||||
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
|
|
||||||
fs: Arc::new(crate::fs::FileManager::new()),
|
|
||||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
|
||||||
settings: Default::default(),
|
|
||||||
context_type: crate::execution::ContextType::Mock,
|
|
||||||
},
|
|
||||||
pipe_value: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a keyword argument. If not set, returns None.
|
/// Get a keyword argument. If not set, returns None.
|
||||||
pub(crate) fn get_kw_arg_opt<'a, T>(&'a self, label: &str) -> Option<T>
|
pub(crate) fn get_kw_arg_opt<'a, T>(&'a self, label: &str) -> Option<T>
|
||||||
where
|
where
|
||||||
|
|||||||
@ -50,7 +50,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// helixPath = helix({
|
/// helixPath = helix({
|
||||||
/// angleStart = 0,
|
/// angleStart = 0,
|
||||||
/// ccw = true,
|
/// ccw = true,
|
||||||
/// revolutions = 16,
|
/// revolutions = 5,
|
||||||
/// length = 10,
|
/// length = 10,
|
||||||
/// radius = 5,
|
/// radius = 5,
|
||||||
/// axis = 'Z',
|
/// axis = 'Z',
|
||||||
@ -59,8 +59,8 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
///
|
///
|
||||||
/// // Create a spring by sweeping around the helix path.
|
/// // Create a spring by sweeping around the helix path.
|
||||||
/// springSketch = startSketchOn('YZ')
|
/// springSketch = startSketchOn('YZ')
|
||||||
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
/// |> circle({ center = [0, 0], radius = 0.5 }, %)
|
||||||
/// //|> sweep({ path = helixPath }, %)
|
/// |> sweep({ path = helixPath }, %)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
@ -72,7 +72,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// helixPath = helix({
|
/// helixPath = helix({
|
||||||
/// angleStart = 0,
|
/// angleStart = 0,
|
||||||
/// ccw = true,
|
/// ccw = true,
|
||||||
/// revolutions = 16,
|
/// revolutions = 5,
|
||||||
/// length = 10,
|
/// length = 10,
|
||||||
/// radius = 5,
|
/// radius = 5,
|
||||||
/// axis = edge001,
|
/// axis = edge001,
|
||||||
@ -80,8 +80,8 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
///
|
///
|
||||||
/// // Create a spring by sweeping around the helix path.
|
/// // Create a spring by sweeping around the helix path.
|
||||||
/// springSketch = startSketchOn('XY')
|
/// springSketch = startSketchOn('XY')
|
||||||
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
/// |> circle({ center = [0, 0], radius = 0.5 }, %)
|
||||||
/// //|> sweep({ path = helixPath }, %)
|
/// |> sweep({ path = helixPath }, %)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
@ -89,7 +89,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// helixPath = helix({
|
/// helixPath = helix({
|
||||||
/// angleStart = 0,
|
/// angleStart = 0,
|
||||||
/// ccw = true,
|
/// ccw = true,
|
||||||
/// revolutions = 16,
|
/// revolutions = 5,
|
||||||
/// length = 10,
|
/// length = 10,
|
||||||
/// radius = 5,
|
/// radius = 5,
|
||||||
/// axis = {
|
/// axis = {
|
||||||
@ -103,7 +103,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// // Create a spring by sweeping around the helix path.
|
/// // Create a spring by sweeping around the helix path.
|
||||||
/// springSketch = startSketchOn('XY')
|
/// springSketch = startSketchOn('XY')
|
||||||
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
||||||
/// //|> sweep({ path = helixPath }, %)
|
/// |> sweep({ path = helixPath }, %)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "helix",
|
name = "helix",
|
||||||
@ -137,7 +137,7 @@ async fn inner_helix(data: HelixData, exec_state: &mut ExecState, args: Args) ->
|
|||||||
};
|
};
|
||||||
|
|
||||||
args.batch_modeling_cmd(
|
args.batch_modeling_cmd(
|
||||||
exec_state.next_uuid(),
|
id,
|
||||||
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
|
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
|
||||||
radius: data.radius,
|
radius: data.radius,
|
||||||
is_clockwise: !data.ccw,
|
is_clockwise: !data.ccw,
|
||||||
@ -154,7 +154,7 @@ async fn inner_helix(data: HelixData, exec_state: &mut ExecState, args: Args) ->
|
|||||||
let edge_id = edge.get_engine_id(exec_state, &args)?;
|
let edge_id = edge.get_engine_id(exec_state, &args)?;
|
||||||
|
|
||||||
args.batch_modeling_cmd(
|
args.batch_modeling_cmd(
|
||||||
exec_state.next_uuid(),
|
id,
|
||||||
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
|
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
|
||||||
radius: data.radius,
|
radius: data.radius,
|
||||||
is_clockwise: !data.ccw,
|
is_clockwise: !data.ccw,
|
||||||
|
|||||||
@ -94,7 +94,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// helixPath = helix({
|
/// helixPath = helix({
|
||||||
/// angleStart = 0,
|
/// angleStart = 0,
|
||||||
/// ccw = true,
|
/// ccw = true,
|
||||||
/// revolutions = 16,
|
/// revolutions = 4,
|
||||||
/// length = 10,
|
/// length = 10,
|
||||||
/// radius = 5,
|
/// radius = 5,
|
||||||
/// axis = 'Z',
|
/// axis = 'Z',
|
||||||
@ -104,7 +104,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// // Create a spring by sweeping around the helix path.
|
/// // Create a spring by sweeping around the helix path.
|
||||||
/// springSketch = startSketchOn('YZ')
|
/// springSketch = startSketchOn('YZ')
|
||||||
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
/// |> circle({ center = [0, 0], radius = 1 }, %)
|
||||||
/// //|> sweep({ path = helixPath }, %)
|
/// |> sweep({ path = helixPath }, %)
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "sweep",
|
name = "sweep",
|
||||||
|
|||||||
@ -5,14 +5,13 @@ use derive_docs::stdlib;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::KclError,
|
errors::KclError,
|
||||||
execution::{ExecState, KclValue},
|
execution::{ExecState, KclValue, UnitLen},
|
||||||
settings::types::UnitLength,
|
|
||||||
std::Args,
|
std::Args,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Millimeters conversion factor for current projects units.
|
/// Millimeters conversion factor for current projects units.
|
||||||
pub async fn mm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn mm(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let result = inner_mm(&args)?;
|
let result = inner_mm(exec_state)?;
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64(result))
|
Ok(args.make_user_val_from_f64(result))
|
||||||
}
|
}
|
||||||
@ -40,20 +39,20 @@ pub async fn mm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
name = "mm",
|
name = "mm",
|
||||||
tags = ["units"],
|
tags = ["units"],
|
||||||
}]
|
}]
|
||||||
fn inner_mm(args: &Args) -> Result<f64, KclError> {
|
fn inner_mm(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||||
match args.ctx.settings.units {
|
match exec_state.length_unit() {
|
||||||
UnitLength::Mm => Ok(1.0),
|
UnitLen::Mm => Ok(1.0),
|
||||||
UnitLength::In => Ok(measurements::Length::from_millimeters(1.0).as_inches()),
|
UnitLen::Inches => Ok(measurements::Length::from_millimeters(1.0).as_inches()),
|
||||||
UnitLength::Ft => Ok(measurements::Length::from_millimeters(1.0).as_feet()),
|
UnitLen::Feet => Ok(measurements::Length::from_millimeters(1.0).as_feet()),
|
||||||
UnitLength::M => Ok(measurements::Length::from_millimeters(1.0).as_meters()),
|
UnitLen::M => Ok(measurements::Length::from_millimeters(1.0).as_meters()),
|
||||||
UnitLength::Cm => Ok(measurements::Length::from_millimeters(1.0).as_centimeters()),
|
UnitLen::Cm => Ok(measurements::Length::from_millimeters(1.0).as_centimeters()),
|
||||||
UnitLength::Yd => Ok(measurements::Length::from_millimeters(1.0).as_yards()),
|
UnitLen::Yards => Ok(measurements::Length::from_millimeters(1.0).as_yards()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inches conversion factor for current projects units.
|
/// Inches conversion factor for current projects units.
|
||||||
pub async fn inch(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn inch(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let result = inner_inch(&args)?;
|
let result = inner_inch(exec_state)?;
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64(result))
|
Ok(args.make_user_val_from_f64(result))
|
||||||
}
|
}
|
||||||
@ -81,20 +80,20 @@ pub async fn inch(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
name = "inch",
|
name = "inch",
|
||||||
tags = ["units"],
|
tags = ["units"],
|
||||||
}]
|
}]
|
||||||
fn inner_inch(args: &Args) -> Result<f64, KclError> {
|
fn inner_inch(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||||
match args.ctx.settings.units {
|
match exec_state.length_unit() {
|
||||||
UnitLength::Mm => Ok(measurements::Length::from_inches(1.0).as_millimeters()),
|
UnitLen::Mm => Ok(measurements::Length::from_inches(1.0).as_millimeters()),
|
||||||
UnitLength::In => Ok(1.0),
|
UnitLen::Inches => Ok(1.0),
|
||||||
UnitLength::Ft => Ok(measurements::Length::from_inches(1.0).as_feet()),
|
UnitLen::Feet => Ok(measurements::Length::from_inches(1.0).as_feet()),
|
||||||
UnitLength::M => Ok(measurements::Length::from_inches(1.0).as_meters()),
|
UnitLen::M => Ok(measurements::Length::from_inches(1.0).as_meters()),
|
||||||
UnitLength::Cm => Ok(measurements::Length::from_inches(1.0).as_centimeters()),
|
UnitLen::Cm => Ok(measurements::Length::from_inches(1.0).as_centimeters()),
|
||||||
UnitLength::Yd => Ok(measurements::Length::from_inches(1.0).as_yards()),
|
UnitLen::Yards => Ok(measurements::Length::from_inches(1.0).as_yards()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Feet conversion factor for current projects units.
|
/// Feet conversion factor for current projects units.
|
||||||
pub async fn ft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn ft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let result = inner_ft(&args)?;
|
let result = inner_ft(exec_state)?;
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64(result))
|
Ok(args.make_user_val_from_f64(result))
|
||||||
}
|
}
|
||||||
@ -123,20 +122,20 @@ pub async fn ft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
name = "ft",
|
name = "ft",
|
||||||
tags = ["units"],
|
tags = ["units"],
|
||||||
}]
|
}]
|
||||||
fn inner_ft(args: &Args) -> Result<f64, KclError> {
|
fn inner_ft(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||||
match args.ctx.settings.units {
|
match exec_state.length_unit() {
|
||||||
UnitLength::Mm => Ok(measurements::Length::from_feet(1.0).as_millimeters()),
|
UnitLen::Mm => Ok(measurements::Length::from_feet(1.0).as_millimeters()),
|
||||||
UnitLength::In => Ok(measurements::Length::from_feet(1.0).as_inches()),
|
UnitLen::Inches => Ok(measurements::Length::from_feet(1.0).as_inches()),
|
||||||
UnitLength::Ft => Ok(1.0),
|
UnitLen::Feet => Ok(1.0),
|
||||||
UnitLength::M => Ok(measurements::Length::from_feet(1.0).as_meters()),
|
UnitLen::M => Ok(measurements::Length::from_feet(1.0).as_meters()),
|
||||||
UnitLength::Cm => Ok(measurements::Length::from_feet(1.0).as_centimeters()),
|
UnitLen::Cm => Ok(measurements::Length::from_feet(1.0).as_centimeters()),
|
||||||
UnitLength::Yd => Ok(measurements::Length::from_feet(1.0).as_yards()),
|
UnitLen::Yards => Ok(measurements::Length::from_feet(1.0).as_yards()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Meters conversion factor for current projects units.
|
/// Meters conversion factor for current projects units.
|
||||||
pub async fn m(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn m(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let result = inner_m(&args)?;
|
let result = inner_m(exec_state)?;
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64(result))
|
Ok(args.make_user_val_from_f64(result))
|
||||||
}
|
}
|
||||||
@ -165,20 +164,20 @@ pub async fn m(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclE
|
|||||||
name = "m",
|
name = "m",
|
||||||
tags = ["units"],
|
tags = ["units"],
|
||||||
}]
|
}]
|
||||||
fn inner_m(args: &Args) -> Result<f64, KclError> {
|
fn inner_m(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||||
match args.ctx.settings.units {
|
match exec_state.length_unit() {
|
||||||
UnitLength::Mm => Ok(measurements::Length::from_meters(1.0).as_millimeters()),
|
UnitLen::Mm => Ok(measurements::Length::from_meters(1.0).as_millimeters()),
|
||||||
UnitLength::In => Ok(measurements::Length::from_meters(1.0).as_inches()),
|
UnitLen::Inches => Ok(measurements::Length::from_meters(1.0).as_inches()),
|
||||||
UnitLength::Ft => Ok(measurements::Length::from_meters(1.0).as_feet()),
|
UnitLen::Feet => Ok(measurements::Length::from_meters(1.0).as_feet()),
|
||||||
UnitLength::M => Ok(1.0),
|
UnitLen::M => Ok(1.0),
|
||||||
UnitLength::Cm => Ok(measurements::Length::from_meters(1.0).as_centimeters()),
|
UnitLen::Cm => Ok(measurements::Length::from_meters(1.0).as_centimeters()),
|
||||||
UnitLength::Yd => Ok(measurements::Length::from_meters(1.0).as_yards()),
|
UnitLen::Yards => Ok(measurements::Length::from_meters(1.0).as_yards()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Centimeters conversion factor for current projects units.
|
/// Centimeters conversion factor for current projects units.
|
||||||
pub async fn cm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn cm(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let result = inner_cm(&args)?;
|
let result = inner_cm(exec_state)?;
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64(result))
|
Ok(args.make_user_val_from_f64(result))
|
||||||
}
|
}
|
||||||
@ -207,20 +206,20 @@ pub async fn cm(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
name = "cm",
|
name = "cm",
|
||||||
tags = ["units"],
|
tags = ["units"],
|
||||||
}]
|
}]
|
||||||
fn inner_cm(args: &Args) -> Result<f64, KclError> {
|
fn inner_cm(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||||
match args.ctx.settings.units {
|
match exec_state.length_unit() {
|
||||||
UnitLength::Mm => Ok(measurements::Length::from_centimeters(1.0).as_millimeters()),
|
UnitLen::Mm => Ok(measurements::Length::from_centimeters(1.0).as_millimeters()),
|
||||||
UnitLength::In => Ok(measurements::Length::from_centimeters(1.0).as_inches()),
|
UnitLen::Inches => Ok(measurements::Length::from_centimeters(1.0).as_inches()),
|
||||||
UnitLength::Ft => Ok(measurements::Length::from_centimeters(1.0).as_feet()),
|
UnitLen::Feet => Ok(measurements::Length::from_centimeters(1.0).as_feet()),
|
||||||
UnitLength::M => Ok(measurements::Length::from_centimeters(1.0).as_meters()),
|
UnitLen::M => Ok(measurements::Length::from_centimeters(1.0).as_meters()),
|
||||||
UnitLength::Cm => Ok(1.0),
|
UnitLen::Cm => Ok(1.0),
|
||||||
UnitLength::Yd => Ok(measurements::Length::from_centimeters(1.0).as_yards()),
|
UnitLen::Yards => Ok(measurements::Length::from_centimeters(1.0).as_yards()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Yards conversion factor for current projects units.
|
/// Yards conversion factor for current projects units.
|
||||||
pub async fn yd(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn yd(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let result = inner_yd(&args)?;
|
let result = inner_yd(exec_state)?;
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64(result))
|
Ok(args.make_user_val_from_f64(result))
|
||||||
}
|
}
|
||||||
@ -249,92 +248,13 @@ pub async fn yd(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
name = "yd",
|
name = "yd",
|
||||||
tags = ["units"],
|
tags = ["units"],
|
||||||
}]
|
}]
|
||||||
fn inner_yd(args: &Args) -> Result<f64, KclError> {
|
fn inner_yd(exec_state: &ExecState) -> Result<f64, KclError> {
|
||||||
match args.ctx.settings.units {
|
match exec_state.length_unit() {
|
||||||
UnitLength::Mm => Ok(measurements::Length::from_yards(1.0).as_millimeters()),
|
UnitLen::Mm => Ok(measurements::Length::from_yards(1.0).as_millimeters()),
|
||||||
UnitLength::In => Ok(measurements::Length::from_yards(1.0).as_inches()),
|
UnitLen::Inches => Ok(measurements::Length::from_yards(1.0).as_inches()),
|
||||||
UnitLength::Ft => Ok(measurements::Length::from_yards(1.0).as_feet()),
|
UnitLen::Feet => Ok(measurements::Length::from_yards(1.0).as_feet()),
|
||||||
UnitLength::M => Ok(measurements::Length::from_yards(1.0).as_meters()),
|
UnitLen::M => Ok(measurements::Length::from_yards(1.0).as_meters()),
|
||||||
UnitLength::Cm => Ok(measurements::Length::from_yards(1.0).as_centimeters()),
|
UnitLen::Cm => Ok(measurements::Length::from_yards(1.0).as_centimeters()),
|
||||||
UnitLength::Yd => Ok(1.0),
|
UnitLen::Yards => Ok(1.0),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
|
||||||
async fn test_units_inner_mm() {
|
|
||||||
let mut args = Args::new_test_args().await.unwrap();
|
|
||||||
args.ctx.settings.units = UnitLength::Mm;
|
|
||||||
let result = inner_mm(&args).unwrap();
|
|
||||||
assert_eq!(result, 1.0);
|
|
||||||
|
|
||||||
args.ctx.settings.units = UnitLength::In;
|
|
||||||
let result = inner_mm(&args).unwrap();
|
|
||||||
assert_eq!(result, 1.0 / 25.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
|
||||||
async fn test_units_inner_inch() {
|
|
||||||
let mut args = Args::new_test_args().await.unwrap();
|
|
||||||
args.ctx.settings.units = UnitLength::In;
|
|
||||||
let result = inner_inch(&args).unwrap();
|
|
||||||
assert_eq!(result, 1.0);
|
|
||||||
|
|
||||||
args.ctx.settings.units = UnitLength::Mm;
|
|
||||||
let result = inner_inch(&args).unwrap();
|
|
||||||
assert_eq!(result, 25.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
|
||||||
async fn test_units_inner_ft() {
|
|
||||||
let mut args = Args::new_test_args().await.unwrap();
|
|
||||||
args.ctx.settings.units = UnitLength::Ft;
|
|
||||||
let result = inner_ft(&args).unwrap();
|
|
||||||
assert_eq!(result, 1.0);
|
|
||||||
|
|
||||||
args.ctx.settings.units = UnitLength::Mm;
|
|
||||||
let result = inner_ft(&args).unwrap();
|
|
||||||
assert_eq!(result, 304.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
|
||||||
async fn test_units_inner_m() {
|
|
||||||
let mut args = Args::new_test_args().await.unwrap();
|
|
||||||
args.ctx.settings.units = UnitLength::M;
|
|
||||||
let result = inner_m(&args).unwrap();
|
|
||||||
assert_eq!(result, 1.0);
|
|
||||||
|
|
||||||
args.ctx.settings.units = UnitLength::Mm;
|
|
||||||
let result = inner_m(&args).unwrap();
|
|
||||||
assert_eq!(result, 1000.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
|
||||||
async fn test_units_inner_cm() {
|
|
||||||
let mut args = Args::new_test_args().await.unwrap();
|
|
||||||
args.ctx.settings.units = UnitLength::Cm;
|
|
||||||
let result = inner_cm(&args).unwrap();
|
|
||||||
assert_eq!(result, 1.0);
|
|
||||||
|
|
||||||
args.ctx.settings.units = UnitLength::Mm;
|
|
||||||
let result = inner_cm(&args).unwrap();
|
|
||||||
assert_eq!(result, 10.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
|
||||||
async fn test_units_inner_yd() {
|
|
||||||
let mut args = Args::new_test_args().await.unwrap();
|
|
||||||
args.ctx.settings.units = UnitLength::Yd;
|
|
||||||
let result = inner_yd(&args).unwrap();
|
|
||||||
assert_eq!(result, 1.0);
|
|
||||||
|
|
||||||
args.ctx.settings.units = UnitLength::Mm;
|
|
||||||
let result = inner_yd(&args).unwrap();
|
|
||||||
assert_eq!(result, 914.4);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use crate::{
|
|||||||
errors::ExecErrorWithState,
|
errors::ExecErrorWithState,
|
||||||
execution::{new_zoo_client, ArtifactCommand, ExecutorContext, ExecutorSettings, Operation, ProgramMemory},
|
execution::{new_zoo_client, ArtifactCommand, ExecutorContext, ExecutorSettings, Operation, ProgramMemory},
|
||||||
settings::types::UnitLength,
|
settings::types::UnitLength,
|
||||||
ConnectionError, ExecError, KclErrorWithOutputs, Program,
|
ConnectionError, ExecError, ExecState, KclErrorWithOutputs, Program,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
@ -72,7 +72,7 @@ async fn do_execute_and_snapshot(
|
|||||||
ctx: &ExecutorContext,
|
ctx: &ExecutorContext,
|
||||||
program: Program,
|
program: Program,
|
||||||
) -> Result<(crate::execution::ExecState, image::DynamicImage), ExecErrorWithState> {
|
) -> Result<(crate::execution::ExecState, image::DynamicImage), ExecErrorWithState> {
|
||||||
let mut exec_state = Default::default();
|
let mut exec_state = ExecState::new(&ctx.settings);
|
||||||
let snapshot_png_bytes = ctx
|
let snapshot_png_bytes = ctx
|
||||||
.execute_and_prepare_snapshot(&program, &mut exec_state)
|
.execute_and_prepare_snapshot(&program, &mut exec_state)
|
||||||
.await
|
.await
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 168 KiB |
@ -80,7 +80,7 @@ pub async fn execute(
|
|||||||
kcl_lib::ExecutorContext::new(engine_manager, fs_manager, settings.into()).await?
|
kcl_lib::ExecutorContext::new(engine_manager, fs_manager, settings.into()).await?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut exec_state = ExecState::default();
|
let mut exec_state = ExecState::new(&ctx.settings);
|
||||||
let mut old_ast_memory = None;
|
let mut old_ast_memory = None;
|
||||||
|
|
||||||
// Populate from the old exec state if it exists.
|
// Populate from the old exec state if it exists.
|
||||||
|
|||||||
@ -18,7 +18,7 @@ async fn cache_test(
|
|||||||
.ok_or_else(|| anyhow::anyhow!("No variations provided for test '{}'", test_name))?;
|
.ok_or_else(|| anyhow::anyhow!("No variations provided for test '{}'", test_name))?;
|
||||||
|
|
||||||
let mut ctx = kcl_lib::ExecutorContext::new_with_client(first.settings.clone(), None, None).await?;
|
let mut ctx = kcl_lib::ExecutorContext::new_with_client(first.settings.clone(), None, None).await?;
|
||||||
let mut exec_state = kcl_lib::ExecState::default();
|
let mut exec_state = kcl_lib::ExecState::new(&ctx.settings);
|
||||||
|
|
||||||
let mut old_ast_state = None;
|
let mut old_ast_state = None;
|
||||||
let mut img_results = Vec::new();
|
let mut img_results = Vec::new();
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use pretty_assertions::assert_eq;
|
|||||||
async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, ModuleId, uuid::Uuid)> {
|
async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, ModuleId, uuid::Uuid)> {
|
||||||
let program = Program::parse_no_errs(code)?;
|
let program = Program::parse_no_errs(code)?;
|
||||||
let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default()).await?;
|
let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default()).await?;
|
||||||
let mut exec_state = ExecState::default();
|
let mut exec_state = ExecState::new(&ctx.settings);
|
||||||
ctx.run(program.clone().into(), &mut exec_state).await?;
|
ctx.run(program.clone().into(), &mut exec_state).await?;
|
||||||
|
|
||||||
// We need to get the sketch ID.
|
// We need to get the sketch ID.
|
||||||
|
|||||||