Compare commits
52 Commits
nightly-v2
...
kurt-delet
| Author | SHA1 | Date | |
|---|---|---|---|
| d70ebca165 | |||
| d8a9abba69 | |||
| 0fd18c14ef | |||
| 36d4830c34 | |||
| 4ce6054e64 | |||
| ced49f8ddc | |||
| e063622139 | |||
| 42178fa649 | |||
| 4bb23bc917 | |||
| 72272d5d98 | |||
| 5ef0a1e75f | |||
| d8dc49b08a | |||
| 87eabef450 | |||
| 40e4f2236f | |||
| 663076f790 | |||
| f2c76b0509 | |||
| 481bef859a | |||
| 1a67d344ee | |||
| 774e3efcb7 | |||
| 4ec44690bf | |||
| d2f0865f95 | |||
| 84d17454e9 | |||
| 5a5138a703 | |||
| 33468c4c96 | |||
| b3467bbe5a | |||
| 90086488b5 | |||
| 32e8975799 | |||
| 648616c667 | |||
| 482487cf57 | |||
| 5fe3023be9 | |||
| 30397ba7ab | |||
| 3344208c63 | |||
| fcf3272ad2 | |||
| d3e4b123d0 | |||
| 2bb548c000 | |||
| b09c240e36 | |||
| 6c9d14af93 | |||
| 0642e49189 | |||
| 6add1d73ad | |||
| 68c89746c7 | |||
| 9f323c207c | |||
| 7197b6c85d | |||
| 913f2641c3 | |||
| e9086c54ba | |||
| 9f93346dc6 | |||
| 1b9f5f20f5 | |||
| 3865637c61 | |||
| 2c40e8a97c | |||
| c696f0837a | |||
| 30edf2ad56 | |||
| e60cabb193 | |||
| 1e9cf6f256 |
@ -9,7 +9,7 @@ Repeat a 2-dimensional sketch along some dimension, with a dynamic amount
|
||||
of distance between each repetition, some specified number of times.
|
||||
|
||||
```js
|
||||
patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?: bool) -> [Sketch]
|
||||
patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet) -> [Sketch]
|
||||
```
|
||||
|
||||
|
||||
@ -19,7 +19,6 @@ patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?:
|
||||
|----------|------|-------------|----------|
|
||||
| `data` | [`LinearPattern2dData`](/docs/kcl/types/LinearPattern2dData) | Data for a linear pattern on a 2D sketch. | Yes |
|
||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||
| `use_original` | `bool` | | No |
|
||||
|
||||
### Returns
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ Repeat a 3-dimensional solid along a linear path, with a dynamic amount
|
||||
of distance between each repetition, some specified number of times.
|
||||
|
||||
```js
|
||||
patternLinear3d(data: LinearPattern3dData, solid_set: SolidSet, use_original?: bool) -> [Solid]
|
||||
patternLinear3d(data: LinearPattern3dData, solid_set: SolidSet) -> [Solid]
|
||||
```
|
||||
|
||||
|
||||
@ -19,7 +19,6 @@ patternLinear3d(data: LinearPattern3dData, solid_set: SolidSet, use_original?: b
|
||||
|----------|------|-------------|----------|
|
||||
| `data` | [`LinearPattern3dData`](/docs/kcl/types/LinearPattern3dData) | Data for a linear pattern on a 3D model. | Yes |
|
||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes |
|
||||
| `use_original` | `bool` | | No |
|
||||
|
||||
### Returns
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ The transform function returns a transform object. All properties of the object
|
||||
- `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
|
||||
|
||||
```js
|
||||
patternTransform(total_instances: integer, transform_function: FunctionParam, solid_set: SolidSet, use_original?: bool) -> [Solid]
|
||||
patternTransform(total_instances: integer, transform_function: FunctionParam, solid_set: SolidSet) -> [Solid]
|
||||
```
|
||||
|
||||
|
||||
@ -46,7 +46,6 @@ patternTransform(total_instances: integer, transform_function: FunctionParam, so
|
||||
| `total_instances` | `integer` | | Yes |
|
||||
| `transform_function` | `FunctionParam` | | Yes |
|
||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes |
|
||||
| `use_original` | `bool` | | No |
|
||||
|
||||
### Returns
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids.
|
||||
|
||||
|
||||
```js
|
||||
patternTransform2d(total_instances: integer, transform_function: FunctionParam, solid_set: SketchSet, use_original?: bool) -> [Sketch]
|
||||
patternTransform2d(total_instances: integer, transform_function: FunctionParam, solid_set: SketchSet) -> [Sketch]
|
||||
```
|
||||
|
||||
|
||||
@ -20,7 +20,6 @@ patternTransform2d(total_instances: integer, transform_function: FunctionParam,
|
||||
| `total_instances` | `integer` | | Yes |
|
||||
| `transform_function` | `FunctionParam` | | Yes |
|
||||
| `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||
| `use_original` | `bool` | | No |
|
||||
|
||||
### Returns
|
||||
|
||||
|
||||
13669
docs/kcl/std.json
@ -20,6 +20,5 @@ Data for a circular pattern on a 2D sketch.
|
||||
| `center` |`[number, number]`| The center about which to make the pattern. This is a 2D vector. | No |
|
||||
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |
|
||||
| `rotateDuplicates` |`boolean`| Whether or not to rotate the duplicates as they are copied. | No |
|
||||
| `useOriginal` |`boolean`| If the target being patterned is itself a pattern, then, should you use the original solid, or the pattern? | No |
|
||||
|
||||
|
||||
|
||||
@ -21,6 +21,5 @@ Data for a circular pattern on a 3D model.
|
||||
| `center` |`[number, number, number]`| The center about which to make the pattern. This is a 3D vector. | No |
|
||||
| `arcDegrees` |`number`| The arc angle (in degrees) to place the repetitions. Must be greater than 0. | No |
|
||||
| `rotateDuplicates` |`boolean`| Whether or not to rotate the duplicates as they are copied. | No |
|
||||
| `useOriginal` |`boolean`| If the target being patterned is itself a pattern, then, should you use the original solid, or the pattern? | No |
|
||||
|
||||
|
||||
|
||||
@ -22,7 +22,6 @@ A sketch is a collection of paths.
|
||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
|
||||
| `originalId` |`string`| | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch is a collection of paths. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||
|
||||
|
||||
@ -31,7 +31,6 @@ A sketch is a collection of paths.
|
||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
|
||||
| `originalId` |`string`| | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch or a group of sketches. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||
|
||||
|
||||
@ -54,23 +54,26 @@ async function doBasicSketch(
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await expect(u.codeLocator).toContainText(
|
||||
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||
)
|
||||
}
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
}
|
||||
await page.waitForTimeout(500)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||
} else {
|
||||
@ -79,8 +82,10 @@ async function doBasicSketch(
|
||||
await page.waitForTimeout(200)
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||
@ -137,8 +142,10 @@ async function doBasicSketch(
|
||||
|
||||
// Open the code pane.
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
await expect(u.codeLocator)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %, $seg01)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(-segLen(seg01), %)`)
|
||||
|
||||
@ -41,8 +41,7 @@ test.describe(
|
||||
},
|
||||
}
|
||||
|
||||
const code = `sketch001 = startSketchOn('${plane}')
|
||||
|> startProfileAt([0.9, -1.22], %)`
|
||||
const code = `sketch001 = startSketchOn('${plane}')profile001 = startProfileAt([0.9, -1.22], sketch001)`
|
||||
|
||||
await u.openDebugPanel()
|
||||
|
||||
|
||||
@ -301,7 +301,7 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
test.skip(
|
||||
'external change of file contents are reflected in editor',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, testInfo) => {
|
||||
|
||||
@ -9,13 +9,15 @@ import {
|
||||
sendCustomCmd,
|
||||
} from '../test-utils'
|
||||
|
||||
type mouseParams = {
|
||||
type MouseParams = {
|
||||
pixelDiff?: number
|
||||
shouldDbClick?: boolean
|
||||
delay?: number
|
||||
}
|
||||
type mouseDragToParams = mouseParams & {
|
||||
type MouseDragToParams = MouseParams & {
|
||||
fromPoint: { x: number; y: number }
|
||||
}
|
||||
type mouseDragFromParams = mouseParams & {
|
||||
type MouseDragFromParams = MouseParams & {
|
||||
toPoint: { x: number; y: number }
|
||||
}
|
||||
|
||||
@ -26,12 +28,12 @@ type SceneSerialised = {
|
||||
}
|
||||
}
|
||||
|
||||
type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
|
||||
type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean>
|
||||
type DblClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
|
||||
type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean>
|
||||
type ClickHandler = (clickParams?: MouseParams) => Promise<void | boolean>
|
||||
type MoveHandler = (moveParams?: MouseParams) => Promise<void | boolean>
|
||||
type DblClickHandler = (clickParams?: MouseParams) => Promise<void | boolean>
|
||||
type DragToHandler = (dragParams: MouseDragToParams) => Promise<void | boolean>
|
||||
type DragFromHandler = (
|
||||
dragParams: mouseDragFromParams
|
||||
dragParams: MouseDragFromParams
|
||||
) => Promise<void | boolean>
|
||||
|
||||
export class SceneFixture {
|
||||
@ -77,17 +79,26 @@ export class SceneFixture {
|
||||
{ steps }: { steps: number } = { steps: 20 }
|
||||
): [ClickHandler, MoveHandler, DblClickHandler] =>
|
||||
[
|
||||
(clickParams?: mouseParams) => {
|
||||
(clickParams?: MouseParams) => {
|
||||
if (clickParams?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
() => this.page.mouse.click(x, y),
|
||||
() =>
|
||||
clickParams?.shouldDbClick
|
||||
? this.page.mouse.dblclick(x, y, {
|
||||
delay: clickParams?.delay || 0,
|
||||
})
|
||||
: this.page.mouse.click(x, y, {
|
||||
delay: clickParams?.delay || 0,
|
||||
}),
|
||||
clickParams.pixelDiff
|
||||
)
|
||||
}
|
||||
return this.page.mouse.click(x, y)
|
||||
return clickParams?.shouldDbClick
|
||||
? this.page.mouse.dblclick(x, y, { delay: clickParams?.delay || 0 })
|
||||
: this.page.mouse.click(x, y, { delay: clickParams?.delay || 0 })
|
||||
},
|
||||
(moveParams?: mouseParams) => {
|
||||
(moveParams?: MouseParams) => {
|
||||
if (moveParams?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
@ -97,7 +108,7 @@ export class SceneFixture {
|
||||
}
|
||||
return this.page.mouse.move(x, y, { steps })
|
||||
},
|
||||
(clickParams?: mouseParams) => {
|
||||
(clickParams?: MouseParams) => {
|
||||
if (clickParams?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
@ -114,7 +125,7 @@ export class SceneFixture {
|
||||
{ steps }: { steps: number } = { steps: 20 }
|
||||
): [DragToHandler, DragFromHandler] =>
|
||||
[
|
||||
(dragToParams: mouseDragToParams) => {
|
||||
(dragToParams: MouseDragToParams) => {
|
||||
if (dragToParams?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
@ -131,7 +142,7 @@ export class SceneFixture {
|
||||
targetPosition: { x, y },
|
||||
})
|
||||
},
|
||||
(dragFromParams: mouseDragFromParams) => {
|
||||
(dragFromParams: MouseDragFromParams) => {
|
||||
if (dragFromParams?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
@ -219,7 +230,7 @@ export class SceneFixture {
|
||||
}
|
||||
|
||||
expectPixelColor = async (
|
||||
colour: [number, number, number],
|
||||
colour: [number, number, number] | [number, number, number][],
|
||||
coords: { x: number; y: number },
|
||||
diff: number
|
||||
) => {
|
||||
@ -241,22 +252,36 @@ export class SceneFixture {
|
||||
}
|
||||
}
|
||||
|
||||
function isColourArray(
|
||||
colour: [number, number, number] | [number, number, number][]
|
||||
): colour is [number, number, number][] {
|
||||
return Array.isArray(colour[0])
|
||||
}
|
||||
|
||||
export async function expectPixelColor(
|
||||
page: Page,
|
||||
colour: [number, number, number],
|
||||
colour: [number, number, number] | [number, number, number][],
|
||||
coords: { x: number; y: number },
|
||||
diff: number
|
||||
) {
|
||||
let finalValue = colour
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
|
||||
if (!pixel) return null
|
||||
finalValue = pixel
|
||||
return pixel.every(
|
||||
(channel, index) => Math.abs(channel - colour[index]) < diff
|
||||
)
|
||||
})
|
||||
.poll(
|
||||
async () => {
|
||||
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
|
||||
if (!pixel) return null
|
||||
finalValue = pixel
|
||||
if (!isColourArray(colour)) {
|
||||
return pixel.every(
|
||||
(channel, index) => Math.abs(channel - colour[index]) < diff
|
||||
)
|
||||
}
|
||||
return colour.some((c) =>
|
||||
c.every((channel, index) => Math.abs(pixel[index] - channel) < diff)
|
||||
)
|
||||
},
|
||||
{ timeout: 10_000 }
|
||||
)
|
||||
.toBeTruthy()
|
||||
.catch((cause) => {
|
||||
throw new Error(
|
||||
|
||||
@ -22,7 +22,10 @@ export class ToolbarFixture {
|
||||
offsetPlaneButton!: Locator
|
||||
startSketchBtn!: Locator
|
||||
lineBtn!: Locator
|
||||
tangentialArcBtn!: Locator
|
||||
circleBtn!: Locator
|
||||
rectangleBtn!: Locator
|
||||
lengthConstraintBtn!: Locator
|
||||
exitSketchBtn!: Locator
|
||||
editSketchBtn!: Locator
|
||||
fileTreeBtn!: Locator
|
||||
@ -51,7 +54,10 @@ export class ToolbarFixture {
|
||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
this.lineBtn = page.getByTestId('line')
|
||||
this.tangentialArcBtn = page.getByTestId('tangential-arc')
|
||||
this.circleBtn = page.getByTestId('circle-center')
|
||||
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
||||
this.lengthConstraintBtn = page.getByTestId('constraint-length')
|
||||
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
||||
this.editSketchBtn = page.getByText('Edit Sketch')
|
||||
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
||||
@ -117,6 +123,15 @@ export class ToolbarFixture {
|
||||
await expect(this.exeIndicator).toBeVisible({ timeout: 15_000 })
|
||||
}
|
||||
}
|
||||
selectCenterRectangle = async () => {
|
||||
await this.page
|
||||
.getByRole('button', { name: 'caret down Corner rectangle:' })
|
||||
.click()
|
||||
await expect(
|
||||
this.page.getByTestId('dropdown-center-rectangle')
|
||||
).toBeVisible()
|
||||
await this.page.getByTestId('dropdown-center-rectangle').click()
|
||||
}
|
||||
|
||||
async closePane(paneId: SidebarType) {
|
||||
return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
||||
|
||||
@ -437,7 +437,7 @@ test.describe('Onboarding tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.fixme(
|
||||
test(
|
||||
'Restarting onboarding on desktop takes one attempt',
|
||||
{
|
||||
appSettings: {
|
||||
@ -514,10 +514,7 @@ test.fixme(
|
||||
const modelColor: [number, number, number] = [76, 76, 76]
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||
await tutorialDismissButton.click()
|
||||
// Make sure model still there.
|
||||
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||
})
|
||||
|
||||
await test.step('Clear code and restart onboarding from settings', async () => {
|
||||
|
||||
@ -216,18 +216,13 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
||||
|
||||
afterChamferSelectSnippet:
|
||||
'sketch002 = startSketchOn(extrude001, seg03)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()`,
|
||||
afterRectangle1stClickSnippet:
|
||||
'startProfileAt([205.96, 254.59], sketch002)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0,11.39],%,$rectangleSegmentA002)
|
||||
|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)
|
||||
|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)
|
||||
|>line(endAbsolute=[profileStartX(%),profileStartY(%)],%)
|
||||
|>close(%)`,
|
||||
})
|
||||
|
||||
await sketchOnAChamfer({
|
||||
@ -248,19 +243,15 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
||||
|
||||
afterChamferSelectSnippet:
|
||||
'sketch003 = startSketchOn(extrude001, seg04)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) - 90,
|
||||
106.84
|
||||
], %, $rectangleSegmentB002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003),
|
||||
-segLen(rectangleSegmentA003)
|
||||
], %, $rectangleSegmentC002)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()`,
|
||||
afterRectangle1stClickSnippet:
|
||||
'startProfileAt([-209.64, 255.28], sketch003)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0,11.56],%,$rectangleSegmentA003)
|
||||
|>angledLine([segAng(rectangleSegmentA003)-90,106.84],%)
|
||||
|>angledLine([segAng(rectangleSegmentA003),-segLen(rectangleSegmentA003)],%)
|
||||
|>line(endAbsolute=[profileStartX(%),profileStartY(%)],%)
|
||||
|>close(%)`,
|
||||
})
|
||||
|
||||
await sketchOnAChamfer({
|
||||
clickCoords: { x: 677, y: 87 },
|
||||
cameraPos: { x: -6200, y: 1500, z: 6200 },
|
||||
@ -273,19 +264,14 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
||||
]
|
||||
}, %)`,
|
||||
afterChamferSelectSnippet:
|
||||
'sketch003 = startSketchOn(extrude001, seg04)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([75.8, 317.2], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) - 90,
|
||||
106.84
|
||||
], %, $rectangleSegmentB002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003),
|
||||
-segLen(rectangleSegmentA003)
|
||||
], %, $rectangleSegmentC002)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()`,
|
||||
'sketch004 = startSketchOn(extrude001, seg05)',
|
||||
afterRectangle1stClickSnippet:
|
||||
'startProfileAt([82.57, 322.96], sketch004)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0,11.16],%,$rectangleSegmentA004)
|
||||
|>angledLine([segAng(rectangleSegmentA004)-90,103.07],%)
|
||||
|>angledLine([segAng(rectangleSegmentA004),-segLen(rectangleSegmentA004)],%)
|
||||
|>line(endAbsolute=[profileStartX(%),profileStartY(%)],%)|
|
||||
>close(%)`,
|
||||
})
|
||||
/// last one
|
||||
await sketchOnAChamfer({
|
||||
@ -298,104 +284,97 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
||||
}, %)`,
|
||||
afterChamferSelectSnippet:
|
||||
'sketch005 = startSketchOn(extrude001, seg06)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005)
|
||||
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005) - 90,
|
||||
84.07
|
||||
], %, $rectangleSegmentB004)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005),
|
||||
-segLen(rectangleSegmentA005)
|
||||
], %, $rectangleSegmentC004)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()`,
|
||||
afterRectangle1stClickSnippet:
|
||||
'startProfileAt([-23.43, 19.69], sketch005)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0,9.1],%,$rectangleSegmentA005)
|
||||
|>angledLine([segAng(rectangleSegmentA005)-90,84.07],%)
|
||||
|>angledLine([segAng(rectangleSegmentA005),-segLen(rectangleSegmentA005)],%)
|
||||
|>line(endAbsolute=[profileStartX(%),profileStartY(%)],%)
|
||||
|>close(%)`,
|
||||
})
|
||||
|
||||
await test.step('verify at the end of the test that final code is what is expected', async () => {
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|
||||
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|
||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
217.26
|
||||
], %, $seg01)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $yo)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|
||||
|> close()
|
||||
extrude001 = extrude(sketch001, length = 100)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getOppositeEdge(seg01)]
|
||||
}, %, $seg03)
|
||||
|> chamfer({ length = 30, tags = [seg01] }, %, $seg04)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getNextAdjacentEdge(seg02)]
|
||||
}, %, $seg05)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getNextAdjacentEdge(yo)]
|
||||
}, %, $seg06)
|
||||
sketch005 = startSketchOn(extrude001, seg06)
|
||||
|> startProfileAt([-23.43,19.69], %)
|
||||
|> angledLine([0, 9.1], %, $rectangleSegmentA005)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005) - 90,
|
||||
84.07
|
||||
], %, $rectangleSegmentB004)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005),
|
||||
-segLen(rectangleSegmentA005)
|
||||
], %, $rectangleSegmentC004)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
sketch004 = startSketchOn(extrude001, seg05)
|
||||
|> startProfileAt([82.57,322.96], %)
|
||||
|> angledLine([0, 11.16], %, $rectangleSegmentA004)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA004) - 90,
|
||||
103.07
|
||||
], %, $rectangleSegmentB003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA004),
|
||||
-segLen(rectangleSegmentA004)
|
||||
], %, $rectangleSegmentC003)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
sketch003 = startSketchOn(extrude001, seg04)
|
||||
|> startProfileAt([-209.64,255.28], %)
|
||||
|> angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) - 90,
|
||||
106.84
|
||||
], %, $rectangleSegmentB002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003),
|
||||
-segLen(rectangleSegmentA003)
|
||||
], %, $rectangleSegmentC002)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
sketch002 = startSketchOn(extrude001, seg03)
|
||||
|> startProfileAt([205.96,254.59], %)
|
||||
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
`,
|
||||
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|
||||
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
217.26
|
||||
], %, $seg01)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $yo)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %, $seg02)
|
||||
|> close(%)
|
||||
extrude001 = extrude(100, sketch001)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getOppositeEdge(seg01)]
|
||||
}, %, $seg03)
|
||||
|> chamfer({ length = 30, tags = [seg01] }, %, $seg04)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getNextAdjacentEdge(seg02)]
|
||||
}, %, $seg05)
|
||||
|> chamfer({
|
||||
length = 30,
|
||||
tags = [getNextAdjacentEdge(yo)]
|
||||
}, %, $seg06)
|
||||
sketch005 = startSketchOn(extrude001, seg06)
|
||||
profile004 = startProfileAt([-23.43, 19.69], sketch005)
|
||||
|> angledLine([0, 9.1], %, $rectangleSegmentA005)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005) - 90,
|
||||
84.07
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA005),
|
||||
-segLen(rectangleSegmentA005)
|
||||
], %)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch004 = startSketchOn(extrude001, seg05)
|
||||
profile003 = startProfileAt([82.57, 322.96], sketch004)
|
||||
|> angledLine([0, 11.16], %, $rectangleSegmentA004)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA004) - 90,
|
||||
103.07
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA004),
|
||||
-segLen(rectangleSegmentA004)
|
||||
], %)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch003 = startSketchOn(extrude001, seg04)
|
||||
profile002 = startProfileAt([-209.64, 255.28], sketch003)
|
||||
|> angledLine([0, 11.56], %, $rectangleSegmentA003)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003) - 90,
|
||||
106.84
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA003),
|
||||
-segLen(rectangleSegmentA003)
|
||||
], %)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch002 = startSketchOn(extrude001, seg03)
|
||||
profile001 = startProfileAt([205.96, 254.59], sketch002)
|
||||
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
})
|
||||
@ -439,18 +418,13 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
||||
beforeChamferSnippetEnd: '}, extrude001)',
|
||||
afterChamferSelectSnippet:
|
||||
'sketch002 = startSketchOn(extrude001, seg03)',
|
||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()`,
|
||||
afterRectangle1stClickSnippet:
|
||||
'startProfileAt([205.96, 254.59], sketch002)',
|
||||
afterRectangle2ndClickSnippet: `angledLine([0,11.39],%,$rectangleSegmentA002)
|
||||
|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)
|
||||
|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)
|
||||
|>line(endAbsolute=[profileStartX(%),profileStartY(%)],%)
|
||||
|>close(%)`,
|
||||
})
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
@ -480,24 +454,119 @@ chamf = chamfer({
|
||||
]
|
||||
}, %)
|
||||
sketch002 = startSketchOn(extrude001, seg03)
|
||||
|> startProfileAt([205.96, 254.59], %)
|
||||
profile001 = startProfileAt([205.96, 254.59], sketch002)
|
||||
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002) - 90,
|
||||
105.26
|
||||
], %, $rectangleSegmentB001)
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA002),
|
||||
-segLen(rectangleSegmentA002)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
], %)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test(`Verify axis, origin, and horizontal snapping`, async ({
|
||||
page,
|
||||
homePage,
|
||||
editor,
|
||||
toolbar,
|
||||
scene,
|
||||
}) => {
|
||||
const viewPortSize = { width: 1200, height: 500 }
|
||||
|
||||
await page.setBodyDimensions(viewPortSize)
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Constants and locators
|
||||
// These are mappings from screenspace to KCL coordinates,
|
||||
// until we merge in our coordinate system helpers
|
||||
const xzPlane = [
|
||||
viewPortSize.width * 0.65,
|
||||
viewPortSize.height * 0.3,
|
||||
] as const
|
||||
const originSloppy = {
|
||||
screen: [
|
||||
viewPortSize.width / 2 + 3, // 3px off the center of the screen
|
||||
viewPortSize.height / 2,
|
||||
],
|
||||
kcl: [0, 0],
|
||||
} as const
|
||||
const xAxisSloppy = {
|
||||
screen: [
|
||||
viewPortSize.width * 0.75,
|
||||
viewPortSize.height / 2 - 3, // 3px off the X-axis
|
||||
],
|
||||
kcl: [20.34, 0],
|
||||
} as const
|
||||
const offYAxis = {
|
||||
screen: [
|
||||
viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range
|
||||
viewPortSize.height * 0.3,
|
||||
],
|
||||
kcl: [8.14, 6.78],
|
||||
} as const
|
||||
const yAxisSloppy = {
|
||||
screen: [
|
||||
viewPortSize.width / 2 + 5, // 5px off the Y-axis
|
||||
viewPortSize.height * 0.3,
|
||||
],
|
||||
kcl: [0, 6.78],
|
||||
} as const
|
||||
const [clickOnXzPlane, moveToXzPlane] = scene.makeMouseHelpers(...xzPlane)
|
||||
const [clickOriginSloppy] = scene.makeMouseHelpers(...originSloppy.screen)
|
||||
const [clickXAxisSloppy, moveXAxisSloppy] = scene.makeMouseHelpers(
|
||||
...xAxisSloppy.screen
|
||||
)
|
||||
const [dragToOffYAxis, dragFromOffAxis] = scene.makeDragHelpers(
|
||||
...offYAxis.screen
|
||||
)
|
||||
|
||||
const expectedCodeSnippets = {
|
||||
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
|
||||
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], sketch001)`,
|
||||
segmentOnXAxis: `xLine(${xAxisSloppy.kcl[0]}, %)`,
|
||||
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], sketch001)`,
|
||||
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], sketch001)`,
|
||||
}
|
||||
|
||||
await test.step(`Start a sketch on the XZ plane`, async () => {
|
||||
await editor.closePane()
|
||||
await toolbar.startSketchPlaneSelection()
|
||||
await moveToXzPlane()
|
||||
await clickOnXzPlane()
|
||||
// timeout wait for engine animation is unavoidable
|
||||
await page.waitForTimeout(600)
|
||||
await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane)
|
||||
})
|
||||
await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => {
|
||||
await clickOriginSloppy()
|
||||
await editor.expectEditor.toContain(expectedCodeSnippets.pointAtOrigin)
|
||||
})
|
||||
await test.step(`Add a segment on x-axis after moving the mouse a bit, verify it snaps`, async () => {
|
||||
await moveXAxisSloppy()
|
||||
await clickXAxisSloppy()
|
||||
await editor.expectEditor.toContain(expectedCodeSnippets.segmentOnXAxis)
|
||||
})
|
||||
await test.step(`Unequip line tool`, async () => {
|
||||
await toolbar.lineBtn.click()
|
||||
await expect(toolbar.lineBtn).not.toHaveAttribute('aria-pressed', 'true')
|
||||
})
|
||||
await test.step(`Drag the origin point up and to the right, verify it's past snapping`, async () => {
|
||||
await dragToOffYAxis({
|
||||
fromPoint: { x: originSloppy.screen[0], y: originSloppy.screen[1] },
|
||||
})
|
||||
})
|
||||
})
|
||||
// yo
|
||||
|
||||
test(`Verify axis, origin, and horizontal snapping`, async ({
|
||||
page,
|
||||
homePage,
|
||||
@ -1001,6 +1070,21 @@ sketch002 = startSketchOn('XZ')
|
||||
await cmdBar.progressCmdBar()
|
||||
await page.waitForTimeout(500)
|
||||
})
|
||||
// // yo
|
||||
// await clickOnSketch2()
|
||||
// await page.waitForTimeout(500)
|
||||
// await cmdBar.progressCmdBar()
|
||||
// await toolbar.openPane('code')
|
||||
// await page.waitForTimeout(500)
|
||||
// })
|
||||
|
||||
// await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
// await scene.expectPixelColor([135, 64, 73], testPoint, 15)
|
||||
// await editor.expectEditor.toContain(sweepDeclaration)
|
||||
// await editor.expectState({
|
||||
// diagnostics: [],
|
||||
// activeLines: [sweepDeclaration],
|
||||
// highlightedCode: '',
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
await scene.expectPixelColor([135, 64, 73], testPoint, 15)
|
||||
@ -1890,7 +1974,7 @@ chamfer04 = chamfer({ length = 5, tags = [getOppositeEdge(seg02)]}, extrude001
|
||||
const testPoint = { x: 575, y: 200 }
|
||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const shellDeclaration =
|
||||
"shell001 = shell(extrude001, faces = ['end'], thickness = 5)"
|
||||
"shell001 = shell({ faces = ['end'], thickness = 5 }, extrude001)"
|
||||
|
||||
await test.step(`Look for the grey of the shape`, async () => {
|
||||
await scene.expectPixelColor([127, 127, 127], testPoint, 15)
|
||||
@ -1990,7 +2074,8 @@ extrude001 = extrude(sketch001, length = 40)
|
||||
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70)
|
||||
const mutatedCode = 'xLine(-40, %, $seg01)'
|
||||
const shellDeclaration =
|
||||
"shell001 = shell(extrude001, faces = ['end', seg01], thickness = 5)"
|
||||
"shell001 = shell({ faces = ['end', seg01], thickness = 5}, extrude001)"
|
||||
const formattedOutLastLine = '}, extrude001)'
|
||||
|
||||
await test.step(`Look for the grey of the shape`, async () => {
|
||||
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
||||
@ -2033,7 +2118,7 @@ extrude001 = extrude(sketch001, length = 40)
|
||||
await editor.expectEditor.toContain(shellDeclaration)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [shellDeclaration],
|
||||
activeLines: [formattedOutLastLine],
|
||||
highlightedCode: '',
|
||||
})
|
||||
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
||||
@ -2087,8 +2172,9 @@ extrude002 = extrude(sketch002, length = 50)
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 550, y: 295 }
|
||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const shellTarget = hasExtrudesInPipe ? 'sketch002' : 'extrude002'
|
||||
const shellDeclaration = `shell001 = shell(${shellTarget}, faces = ['end'], thickness = 5)`
|
||||
const shellDeclaration = `shell001 = shell({ faces = ['end'], thickness = 5 }, ${
|
||||
hasExtrudesInPipe ? 'sketch002' : 'extrude002'
|
||||
})`
|
||||
|
||||
await test.step(`Look for the grey of the shape`, async () => {
|
||||
await toolbar.closePane('code')
|
||||
|
||||
@ -35,108 +35,106 @@ sketch003 = startSketchOn('XY')
|
||||
extrude003 = extrude(sketch003, length = 20)
|
||||
`
|
||||
|
||||
test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
test.describe('Check the happy path, for basic changing color', () => {
|
||||
const cases = [
|
||||
{
|
||||
desc: 'User accepts change',
|
||||
shouldReject: false,
|
||||
},
|
||||
{
|
||||
desc: 'User rejects change',
|
||||
shouldReject: true,
|
||||
},
|
||||
] as const
|
||||
for (const { desc, shouldReject } of cases) {
|
||||
test(`${desc}`, async ({
|
||||
context,
|
||||
homePage,
|
||||
cmdBar,
|
||||
editor,
|
||||
page,
|
||||
scene,
|
||||
}) => {
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
test.describe('Check the happy path, for basic changing color', () => {
|
||||
const cases = [
|
||||
{
|
||||
desc: 'User accepts change',
|
||||
shouldReject: false,
|
||||
},
|
||||
{
|
||||
desc: 'User rejects change',
|
||||
shouldReject: true,
|
||||
},
|
||||
] as const
|
||||
for (const { desc, shouldReject } of cases) {
|
||||
test(`${desc}`, async ({
|
||||
context,
|
||||
homePage,
|
||||
cmdBar,
|
||||
editor,
|
||||
page,
|
||||
scene,
|
||||
}) => {
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
const body1CapCoords = { x: 571, y: 351 }
|
||||
const greenCheckCoords = { x: 565, y: 345 }
|
||||
const body2WallCoords = { x: 609, y: 153 }
|
||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||
body1CapCoords.x,
|
||||
body1CapCoords.y
|
||||
)
|
||||
const yellow: [number, number, number] = [179, 179, 131]
|
||||
const green: [number, number, number] = [108, 152, 75]
|
||||
const notGreen: [number, number, number] = [132, 132, 132]
|
||||
const body2NotGreen: [number, number, number] = [88, 88, 88]
|
||||
const submittingToast = page.getByText(
|
||||
'Submitting to Text-to-CAD API...'
|
||||
)
|
||||
const successToast = page.getByText('Prompt to edit successful')
|
||||
const acceptBtn = page.getByRole('button', { name: 'checkmark Accept' })
|
||||
const rejectBtn = page.getByRole('button', { name: 'close Reject' })
|
||||
const body1CapCoords = { x: 571, y: 351 }
|
||||
const greenCheckCoords = { x: 565, y: 345 }
|
||||
const body2WallCoords = { x: 609, y: 153 }
|
||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||
body1CapCoords.x,
|
||||
body1CapCoords.y
|
||||
)
|
||||
const yellow: [number, number, number] = [179, 179, 131]
|
||||
const green: [number, number, number] = [108, 152, 75]
|
||||
const notGreen: [number, number, number] = [132, 132, 132]
|
||||
const body2NotGreen: [number, number, number] = [88, 88, 88]
|
||||
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
||||
const successToast = page.getByText('Prompt to edit successful')
|
||||
const acceptBtn = page.getByRole('button', { name: 'checkmark Accept' })
|
||||
const rejectBtn = page.getByRole('button', { name: 'close Reject' })
|
||||
|
||||
await test.step('wait for scene to load select body and check selection came through', async () => {
|
||||
await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15)
|
||||
await clickBody1Cap()
|
||||
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
||||
diagnostics: [],
|
||||
})
|
||||
await test.step('wait for scene to load select body and check selection came through', async () => {
|
||||
await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15)
|
||||
await clickBody1Cap()
|
||||
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
||||
diagnostics: [],
|
||||
})
|
||||
|
||||
await test.step('fire off edit prompt', async () => {
|
||||
await cmdBar.openCmdBar('promptToEdit')
|
||||
// being specific about the color with a hex means asserting pixel color is more stable
|
||||
await page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.fill('make this neon green please, use #39FF14')
|
||||
await page.waitForTimeout(100)
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(submittingToast).toBeVisible()
|
||||
await expect(submittingToast).not.toBeVisible({ timeout: 2 * 60_000 }) // can take a while
|
||||
await expect(successToast).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('verify initial change', async () => {
|
||||
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||
await scene.expectPixelColor(body2NotGreen, body2WallCoords, 15)
|
||||
await editor.expectEditor.toContain('appearance({')
|
||||
})
|
||||
|
||||
if (!shouldReject) {
|
||||
await test.step('check accept works and can be "undo"ed', async () => {
|
||||
await acceptBtn.click()
|
||||
await expect(successToast).not.toBeVisible()
|
||||
|
||||
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||
await editor.expectEditor.toContain('appearance({')
|
||||
|
||||
// ctrl-z works after accepting
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
await editor.expectEditor.not.toContain('appearance({')
|
||||
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||
})
|
||||
} else {
|
||||
await test.step('check reject works', async () => {
|
||||
await rejectBtn.click()
|
||||
await expect(successToast).not.toBeVisible()
|
||||
|
||||
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||
await editor.expectEditor.not.toContain('appearance({')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
await test.step('fire off edit prompt', async () => {
|
||||
await cmdBar.openCmdBar('promptToEdit')
|
||||
// being specific about the color with a hex means asserting pixel color is more stable
|
||||
await page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.fill('make this neon green please, use #39FF14')
|
||||
await page.waitForTimeout(100)
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(submittingToast).toBeVisible()
|
||||
await expect(submittingToast).not.toBeVisible({ timeout: 2 * 60_000 }) // can take a while
|
||||
await expect(successToast).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('verify initial change', async () => {
|
||||
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||
await scene.expectPixelColor(body2NotGreen, body2WallCoords, 15)
|
||||
await editor.expectEditor.toContain('appearance({')
|
||||
})
|
||||
|
||||
if (!shouldReject) {
|
||||
await test.step('check accept works and can be "undo"ed', async () => {
|
||||
await acceptBtn.click()
|
||||
await expect(successToast).not.toBeVisible()
|
||||
|
||||
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||
await editor.expectEditor.toContain('appearance({')
|
||||
|
||||
// ctrl-z works after accepting
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
await editor.expectEditor.not.toContain('appearance({')
|
||||
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||
})
|
||||
} else {
|
||||
await test.step('check reject works', async () => {
|
||||
await rejectBtn.click()
|
||||
await expect(successToast).not.toBeVisible()
|
||||
|
||||
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||
await editor.expectEditor.not.toContain('appearance({')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test.describe('bad path', { tag: ['@skipWin'] }, () => {
|
||||
test(`bad edit prompt`, async ({
|
||||
context,
|
||||
homePage,
|
||||
|
||||
@ -253,7 +253,7 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
|>
|
||||
|
||||
example = extrude(exampleSketch, length = 5)
|
||||
shell(exampleSketch, faces = ['end'], thickness = 0.25)`
|
||||
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
|
||||
)
|
||||
})
|
||||
|
||||
@ -314,7 +314,6 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
)
|
||||
|
||||
test('when engine fails export we handle the failure and alert the user', async ({
|
||||
scene,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
@ -384,7 +383,10 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
await scene.waitForExecutionDone()
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Now try exporting
|
||||
|
||||
|
||||
@ -444,8 +444,7 @@ test(
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
code += `
|
||||
|> startProfileAt([7.19, -9.7], %)`
|
||||
code += `profile001 = startProfileAt([7.19, -9.7], sketch001)`
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -467,6 +466,10 @@ test(
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
|
||||
// click to continue profile
|
||||
await page.mouse.move(813, 392, { steps: 10 })
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
@ -589,8 +592,7 @@ test(
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [14.44, -2.44], radius = 1 }, %)`
|
||||
`sketch001 = startSketchOn('XZ')profile001 = circle({ center = [14.44, -2.44], radius = 1 }, sketch001)`
|
||||
)
|
||||
}
|
||||
)
|
||||
@ -634,8 +636,7 @@ test.describe(
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
code += `
|
||||
|> startProfileAt([7.19, -9.7], %)`
|
||||
code += `profile001 = startProfileAt([7.19, -9.7], sketch001)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -653,6 +654,10 @@ test.describe(
|
||||
.click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click to continue profile
|
||||
await page.mouse.click(813, 392)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||
|
||||
code += `
|
||||
@ -739,8 +744,7 @@ test.describe(
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
code += `
|
||||
|> startProfileAt([182.59, -246.32], %)`
|
||||
code += `profile001 = startProfileAt([182.59, -246.32], sketch001)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -758,6 +762,10 @@ test.describe(
|
||||
.click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click to continue profile
|
||||
await page.mouse.click(813, 392)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||
|
||||
code += `
|
||||
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
@ -1,6 +1,7 @@
|
||||
import { test, expect } from './zoo-test'
|
||||
|
||||
import { commonPoints, getUtils } from './test-utils'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
|
||||
test.describe('Test network and connection issues', () => {
|
||||
test('simulate network down and network little widget', async ({
|
||||
@ -110,18 +111,17 @@ test.describe('Test network and connection issues', () => {
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||
)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|
||||
// Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
@ -168,7 +168,9 @@ test.describe('Test network and connection issues', () => {
|
||||
await page.mouse.click(100, 100)
|
||||
|
||||
// select a line
|
||||
await page.getByText(`startProfileAt(${commonPoints.startAt}, %)`).click()
|
||||
await page
|
||||
.getByText(`startProfileAt(${commonPoints.startAt}, sketch001)`)
|
||||
.click()
|
||||
|
||||
// enter sketch again
|
||||
await u.doAndWaitForCmd(
|
||||
@ -182,11 +184,36 @@ test.describe('Test network and connection issues', () => {
|
||||
|
||||
await page.waitForTimeout(150)
|
||||
|
||||
const camCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
center: { x: 109, y: 0, z: -152 },
|
||||
vantage: { x: 115, y: -505, z: -152 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
}
|
||||
const updateCamCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
}
|
||||
await u.sendCustomCmd(camCommand)
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd(updateCamCommand)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click to continue profile
|
||||
await page.mouse.click(1007, 400)
|
||||
await page.waitForTimeout(100)
|
||||
// Ensure we can continue sketching
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
await expect.poll(u.normalisedEditorCode)
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
||||
|> xLine(12.34, %)
|
||||
|> line(end = [-12.34, 12.34])
|
||||
|
||||
@ -196,7 +223,7 @@ test.describe('Test network and connection issues', () => {
|
||||
|
||||
await expect.poll(u.normalisedEditorCode)
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
||||
|> xLine(12.34, %)
|
||||
|> line(end = [-12.34, 12.34])
|
||||
|> xLine(-12.34, %)
|
||||
|
||||
@ -19,7 +19,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
|> line(end = [20, 0])
|
||||
|> line(end = [0, 20])
|
||||
|> xLine(-20, %)
|
||||
`
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
@ -673,7 +673,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
},
|
||||
] as const
|
||||
for (const { testName, addVariable, value, constraint } of cases) {
|
||||
test(`${testName}`, async ({ context, homePage, page }) => {
|
||||
test(`${testName}`, async ({ context, homePage, page, editor }) => {
|
||||
// constants and locators
|
||||
const cmdBarKclInput = page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
@ -706,8 +706,11 @@ part002 = startSketchOn('XZ')
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await page.getByText('line(end = [74.36, 130.4])').click()
|
||||
await editor.scrollToText('line(end = [74.36, 130.4], %)', true)
|
||||
await page.getByText('line(end = [74.36, 130.4], %)').click()
|
||||
await page.screenshot({ path: 'ok.png' })
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
const line3 = await u.getSegmentBodyCoords(
|
||||
|
||||
@ -63,36 +63,41 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
await page.mouse.click(700, 200)
|
||||
await page.waitForTimeout(700) // wait for animation
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
await page.waitForTimeout(700) // wait for animation
|
||||
|
||||
const startXPx = 600
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||
)
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||
commonPoints.startAt
|
||||
}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)
|
||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||
|
||||
// deselect line tool
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
@ -255,66 +260,88 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-79.26, 95.04], %)
|
||||
|> line(end = [112.54, 127.64], tag = $seg02)
|
||||
|> line(end = [170.36, -121.61], tag = $seg01)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
extrude001 = extrude(sketch001, length = 50)
|
||||
sketch005 = startSketchOn(extrude001, 'END')
|
||||
|> startProfileAt([23.24, 136.52], %)
|
||||
|> line(end = [-8.44, 36.61])
|
||||
|> line(end = [49.4, 2.05])
|
||||
|> line(end = [29.69, -46.95])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
sketch003 = startSketchOn(extrude001, seg01)
|
||||
|> startProfileAt([21.23, 17.81], %)
|
||||
|> line(end = [51.97, 21.32])
|
||||
|> line(end = [4.07, -22.75])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
sketch002 = startSketchOn(extrude001, seg02)
|
||||
|> startProfileAt([-100.54, 16.99], %)
|
||||
|> line(end = [0, 20.03])
|
||||
|> line(end = [62.61, 0], tag = $seg03)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
extrude002 = extrude(sketch002, length = 50)
|
||||
sketch004 = startSketchOn(extrude002, seg03)
|
||||
|> startProfileAt([57.07, 134.77], %)
|
||||
|> line(end = [-4.72, 22.84])
|
||||
|> line(end = [28.8, 6.71])
|
||||
|> line(end = [9.19, -25.33])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
extrude003 = extrude(sketch004, length = 20)
|
||||
pipeLength = 40
|
||||
pipeSmallDia = 10
|
||||
pipeLargeDia = 20
|
||||
thickness = 0.5
|
||||
part009 = startSketchOn('XY')
|
||||
|> startProfileAt([pipeLargeDia - (thickness / 2), 38], %)
|
||||
|> line(end = [thickness, 0])
|
||||
|> line(end = [0, -1])
|
||||
|> angledLineToX({
|
||||
angle = 60,
|
||||
to = pipeSmallDia + thickness
|
||||
}, %)
|
||||
|> line(end = [0, -pipeLength])
|
||||
|> angledLineToX({
|
||||
angle = -60,
|
||||
to = pipeLargeDia + thickness
|
||||
}, %)
|
||||
|> line(end = [0, -1])
|
||||
|> line(end = [-thickness, 0])
|
||||
|> line(end = [0, 1])
|
||||
|> angledLineToX({ angle = 120, to = pipeSmallDia }, %)
|
||||
|> line(end = [0, pipeLength])
|
||||
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|
||||
|> close()
|
||||
rev = revolve({ axis: 'y' }, part009)
|
||||
`
|
||||
|> startProfileAt([-79.26, 95.04], %)
|
||||
|> line(end=[112.54, 127.64], %, $seg02)
|
||||
|> line(end=[170.36, -121.61], %, $seg01)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(50, sketch001)
|
||||
sketch005 = startSketchOn(extrude001, 'END')
|
||||
|> startProfileAt([23.24, 136.52], %)
|
||||
|> line(end=[-8.44, 36.61], %)
|
||||
|> line(end=[49.4, 2.05], %)
|
||||
|> line(end=[29.69, -46.95], %)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch003 = startSketchOn(extrude001, seg01)
|
||||
|> startProfileAt([21.23, 17.81], %)
|
||||
|> line(end=[51.97, 21.32], %)
|
||||
|> line(end=[4.07, -22.75], %)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch002 = startSketchOn(extrude001, seg02)
|
||||
|> startProfileAt([-100.54, 16.99], %)
|
||||
|> line(end=[0, 20.03], %)
|
||||
|> line(end=[62.61, 0], %, $seg03)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude002 = extrude(50, sketch002)
|
||||
sketch004 = startSketchOn(extrude002, seg03)
|
||||
|> startProfileAt([57.07, 134.77], %)
|
||||
|> line(end=[-4.72, 22.84], %)
|
||||
|> line(end=[28.8, 6.71], %)
|
||||
|> line(end=[9.19, -25.33], %)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude003 = extrude(20, sketch004)
|
||||
pipeLength = 40
|
||||
pipeSmallDia = 10
|
||||
pipeLargeDia = 20
|
||||
thickness = 0.5
|
||||
part009 = startSketchOn('XY')
|
||||
|> startProfileAt([pipeLargeDia - (thickness / 2), 38], %)
|
||||
|> line(end=[thickness, 0], %)
|
||||
|> line(end=[0, -1], %)
|
||||
|> angledLineToX({
|
||||
angle = 60,
|
||||
to = pipeSmallDia + thickness
|
||||
}, %)
|
||||
|> line(end=[0, -pipeLength], %)
|
||||
|> angledLineToX({
|
||||
angle = -60,
|
||||
to = pipeLargeDia + thickness
|
||||
}, %)
|
||||
|> line(end=[0, -1], %)
|
||||
|> line(end=[-thickness, 0], %)
|
||||
|> line(end=[0, 1], %)
|
||||
|> angledLineToX({ angle = 120, to = pipeSmallDia }, %)
|
||||
|> line(end=[0, pipeLength], %)
|
||||
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|
||||
|> close(%)
|
||||
rev = revolve({ axis = 'y' }, part009)
|
||||
sketch006 = startSketchOn('XY')
|
||||
profile001 = circle({
|
||||
center = [42.91, -70.42],
|
||||
radius = 17.96
|
||||
}, sketch006)
|
||||
profile002 = startProfileAt([86.92, -63.81], sketch006)
|
||||
|> angledLine([0, 63.81], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
17.05
|
||||
], %)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %)
|
||||
|> line(endAbsolute=[profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
profile003 = startProfileAt([40.16, -120.48], sketch006)
|
||||
|> line(end=[26.95, 24.21], %)
|
||||
|> line(end=[20.91, -28.61], %)
|
||||
|> line(end=[32.46, 18.71], %)
|
||||
|
||||
`
|
||||
)
|
||||
}, KCL_DEFAULT_LENGTH)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
@ -346,9 +373,10 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const revolve = { x: 646, y: 248 }
|
||||
const revolve = { x: 635, y: 253 }
|
||||
const parentExtrude = { x: 915, y: 133 }
|
||||
const solid2d = { x: 770, y: 167 }
|
||||
const individualProfile = { x: 694, y: 432 }
|
||||
|
||||
// DELETE REVOLVE
|
||||
await page.mouse.click(revolve.x, revolve.y)
|
||||
@ -414,6 +442,20 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await page.waitForTimeout(200)
|
||||
await expect(u.codeLocator).not.toContainText(`sketch005 = startSketchOn({`)
|
||||
|
||||
// Delete a single profile
|
||||
await page.mouse.click(individualProfile.x, individualProfile.y)
|
||||
await page.waitForTimeout(100)
|
||||
const codeToBeDeletedSnippet =
|
||||
'profile003 = startProfileAt([40.16, -120.48], sketch006)'
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
' |> line([20.91, -28.61], %)'
|
||||
)
|
||||
await u.clearCommandLogs()
|
||||
await page.keyboard.press('Backspace')
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await page.waitForTimeout(200)
|
||||
await expect(u.codeLocator).not.toContainText(codeToBeDeletedSnippet)
|
||||
})
|
||||
test("Deleting solid that the AST mod can't handle results in a toast message", async ({
|
||||
page,
|
||||
@ -1211,12 +1253,15 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
const firstClickCoords = { x: 650, y: 200 } as const
|
||||
// Place a point because the line tool will exit if no points are pressed
|
||||
await page.mouse.click(650, 200)
|
||||
await page.mouse.click(firstClickCoords.x, firstClickCoords.y)
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
// Code before exiting the tool
|
||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
let previousCodeContent = (
|
||||
await page.locator('.cm-content').innerText()
|
||||
).replace(/\s+/g, '')
|
||||
|
||||
// deselect the line tool by clicking it
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
@ -1228,14 +1273,23 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
await page.mouse.click(750, 200)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// expect no change
|
||||
await expect(page.locator('.cm-content')).toHaveText(previousCodeContent)
|
||||
await expect
|
||||
.poll(async () => {
|
||||
let str = await page.locator('.cm-content').innerText()
|
||||
str = str.replace(/\s+/g, '')
|
||||
return str
|
||||
})
|
||||
.toBe(previousCodeContent)
|
||||
|
||||
// select line tool again
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Click to continue profile
|
||||
await page.mouse.click(firstClickCoords.x, firstClickCoords.y)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// line tool should work as expected again
|
||||
await page.mouse.click(700, 200)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(
|
||||
|
||||
@ -896,53 +896,4 @@ test.describe('Testing settings', () => {
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test(`Change inline units setting`, async ({
|
||||
page,
|
||||
homePage,
|
||||
context,
|
||||
editor,
|
||||
}) => {
|
||||
const initialInlineUnits = 'yd'
|
||||
const editedInlineUnits = { short: 'mm', long: 'Millimeters' }
|
||||
const inlineSettingsString = (s: string) =>
|
||||
`@settings(defaultLengthUnit = ${s})`
|
||||
const unitsIndicator = page.getByRole('button', {
|
||||
name: 'Current units are:',
|
||||
})
|
||||
const unitsChangeButton = (name: string) =>
|
||||
page.getByRole('button', { name, exact: true })
|
||||
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'project-000')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cube.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
})
|
||||
|
||||
await test.step(`Initial units from settings`, async () => {
|
||||
await homePage.openProject('project-000')
|
||||
await expect(unitsIndicator).toHaveText('Current units are: in')
|
||||
})
|
||||
|
||||
await test.step(`Manually write inline settings`, async () => {
|
||||
await editor.openPane()
|
||||
await editor.replaceCode(
|
||||
`fn cube`,
|
||||
`${inlineSettingsString(initialInlineUnits)}
|
||||
fn cube`
|
||||
)
|
||||
await expect(unitsIndicator).toContainText(initialInlineUnits)
|
||||
})
|
||||
|
||||
await test.step(`Change units setting via lower-right control`, async () => {
|
||||
await unitsIndicator.click()
|
||||
await unitsChangeButton(editedInlineUnits.long).click()
|
||||
await expect(
|
||||
page.getByText(`Updated per-file units to ${editedInlineUnits.short}`)
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -3,7 +3,7 @@ import { getUtils, createProject } from './test-utils'
|
||||
import { join } from 'path'
|
||||
import fs from 'fs'
|
||||
|
||||
test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
|
||||
test.describe('Text-to-CAD tests', () => {
|
||||
test('basic lego happy case', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
|
||||
@ -205,8 +205,13 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
||||
// Draw a line
|
||||
await page.mouse.move(700, 200, { steps: 5 })
|
||||
await page.mouse.click(700, 200)
|
||||
await page.mouse.move(800, 250, { steps: 5 })
|
||||
await page.mouse.click(800, 250)
|
||||
|
||||
const secondMousePosition = { x: 800, y: 250 }
|
||||
|
||||
await page.mouse.move(secondMousePosition.x, secondMousePosition.y, {
|
||||
steps: 5,
|
||||
})
|
||||
await page.mouse.click(secondMousePosition.x, secondMousePosition.y)
|
||||
// Unequip line tool
|
||||
await page.keyboard.press('Escape')
|
||||
// Make sure we didn't pop out of sketch mode.
|
||||
@ -215,9 +220,17 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
||||
// Equip arc tool
|
||||
await page.keyboard.press('a')
|
||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
// click in the same position again to continue the profile
|
||||
await page.mouse.move(secondMousePosition.x, secondMousePosition.y, {
|
||||
steps: 5,
|
||||
})
|
||||
await page.mouse.click(secondMousePosition.x, secondMousePosition.y)
|
||||
|
||||
await page.mouse.move(1000, 100, { steps: 5 })
|
||||
await page.mouse.click(1000, 100)
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'false')
|
||||
await page.keyboard.press('l')
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
@ -519,11 +532,11 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
||||
|
||||
await expect.poll(u.normalisedEditorCode).toContain(
|
||||
u.normalisedCode(`sketch002 = startSketchOn(extrude001, seg01)
|
||||
|> startProfileAt([-12.94, 6.6], %)
|
||||
|> line(end = [2.45, -0.2])
|
||||
|> line(end = [-2.6, -1.25])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
profile001 = startProfileAt([-12.88, 6.66], sketch002)
|
||||
|> line(end = [2.71, -0.22], %)
|
||||
|> line(end = [-2.87, -1.38], %)
|
||||
|> lineTo(endAbsolute = [profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
`)
|
||||
)
|
||||
|
||||
@ -537,9 +550,8 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
||||
await page.getByText('startProfileAt([-12').click()
|
||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
await page.waitForTimeout(150)
|
||||
await page.setBodyDimensions({ width: 1200, height: 1200 })
|
||||
await page.waitForTimeout(500)
|
||||
await page.setViewportSize({ width: 1200, height: 1200 })
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.updateCamPosition([452, -152, 1166])
|
||||
await u.closeDebugPanel()
|
||||
|
||||
@ -85,7 +85,7 @@
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/achalmers/kw-shell/manifest.json",
|
||||
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/manifest.json",
|
||||
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
||||
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
@ -158,8 +158,8 @@
|
||||
"@types/electron": "^1.6.10",
|
||||
"@types/isomorphic-fetch": "^0.0.39",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "^22.13.1",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/node": "^22.7.8",
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react": "^18.3.4",
|
||||
|
||||
@ -1,212 +1 @@
|
||||
[
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "80-20-rail/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "80/20 Rail",
|
||||
"description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position"
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "a-parametric-bearing-pillow-block/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "A Parametric Bearing Pillow Block",
|
||||
"description": "A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "ball-bearing/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Ball Bearing",
|
||||
"description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "bracket/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Shelf Bracket",
|
||||
"description": "This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl",
|
||||
"multipleFiles": true,
|
||||
"title": "Car Wheel Assembly",
|
||||
"description": "A car wheel assembly with a rotor, tire, and lug nuts."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "dodecahedron/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Hollow Dodecahedron",
|
||||
"description": "A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the individual faces of the dodecahedron and extruding inwards."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "enclosure/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Enclosure",
|
||||
"description": "An enclosure body and sealing lid for storing items"
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "flange-with-patterns/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Flange",
|
||||
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "flange-xy/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Flange with XY coordinates",
|
||||
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "focusrite-scarlett-mounting-bracket/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "A mounting bracket for the Focusrite Scarlett Solo audio interface",
|
||||
"description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material"
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "food-service-spatula/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Food Service Spatula",
|
||||
"description": "Use these spatulas for mixing, flipping, and scraping."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "french-press/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "French Press",
|
||||
"description": "A french press immersion coffee maker"
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "gear/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Spur Gear",
|
||||
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "gear-rack/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "100mm Gear Rack",
|
||||
"description": "A flat bar or rail that is engraved with teeth along its length. These teeth are designed to mesh with the teeth of a gear, known as a pinion. When the pinion, a small cylindrical gear, rotates, its teeth engage with the teeth on the rack, causing the rack to move linearly. Conversely, linear motion applied to the rack will cause the pinion to rotate."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "hex-nut/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Hex nut",
|
||||
"description": "A hex nut is a type of fastener with a threaded hole and a hexagonal outer shape, used in a wide variety of applications to secure parts together. The hexagonal shape allows for a greater torque to be applied with wrenches or tools, making it one of the most common nut types in hardware."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "i-beam/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "I-beam",
|
||||
"description": "A structural metal beam with an I shaped cross section. Often used in construction"
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "kitt/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Kitt",
|
||||
"description": "The beloved KittyCAD mascot in a voxelized style."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "lego/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Lego Brick",
|
||||
"description": "A standard Lego brick. This is a small, plastic construction block toy that can be interlocked with other blocks to build various structures, models, and figures. There are a lot of hacks used in this code."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "mounting-plate/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Mounting Plate",
|
||||
"description": "A flat piece of material, often metal or plastic, that serves as a support or base for attaching, securing, or mounting various types of equipment, devices, or components."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "multi-axis-robot/main.kcl",
|
||||
"multipleFiles": true,
|
||||
"title": "Robot Arm",
|
||||
"description": "A 4 axis robotic arm for industrial use. These machines can be used for assembly, packaging, organization of goods, and quality inspection processes"
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "pipe/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Pipe",
|
||||
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "pipe-flange-assembly/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Pipe and Flange Assembly",
|
||||
"description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "pipe-with-bend/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Pipe with bend",
|
||||
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "poopy-shoe/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Poopy Shoe",
|
||||
"description": "poop shute for bambu labs printer - optimized for printing."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "router-template-cross-bar/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Router template for a cross bar",
|
||||
"description": "A guide for routing a notch into a cross bar."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "router-template-slate/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Router template for a slate",
|
||||
"description": "A guide for routing a slate for a cross bar."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "sheet-metal-bracket/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Sheet Metal Bracket",
|
||||
"description": "A component typically made from flat sheet metal through various manufacturing processes such as bending, punching, cutting, and forming. These brackets are used to support, attach, or mount other hardware components, often providing a structural or functional base for assembly."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "socket-head-cap-screw/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Socket Head Cap Screw",
|
||||
"description": "This is for a #10-24 screw that is 1.00 inches long. A socket head cap screw is a type of fastener that is widely used in a variety of applications requiring a high strength fastening solution. It is characterized by its cylindrical head and internal hexagonal drive, which allows for tightening with an Allen wrench or hex key."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "walkie-talkie/main.kcl",
|
||||
"multipleFiles": true,
|
||||
"title": "Walkie Talkie",
|
||||
"description": "A portable, handheld two-way radio device that allows users to communicate wirelessly over short to medium distances. It operates on specific radio frequencies and features a push-to-talk button for transmitting messages, making it ideal for quick and reliable communication in outdoor, work, or emergency settings."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "washer/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Washer",
|
||||
"description": "A small, typically disk-shaped component with a hole in the middle, used in a wide range of applications, primarily in conjunction with fasteners like bolts and screws. Washers distribute the load of a fastener across a broader area. This is especially important when the fastening surface is soft or uneven, as it helps to prevent damage to the surface and ensures the load is evenly distributed, reducing the risk of the fastener becoming loose over time."
|
||||
}
|
||||
]
|
||||
404: Not Found
|
||||
@ -5,7 +5,6 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import { isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
@ -21,6 +20,7 @@ import {
|
||||
} from 'lib/toolbar'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { isCursorInFunctionDefinition } from 'lang/queryAst'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { isArray } from 'lib/utils'
|
||||
|
||||
@ -37,7 +37,12 @@ export function Toolbar({
|
||||
const buttonBorderClassName = '!border-transparent'
|
||||
|
||||
const sketchPathId = useMemo(() => {
|
||||
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast))
|
||||
if (
|
||||
isCursorInFunctionDefinition(
|
||||
kclManager.ast,
|
||||
context.selectionRanges.graphSelections[0]
|
||||
)
|
||||
)
|
||||
return false
|
||||
return isCursorInSketchCommandRange(
|
||||
engineCommandManager.artifactGraph,
|
||||
|
||||
@ -125,14 +125,7 @@ export const ClientSideScene = ({
|
||||
'mouseup',
|
||||
toSync(sceneInfra.onMouseUp, reportRejection)
|
||||
)
|
||||
sceneEntitiesManager
|
||||
.tearDownSketch()
|
||||
.then(() => {
|
||||
// no op
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
})
|
||||
sceneEntitiesManager.tearDownSketch({ removeAxis: true })
|
||||
}
|
||||
}, [])
|
||||
|
||||
@ -191,12 +184,15 @@ const Overlays = () => {
|
||||
style={{ zIndex: '99999999' }}
|
||||
>
|
||||
{Object.entries(context.segmentOverlays)
|
||||
.filter((a) => a[1].visible)
|
||||
.map(([pathToNodeString, overlay], index) => {
|
||||
.flatMap((a) =>
|
||||
a[1].map((b) => ({ pathToNodeString: a[0], overlay: b }))
|
||||
)
|
||||
.filter((a) => a.overlay.visible)
|
||||
.map(({ pathToNodeString, overlay }, index) => {
|
||||
return (
|
||||
<Overlay
|
||||
overlay={overlay}
|
||||
key={pathToNodeString}
|
||||
key={pathToNodeString + String(index)}
|
||||
pathToNodeString={pathToNodeString}
|
||||
overlayIndex={index}
|
||||
/>
|
||||
@ -237,11 +233,17 @@ const Overlay = ({
|
||||
|
||||
const constraints =
|
||||
callExpression.type === 'CallExpression'
|
||||
? getConstraintInfo(callExpression, codeManager.code, overlay.pathToNode)
|
||||
? getConstraintInfo(
|
||||
callExpression,
|
||||
codeManager.code,
|
||||
overlay.pathToNode,
|
||||
overlay.filterValue
|
||||
)
|
||||
: getConstraintInfoKw(
|
||||
callExpression,
|
||||
codeManager.code,
|
||||
overlay.pathToNode
|
||||
overlay.pathToNode,
|
||||
overlay.filterValue
|
||||
)
|
||||
|
||||
const offset = 20 // px
|
||||
@ -261,7 +263,6 @@ const Overlay = ({
|
||||
state.matches({ Sketch: 'Tangential arc to' }) ||
|
||||
state.matches({ Sketch: 'Rectangle tool' })
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={`absolute w-0 h-0`}>
|
||||
<div
|
||||
@ -319,17 +320,18 @@ const Overlay = ({
|
||||
this will likely change soon when we implement multi-profile so we'll leave it for now
|
||||
issue: https://github.com/KittyCAD/modeling-app/issues/3910
|
||||
*/}
|
||||
{callExpression?.callee?.name !== 'circle' && (
|
||||
<SegmentMenu
|
||||
verticalPosition={
|
||||
overlay.windowCoords[1] > window.innerHeight / 2
|
||||
? 'top'
|
||||
: 'bottom'
|
||||
}
|
||||
pathToNode={overlay.pathToNode}
|
||||
stdLibFnName={constraints[0]?.stdLibFnName}
|
||||
/>
|
||||
)}
|
||||
{callExpression?.callee?.name !== 'circle' &&
|
||||
callExpression?.callee?.name !== 'circleThreePoint' && (
|
||||
<SegmentMenu
|
||||
verticalPosition={
|
||||
overlay.windowCoords[1] > window.innerHeight / 2
|
||||
? 'top'
|
||||
: 'bottom'
|
||||
}
|
||||
pathToNode={overlay.pathToNode}
|
||||
stdLibFnName={constraints[0]?.stdLibFnName}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -450,6 +452,8 @@ export async function deleteSegment({
|
||||
if (!sketchDetails) return
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
pathToNode,
|
||||
sketchDetails.sketchNodePaths,
|
||||
sketchDetails.planeNodePath,
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
|
||||
@ -182,13 +182,15 @@ export class SceneInfra {
|
||||
callbacks: (() => SegmentOverlayPayload | null)[] = []
|
||||
_overlayCallbacks(callbacks: (() => SegmentOverlayPayload | null)[]) {
|
||||
const segmentOverlayPayload: SegmentOverlayPayload = {
|
||||
type: 'set-many',
|
||||
type: 'add-many',
|
||||
overlays: {},
|
||||
}
|
||||
callbacks.forEach((cb) => {
|
||||
const overlay = cb()
|
||||
if (overlay?.type === 'set-one') {
|
||||
segmentOverlayPayload.overlays[overlay.pathToNodeString] = overlay.seg
|
||||
} else if (overlay?.type === 'add-many') {
|
||||
Object.assign(segmentOverlayPayload.overlays, overlay.overlays)
|
||||
}
|
||||
})
|
||||
this.modelingSend({
|
||||
@ -213,25 +215,27 @@ export class SceneInfra {
|
||||
|
||||
overlayThrottleMap: { [pathToNodeString: string]: number } = {}
|
||||
updateOverlayDetails({
|
||||
arrowGroup,
|
||||
handle,
|
||||
group,
|
||||
isHandlesVisible,
|
||||
from,
|
||||
to,
|
||||
angle,
|
||||
hasThreeDotMenu,
|
||||
}: {
|
||||
arrowGroup: Group
|
||||
handle: Group
|
||||
group: Group
|
||||
isHandlesVisible: boolean
|
||||
from: Coords2d
|
||||
to: Coords2d
|
||||
hasThreeDotMenu: boolean
|
||||
angle?: number
|
||||
}): SegmentOverlayPayload | null {
|
||||
if (!group.userData.draft && group.userData.pathToNode && arrowGroup) {
|
||||
if (!group.userData.draft && group.userData.pathToNode && handle) {
|
||||
const vector = new Vector3(0, 0, 0)
|
||||
|
||||
// Get the position of the object3D in world space
|
||||
arrowGroup.getWorldPosition(vector)
|
||||
handle.getWorldPosition(vector)
|
||||
|
||||
// Project that position to screen space
|
||||
vector.project(this.camControls.camera)
|
||||
@ -244,13 +248,16 @@ export class SceneInfra {
|
||||
return {
|
||||
type: 'set-one',
|
||||
pathToNodeString,
|
||||
seg: {
|
||||
windowCoords: [x, y],
|
||||
angle: _angle,
|
||||
group,
|
||||
pathToNode: group.userData.pathToNode,
|
||||
visible: isHandlesVisible,
|
||||
},
|
||||
seg: [
|
||||
{
|
||||
windowCoords: [x, y],
|
||||
angle: _angle,
|
||||
group,
|
||||
pathToNode: group.userData.pathToNode,
|
||||
visible: isHandlesVisible,
|
||||
hasThreeDotMenu,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
||||
@ -31,6 +31,12 @@ import {
|
||||
CIRCLE_SEGMENT,
|
||||
CIRCLE_SEGMENT_BODY,
|
||||
CIRCLE_SEGMENT_DASH,
|
||||
CIRCLE_THREE_POINT_HANDLE1,
|
||||
CIRCLE_THREE_POINT_HANDLE2,
|
||||
CIRCLE_THREE_POINT_HANDLE3,
|
||||
CIRCLE_THREE_POINT_SEGMENT,
|
||||
CIRCLE_THREE_POINT_SEGMENT_BODY,
|
||||
CIRCLE_THREE_POINT_SEGMENT_DASH,
|
||||
EXTRA_SEGMENT_HANDLE,
|
||||
EXTRA_SEGMENT_OFFSET_PX,
|
||||
HIDE_HOVER_SEGMENT_LENGTH,
|
||||
@ -48,19 +54,26 @@ import {
|
||||
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
||||
import {
|
||||
ARROWHEAD,
|
||||
CIRCLE_3_POINT_DRAFT_CIRCLE,
|
||||
DRAFT_POINT,
|
||||
SceneInfra,
|
||||
SEGMENT_LENGTH_LABEL,
|
||||
SEGMENT_LENGTH_LABEL_OFFSET_PX,
|
||||
SEGMENT_LENGTH_LABEL_TEXT,
|
||||
SKETCH_LAYER,
|
||||
} from './sceneInfra'
|
||||
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
|
||||
import { normaliseAngle, roundOff } from 'lib/utils'
|
||||
import { SegmentOverlayPayload } from 'machines/modelingMachine'
|
||||
import {
|
||||
SegmentOverlay,
|
||||
SegmentOverlayPayload,
|
||||
SegmentOverlays,
|
||||
} from 'machines/modelingMachine'
|
||||
import { SegmentInputs } from 'lang/std/stdTypes'
|
||||
import { err } from 'lib/trap'
|
||||
import { editorManager, sceneInfra } from 'lib/singletons'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { calculate_circle_from_3_points } from 'wasm-lib/pkg/wasm_lib'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
|
||||
interface CreateSegmentArgs {
|
||||
@ -307,11 +320,12 @@ class StraightSegment implements SegmentUtils {
|
||||
}
|
||||
return () =>
|
||||
sceneInfra.updateOverlayDetails({
|
||||
arrowGroup,
|
||||
handle: arrowGroup,
|
||||
group,
|
||||
isHandlesVisible,
|
||||
from,
|
||||
to,
|
||||
hasThreeDotMenu: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -483,12 +497,13 @@ class TangentialArcToSegment implements SegmentUtils {
|
||||
)
|
||||
return () =>
|
||||
sceneInfra.updateOverlayDetails({
|
||||
arrowGroup,
|
||||
handle: arrowGroup,
|
||||
group,
|
||||
isHandlesVisible,
|
||||
from,
|
||||
to,
|
||||
angle,
|
||||
hasThreeDotMenu: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -684,35 +699,255 @@ class CircleSegment implements SegmentUtils {
|
||||
}
|
||||
return () =>
|
||||
sceneInfra.updateOverlayDetails({
|
||||
arrowGroup,
|
||||
handle: arrowGroup,
|
||||
group,
|
||||
isHandlesVisible,
|
||||
from: from,
|
||||
to: [center[0], center[1]],
|
||||
angle: Math.PI / 4,
|
||||
hasThreeDotMenu: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class CircleThreePointSegment implements SegmentUtils {
|
||||
init: SegmentUtils['init'] = ({
|
||||
input,
|
||||
id,
|
||||
pathToNode,
|
||||
isDraftSegment,
|
||||
scale = 1,
|
||||
theme,
|
||||
isSelected = false,
|
||||
sceneInfra,
|
||||
prevSegment,
|
||||
}) => {
|
||||
if (input.type !== 'circle-three-point-segment') {
|
||||
return new Error('Invalid segment type')
|
||||
}
|
||||
const { p1, p2, p3 } = input
|
||||
const { center_x, center_y, radius } = calculate_circle_from_3_points(
|
||||
p1[0],
|
||||
p1[1],
|
||||
p2[0],
|
||||
p2[1],
|
||||
p3[0],
|
||||
p3[1]
|
||||
)
|
||||
const center: [number, number] = [center_x, center_y]
|
||||
const baseColor = getThemeColorForThreeJs(theme)
|
||||
const color = isSelected ? 0x0000ff : baseColor
|
||||
|
||||
const group = new Group()
|
||||
const geometry = createArcGeometry({
|
||||
center,
|
||||
radius,
|
||||
startAngle: 0,
|
||||
endAngle: Math.PI * 2,
|
||||
ccw: true,
|
||||
isDashed: isDraftSegment,
|
||||
scale,
|
||||
})
|
||||
const mat = new MeshBasicMaterial({ color })
|
||||
const arcMesh = new Mesh(geometry, mat)
|
||||
const meshType = isDraftSegment
|
||||
? CIRCLE_THREE_POINT_SEGMENT_DASH
|
||||
: CIRCLE_THREE_POINT_SEGMENT_BODY
|
||||
const handle1 = createCircleThreePointHandle(
|
||||
scale,
|
||||
theme,
|
||||
CIRCLE_THREE_POINT_HANDLE1,
|
||||
color
|
||||
)
|
||||
const handle2 = createCircleThreePointHandle(
|
||||
scale,
|
||||
theme,
|
||||
CIRCLE_THREE_POINT_HANDLE2,
|
||||
color
|
||||
)
|
||||
const handle3 = createCircleThreePointHandle(
|
||||
scale,
|
||||
theme,
|
||||
CIRCLE_THREE_POINT_HANDLE3,
|
||||
color
|
||||
)
|
||||
|
||||
arcMesh.userData.type = meshType
|
||||
arcMesh.name = meshType
|
||||
group.userData = {
|
||||
type: CIRCLE_THREE_POINT_SEGMENT,
|
||||
draft: isDraftSegment,
|
||||
id,
|
||||
p1,
|
||||
p2,
|
||||
p3,
|
||||
ccw: true,
|
||||
prevSegment,
|
||||
pathToNode,
|
||||
isSelected,
|
||||
baseColor,
|
||||
}
|
||||
group.name = CIRCLE_THREE_POINT_SEGMENT
|
||||
|
||||
group.add(arcMesh, handle1, handle2, handle3)
|
||||
const updateOverlaysCallback = this.update({
|
||||
prevSegment,
|
||||
input,
|
||||
group,
|
||||
scale,
|
||||
sceneInfra,
|
||||
})
|
||||
if (err(updateOverlaysCallback)) return updateOverlaysCallback
|
||||
|
||||
return {
|
||||
group,
|
||||
updateOverlaysCallback,
|
||||
}
|
||||
}
|
||||
update: SegmentUtils['update'] = ({
|
||||
input,
|
||||
group,
|
||||
scale = 1,
|
||||
sceneInfra,
|
||||
}) => {
|
||||
if (input.type !== 'circle-three-point-segment') {
|
||||
return new Error('Invalid segment type')
|
||||
}
|
||||
const { p1, p2, p3 } = input
|
||||
group.userData.p1 = p1
|
||||
group.userData.p2 = p2
|
||||
group.userData.p3 = p3
|
||||
const { center_x, center_y, radius } = calculate_circle_from_3_points(
|
||||
p1[0],
|
||||
p1[1],
|
||||
p2[0],
|
||||
p2[1],
|
||||
p3[0],
|
||||
p3[1]
|
||||
)
|
||||
const center: [number, number] = [center_x, center_y]
|
||||
const points = [p1, p2, p3]
|
||||
const handles = [
|
||||
CIRCLE_THREE_POINT_HANDLE1,
|
||||
CIRCLE_THREE_POINT_HANDLE2,
|
||||
CIRCLE_THREE_POINT_HANDLE3,
|
||||
].map((handle) => group.getObjectByName(handle) as Group)
|
||||
handles.forEach((handle, i) => {
|
||||
const point = points[i]
|
||||
if (handle && point) {
|
||||
handle.position.set(point[0], point[1], 0)
|
||||
handle.scale.set(scale, scale, scale)
|
||||
handle.visible = true
|
||||
}
|
||||
})
|
||||
|
||||
const pxLength = (2 * radius * Math.PI) / scale
|
||||
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
|
||||
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
|
||||
|
||||
const hoveredParent =
|
||||
sceneInfra.hoveredObject &&
|
||||
getParentGroup(sceneInfra.hoveredObject, [CIRCLE_SEGMENT])
|
||||
let isHandlesVisible = !shouldHideIdle
|
||||
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
|
||||
isHandlesVisible = !shouldHideHover
|
||||
}
|
||||
|
||||
const circleSegmentBody = group.children.find(
|
||||
(child) => child.userData.type === CIRCLE_THREE_POINT_SEGMENT_BODY
|
||||
) as Mesh
|
||||
|
||||
if (circleSegmentBody) {
|
||||
const newGeo = createArcGeometry({
|
||||
radius,
|
||||
center,
|
||||
startAngle: 0,
|
||||
endAngle: Math.PI * 2,
|
||||
ccw: true,
|
||||
scale,
|
||||
})
|
||||
circleSegmentBody.geometry = newGeo
|
||||
}
|
||||
const circleSegmentBodyDashed = group.getObjectByName(
|
||||
CIRCLE_THREE_POINT_SEGMENT_DASH
|
||||
)
|
||||
if (circleSegmentBodyDashed instanceof Mesh) {
|
||||
// consider throttling the whole updateTangentialArcToSegment
|
||||
// if there are more perf considerations going forward
|
||||
circleSegmentBodyDashed.geometry = createArcGeometry({
|
||||
center,
|
||||
radius,
|
||||
ccw: true,
|
||||
// make the start end where the handle is
|
||||
startAngle: Math.PI * 0.25,
|
||||
endAngle: Math.PI * 2.25,
|
||||
isDashed: true,
|
||||
scale,
|
||||
})
|
||||
}
|
||||
return () => {
|
||||
const overlays: SegmentOverlays = {}
|
||||
const points = [p1, p2, p3]
|
||||
const overlayDetails = handles.map((handle, index) => {
|
||||
const currentPoint = points[index]
|
||||
const angle = Math.atan2(
|
||||
currentPoint[1] - center[1],
|
||||
currentPoint[0] - center[0]
|
||||
)
|
||||
return sceneInfra.updateOverlayDetails({
|
||||
handle,
|
||||
group,
|
||||
isHandlesVisible,
|
||||
from: [0, 0],
|
||||
to: [center[0], center[1]],
|
||||
angle: angle,
|
||||
hasThreeDotMenu: index === 0,
|
||||
})
|
||||
})
|
||||
const segmentOverlays: SegmentOverlay[] = []
|
||||
overlayDetails.forEach((payload, index) => {
|
||||
if (payload?.type === 'set-one') {
|
||||
overlays[payload.pathToNodeString] = payload.seg
|
||||
segmentOverlays.push({
|
||||
...payload.seg[0],
|
||||
filterValue: index === 0 ? 'p1' : index === 1 ? 'p2' : 'p3',
|
||||
})
|
||||
}
|
||||
})
|
||||
const segmentOverlayPayload: SegmentOverlayPayload = {
|
||||
type: 'set-one',
|
||||
pathToNodeString:
|
||||
overlayDetails[0]?.type === 'set-one'
|
||||
? overlayDetails[0].pathToNodeString
|
||||
: '',
|
||||
seg: segmentOverlays,
|
||||
}
|
||||
return segmentOverlayPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createProfileStartHandle({
|
||||
from,
|
||||
isDraft = false,
|
||||
scale = 1,
|
||||
theme,
|
||||
isSelected,
|
||||
size = 12,
|
||||
...rest
|
||||
}: {
|
||||
from: Coords2d
|
||||
scale?: number
|
||||
theme: Themes
|
||||
isSelected?: boolean
|
||||
size?: number
|
||||
} & (
|
||||
| { isDraft: true }
|
||||
| { isDraft: false; id: string; pathToNode: PathToNode }
|
||||
)) {
|
||||
const group = new Group()
|
||||
|
||||
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
|
||||
const geometry = new BoxGeometry(size, size, size) // in pixels scaled later
|
||||
const baseColor = getThemeColorForThreeJs(theme)
|
||||
const color = isSelected ? 0x0000ff : baseColor
|
||||
const body = new MeshBasicMaterial({ color })
|
||||
@ -774,6 +1009,32 @@ function createCircleCenterHandle(
|
||||
circleCenterGroup.scale.set(scale, scale, scale)
|
||||
return circleCenterGroup
|
||||
}
|
||||
function createCircleThreePointHandle(
|
||||
scale = 1,
|
||||
theme: Themes,
|
||||
name:
|
||||
| 'circle-three-point-handle1'
|
||||
| 'circle-three-point-handle2'
|
||||
| 'circle-three-point-handle3',
|
||||
color?: number
|
||||
): Group {
|
||||
const circleCenterGroup = new Group()
|
||||
|
||||
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
|
||||
const baseColor = getThemeColorForThreeJs(theme)
|
||||
const body = new MeshBasicMaterial({ color })
|
||||
const mesh = new Mesh(geometry, body)
|
||||
|
||||
circleCenterGroup.add(mesh)
|
||||
|
||||
circleCenterGroup.userData = {
|
||||
type: name,
|
||||
baseColor,
|
||||
}
|
||||
circleCenterGroup.name = name
|
||||
circleCenterGroup.scale.set(scale, scale, scale)
|
||||
return circleCenterGroup
|
||||
}
|
||||
|
||||
function createExtraSegmentHandle(
|
||||
scale: number,
|
||||
@ -1100,4 +1361,5 @@ export const segmentUtils = {
|
||||
straight: new StraightSegment(),
|
||||
tangentialArcTo: new TangentialArcToSegment(),
|
||||
circle: new CircleSegment(),
|
||||
circleThreePoint: new CircleThreePointSegment(),
|
||||
} as const
|
||||
|
||||
@ -57,7 +57,7 @@ function CommandComboBox({
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
(event.metaKey && event.key === 'k') ||
|
||||
event.key === 'Escape'
|
||||
(event.key === 'Backspace' && !event.currentTarget.value)
|
||||
) {
|
||||
event.preventDefault()
|
||||
commandBarActor.send({ type: 'Close' })
|
||||
|
||||
@ -329,7 +329,7 @@ export const FileMachineProvider = ({
|
||||
onSubmit: async (data) => {
|
||||
if (data.method === 'overwrite') {
|
||||
codeManager.updateCodeStateEditor(data.code)
|
||||
await kclManager.executeCode(true)
|
||||
await kclManager.executeCode({ zoomToFit: true })
|
||||
await codeManager.writeToFile()
|
||||
} else if (data.method === 'newFile' && isDesktop()) {
|
||||
send({
|
||||
|
||||
@ -21,7 +21,6 @@ import { ContextMenu, ContextMenuItem } from './ContextMenu'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { FileEntry } from 'lib/project'
|
||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
||||
import { normalizeLineEndings } from 'lib/codeEditor'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
|
||||
function getIndentationCSS(level: number) {
|
||||
@ -190,24 +189,25 @@ const FileTreeItem = ({
|
||||
// Because subtrees only render when they are opened, that means this
|
||||
// only listens when they open. Because this acts like a useEffect, when
|
||||
// the ReactNodes are destroyed, so is this listener :)
|
||||
useFileSystemWatcher(
|
||||
async (eventType, path) => {
|
||||
// Prevents a cyclic read / write causing editor problems such as
|
||||
// misplaced cursor positions.
|
||||
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
|
||||
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
|
||||
return
|
||||
}
|
||||
/** Disabling this in favor of faster file writes until we fix file writing **/
|
||||
/* useFileSystemWatcher(
|
||||
* async (eventType, path) => {
|
||||
* // Prevents a cyclic read / write causing editor problems such as
|
||||
* // misplaced cursor positions.
|
||||
* if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
|
||||
* codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
|
||||
* return
|
||||
* }
|
||||
|
||||
if (isCurrentFile && eventType === 'change') {
|
||||
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
|
||||
code = normalizeLineEndings(code)
|
||||
codeManager.updateCodeStateEditor(code)
|
||||
}
|
||||
fileSend({ type: 'Refresh' })
|
||||
},
|
||||
[fileOrDir.path]
|
||||
)
|
||||
* if (isCurrentFile && eventType === 'change') {
|
||||
* let code = await window.electron.readFile(path, { encoding: 'utf-8' })
|
||||
* code = normalizeLineEndings(code)
|
||||
* codeManager.updateCodeStateEditor(code)
|
||||
* }
|
||||
* fileSend({ type: 'Refresh' })
|
||||
* },
|
||||
* [fileOrDir.path]
|
||||
* ) */
|
||||
|
||||
const showNewTreeEntry =
|
||||
newTreeEntry !== undefined &&
|
||||
@ -263,7 +263,7 @@ const FileTreeItem = ({
|
||||
await codeManager.writeToFile()
|
||||
|
||||
// Prevent seeing the model built one piece at a time when changing files
|
||||
await kclManager.executeCode(true)
|
||||
await kclManager.executeCode({ zoomToFit: true })
|
||||
} else {
|
||||
// Let the lsp servers know we closed a file.
|
||||
onFileClose(currentFile?.path || null, project?.path || null)
|
||||
|
||||
@ -25,7 +25,7 @@ import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import {
|
||||
isCursorInSketchCommandRange,
|
||||
updatePathToNodeFromMap,
|
||||
updateSketchDetailsNodePaths,
|
||||
} from 'lang/util'
|
||||
import {
|
||||
kclManager,
|
||||
@ -65,17 +65,32 @@ import {
|
||||
replaceValueAtNodePath,
|
||||
sketchOnExtrudedFace,
|
||||
sketchOnOffsetPlane,
|
||||
splitPipedProfile,
|
||||
startSketchOnDefault,
|
||||
} from 'lang/modifyAst'
|
||||
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
|
||||
import { artifactIsPlaneWithPaths, isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import {
|
||||
CodeRef,
|
||||
PathToNode,
|
||||
Program,
|
||||
VariableDeclaration,
|
||||
parse,
|
||||
recast,
|
||||
resultIsOk,
|
||||
} from 'lang/wasm'
|
||||
import {
|
||||
artifactIsPlaneWithPaths,
|
||||
doesSketchPipeNeedSplitting,
|
||||
getNodeFromPath,
|
||||
isCursorInFunctionDefinition,
|
||||
traverse,
|
||||
} from 'lang/queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
import { Models } from '@kittycad/lib/dist/types/src'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||
import { err, reportRejection, trap } from 'lib/trap'
|
||||
import { err, reportRejection, trap, reject } from 'lib/trap'
|
||||
import {
|
||||
ExportIntent,
|
||||
EngineConnectionStateType,
|
||||
@ -86,6 +101,10 @@ import { useFileContext } from 'hooks/useFileContext'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { IndexLoaderData } from 'lib/types'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import {
|
||||
getPathsFromArtifact,
|
||||
getPlaneFromArtifact,
|
||||
} from 'lang/std/artifactGraph'
|
||||
import { promptToEditFlow } from 'lib/promptToEdit'
|
||||
import { kclEditorActor } from 'machines/kclEditorMachine'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
@ -163,38 +182,57 @@ export const ModelingMachineProvider = ({
|
||||
'enable copilot': () => {
|
||||
editorManager.setCopilotEnabled(true)
|
||||
},
|
||||
'sketch exit execute': ({ context: { store } }) => {
|
||||
// TODO: Remove this async callback. For some reason eslint wouldn't
|
||||
// let me disable @typescript-eslint/no-misused-promises for the line.
|
||||
;(async () => {
|
||||
// When cancelling the sketch mode we should disable sketch mode within the engine.
|
||||
await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: { type: 'sketch_mode_disable' },
|
||||
// tsc reports this typing as perfectly fine, but eslint is complaining.
|
||||
// It's actually nonsensical, so I'm quieting.
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
'sketch exit execute': async ({
|
||||
context: { store },
|
||||
}): Promise<void> => {
|
||||
// When cancelling the sketch mode we should disable sketch mode within the engine.
|
||||
await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: { type: 'sketch_mode_disable' },
|
||||
})
|
||||
|
||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||
|
||||
if (cameraProjection.current === 'perspective') {
|
||||
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
|
||||
}
|
||||
|
||||
sceneInfra.camControls.syncDirection = 'engineToClient'
|
||||
|
||||
store.videoElement?.pause()
|
||||
|
||||
return kclManager.executeCode().then(() => {
|
||||
if (engineCommandManager.engineConnection?.idleMode) return
|
||||
|
||||
store.videoElement?.play().catch((e) => {
|
||||
console.warn('Video playing was prevented', e)
|
||||
})
|
||||
})
|
||||
|
||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||
|
||||
if (cameraProjection.current === 'perspective') {
|
||||
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
|
||||
}
|
||||
if (cameraProjection.current === 'perspective') {
|
||||
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
|
||||
}
|
||||
|
||||
sceneInfra.camControls.syncDirection = 'engineToClient'
|
||||
sceneInfra.camControls.syncDirection = 'engineToClient'
|
||||
|
||||
store.videoElement?.pause()
|
||||
store.videoElement?.pause()
|
||||
|
||||
return kclManager
|
||||
.executeCode()
|
||||
.then(() => {
|
||||
if (engineCommandManager.engineConnection?.idleMode) return
|
||||
return kclManager
|
||||
.executeCode()
|
||||
.then(() => {
|
||||
if (engineCommandManager.engineConnection?.idleMode) return
|
||||
|
||||
store.videoElement?.play().catch((e) => {
|
||||
console.warn('Video playing was prevented', e)
|
||||
})
|
||||
store.videoElement?.play().catch((e) => {
|
||||
console.warn('Video playing was prevented', e)
|
||||
})
|
||||
.catch(reportRejection)
|
||||
})().catch(reportRejection)
|
||||
})
|
||||
.catch(reportRejection)
|
||||
},
|
||||
'Set mouse state': assign(({ context, event }) => {
|
||||
if (event.type !== 'Set mouse state') return {}
|
||||
@ -254,7 +292,11 @@ export const ModelingMachineProvider = ({
|
||||
'Set Segment Overlays': assign({
|
||||
segmentOverlays: ({ context: { segmentOverlays }, event }) => {
|
||||
if (event.type !== 'Set Segment Overlays') return {}
|
||||
if (event.data.type === 'set-many') return event.data.overlays
|
||||
if (event.data.type === 'add-many')
|
||||
return {
|
||||
...segmentOverlays,
|
||||
...event.data.overlays,
|
||||
}
|
||||
if (event.data.type === 'set-one')
|
||||
return {
|
||||
...segmentOverlays,
|
||||
@ -287,7 +329,7 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
sketchDetails: {
|
||||
...sketchDetails,
|
||||
sketchPathToNode: event.data,
|
||||
sketchEntryNodePath: event.data,
|
||||
},
|
||||
}
|
||||
}),
|
||||
@ -411,9 +453,17 @@ export const ModelingMachineProvider = ({
|
||||
selectionRanges: setSelections.selection,
|
||||
sketchDetails: {
|
||||
...sketchDetails,
|
||||
sketchPathToNode:
|
||||
setSelections.updatedPathToNode ||
|
||||
sketchDetails?.sketchPathToNode ||
|
||||
sketchEntryNodePath:
|
||||
setSelections.updatedSketchEntryNodePath ||
|
||||
sketchDetails?.sketchEntryNodePath ||
|
||||
[],
|
||||
sketchNodePaths:
|
||||
setSelections.updatedSketchNodePaths ||
|
||||
sketchDetails?.sketchNodePaths ||
|
||||
[],
|
||||
planeNodePath:
|
||||
setSelections.updatedPlaneNodePath ||
|
||||
sketchDetails?.planeNodePath ||
|
||||
[],
|
||||
},
|
||||
}
|
||||
@ -566,7 +616,12 @@ export const ModelingMachineProvider = ({
|
||||
if (artifactIsPlaneWithPaths(selectionRanges)) {
|
||||
return true
|
||||
}
|
||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
||||
if (
|
||||
isCursorInFunctionDefinition(
|
||||
kclManager.ast,
|
||||
selectionRanges.graphSelections[0]
|
||||
)
|
||||
)
|
||||
return false
|
||||
return !!isCursorInSketchCommandRange(
|
||||
engineCommandManager.artifactGraph,
|
||||
@ -597,10 +652,32 @@ export const ModelingMachineProvider = ({
|
||||
// this assumes no changes have been made to the sketch besides what we did when entering the sketch
|
||||
// i.e. doesn't account for user's adding code themselves, maybe we need store a flag userEditedSinceSketchMode?
|
||||
const newAst = structuredClone(kclManager.ast)
|
||||
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
|
||||
const varDecIndex = sketchDetails.planeNodePath[1][0]
|
||||
|
||||
const varDec = getNodeFromPath<VariableDeclaration>(
|
||||
newAst,
|
||||
sketchDetails.planeNodePath,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(varDec)) return reject(new Error('No varDec'))
|
||||
const variableName = varDec.node.declaration.id.name
|
||||
let isIdentifierUsed = false
|
||||
traverse(newAst, {
|
||||
enter: (node) => {
|
||||
if (
|
||||
node.type === 'Identifier' &&
|
||||
node.name === variableName
|
||||
) {
|
||||
isIdentifierUsed = true
|
||||
}
|
||||
},
|
||||
})
|
||||
if (isIdentifierUsed) return
|
||||
|
||||
// remove body item at varDecIndex
|
||||
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
||||
await kclManager.executeAstMock(newAst)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(newAst)
|
||||
}
|
||||
sceneInfra.setCallbacks({
|
||||
onClick: () => {},
|
||||
@ -610,7 +687,7 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
),
|
||||
'animate-to-face': fromPromise(async ({ input }) => {
|
||||
if (!input) return undefined
|
||||
if (!input) return null
|
||||
if (input.type === 'extrudeFace' || input.type === 'offsetPlane') {
|
||||
const sketched =
|
||||
input.type === 'extrudeFace'
|
||||
@ -637,7 +714,9 @@ export const ModelingMachineProvider = ({
|
||||
await letEngineAnimateAndSyncCamAfter(engineCommandManager, id)
|
||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||
return {
|
||||
sketchPathToNode: pathToNewSketchNode,
|
||||
sketchEntryNodePath: [],
|
||||
planeNodePath: pathToNewSketchNode,
|
||||
sketchNodePaths: [],
|
||||
zAxis: input.zAxis,
|
||||
yAxis: input.yAxis,
|
||||
origin: input.position,
|
||||
@ -658,7 +737,9 @@ export const ModelingMachineProvider = ({
|
||||
)
|
||||
|
||||
return {
|
||||
sketchPathToNode: pathToNode,
|
||||
sketchEntryNodePath: [],
|
||||
planeNodePath: pathToNode,
|
||||
sketchNodePaths: [],
|
||||
zAxis: input.zAxis,
|
||||
yAxis: input.yAxis,
|
||||
origin: [0, 0, 0],
|
||||
@ -667,12 +748,14 @@ export const ModelingMachineProvider = ({
|
||||
}),
|
||||
'animate-to-sketch': fromPromise(
|
||||
async ({ input: { selectionRanges } }) => {
|
||||
const sourceRange =
|
||||
selectionRanges.graphSelections[0]?.codeRef?.range
|
||||
const sketchPathToNode = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
sourceRange
|
||||
const sketchPathToNode =
|
||||
selectionRanges.graphSelections[0]?.codeRef?.pathToNode
|
||||
const plane = getPlaneFromArtifact(
|
||||
selectionRanges.graphSelections[0].artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(plane)) return Promise.reject(plane)
|
||||
|
||||
const info = await getSketchOrientationDetails(
|
||||
sketchPathToNode || []
|
||||
)
|
||||
@ -680,8 +763,22 @@ export const ModelingMachineProvider = ({
|
||||
engineCommandManager,
|
||||
info?.sketchDetails?.faceId || ''
|
||||
)
|
||||
return {
|
||||
const sketchPaths = getPathsFromArtifact({
|
||||
artifact: selectionRanges.graphSelections[0].artifact,
|
||||
sketchPathToNode: sketchPathToNode || [],
|
||||
})
|
||||
if (err(sketchPaths)) return Promise.reject(sketchPaths)
|
||||
let codeRef =
|
||||
'faceCodeRef' in plane && plane.faceCodeRef
|
||||
? plane.faceCodeRef
|
||||
: 'codeRef' in plane && plane.codeRef
|
||||
? plane.codeRef
|
||||
: null
|
||||
if (!codeRef) return Promise.reject(new Error('No plane codeRef'))
|
||||
return {
|
||||
sketchEntryNodePath: sketchPathToNode || [],
|
||||
sketchNodePaths: sketchPaths,
|
||||
planeNodePath: codeRef.pathToNode,
|
||||
zAxis: info.sketchDetails.zAxis || null,
|
||||
yAxis: info.sketchDetails.yAxis || null,
|
||||
origin: info.sketchDetails.origin.map(
|
||||
@ -694,7 +791,7 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
'Get horizontal info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await applyConstraintHorzVertDistance({
|
||||
constraint: 'setHorzDistance',
|
||||
selectionRanges,
|
||||
@ -706,13 +803,23 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -733,13 +840,15 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
'Get vertical info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await applyConstraintHorzVertDistance({
|
||||
constraint: 'setVertDistance',
|
||||
selectionRanges,
|
||||
@ -750,13 +859,23 @@ export const ModelingMachineProvider = ({
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -777,7 +896,9 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -787,14 +908,15 @@ export const ModelingMachineProvider = ({
|
||||
selectionRanges,
|
||||
})
|
||||
if (err(info)) return Promise.reject(info)
|
||||
const { modifiedAst, pathToNodeMap } = await (info.enabled
|
||||
? applyConstraintAngleBetween({
|
||||
selectionRanges,
|
||||
})
|
||||
: applyConstraintAngleLength({
|
||||
selectionRanges,
|
||||
angleOrLength: 'setAngle',
|
||||
}))
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await (info.enabled
|
||||
? applyConstraintAngleBetween({
|
||||
selectionRanges,
|
||||
})
|
||||
: applyConstraintAngleLength({
|
||||
selectionRanges,
|
||||
angleOrLength: 'setAngle',
|
||||
}))
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
@ -803,13 +925,23 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -830,7 +962,9 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -845,20 +979,30 @@ export const ModelingMachineProvider = ({
|
||||
length: lengthValue,
|
||||
})
|
||||
if (err(constraintResult)) return Promise.reject(constraintResult)
|
||||
const { modifiedAst, pathToNodeMap } = constraintResult
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
constraintResult
|
||||
const pResult = parse(recast(modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -879,13 +1023,15 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
'Get perpendicular distance info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await applyConstraintIntersect({
|
||||
selectionRanges,
|
||||
})
|
||||
@ -895,13 +1041,22 @@ export const ModelingMachineProvider = ({
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -922,13 +1077,15 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
'Get ABS X info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await applyConstraintAbsDistance({
|
||||
constraint: 'xAbs',
|
||||
selectionRanges,
|
||||
@ -939,13 +1096,22 @@ export const ModelingMachineProvider = ({
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -966,13 +1132,15 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
'Get ABS Y info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await applyConstraintAbsDistance({
|
||||
constraint: 'yAbs',
|
||||
selectionRanges,
|
||||
@ -983,13 +1151,22 @@ export const ModelingMachineProvider = ({
|
||||
const _modifiedAst = pResult.program
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex,
|
||||
})
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -1010,7 +1187,9 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -1030,9 +1209,11 @@ export const ModelingMachineProvider = ({
|
||||
let result: {
|
||||
modifiedAst: Node<Program>
|
||||
pathToReplaced: PathToNode | null
|
||||
exprInsertIndex: number
|
||||
} = {
|
||||
modifiedAst: parsed,
|
||||
pathToReplaced: null,
|
||||
exprInsertIndex: -1,
|
||||
}
|
||||
// If the user provided a constant name,
|
||||
// we need to insert the named constant
|
||||
@ -1062,6 +1243,7 @@ export const ModelingMachineProvider = ({
|
||||
result = {
|
||||
modifiedAst: parseResultAfterInsertion.program,
|
||||
pathToReplaced: astAfterReplacement.pathToReplaced,
|
||||
exprInsertIndex: astAfterReplacement.exprInsertIndex,
|
||||
}
|
||||
} else if ('valueText' in data.namedValue) {
|
||||
// If they didn't provide a constant name,
|
||||
@ -1092,10 +1274,22 @@ export const ModelingMachineProvider = ({
|
||||
parsed = parsed as Node<Program>
|
||||
if (!result.pathToReplaced)
|
||||
return Promise.reject(new Error('No path to replaced node'))
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex: result.exprInsertIndex,
|
||||
})
|
||||
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
result.pathToReplaced || [],
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
parsed,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
@ -1116,7 +1310,168 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode: result.pathToReplaced,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
'set-up-draft-circle': fromPromise(
|
||||
async ({ input: { sketchDetails, data } }) => {
|
||||
if (!sketchDetails || !data)
|
||||
return reject('No sketch details or data')
|
||||
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||
|
||||
const result = await sceneEntitiesManager.setupDraftCircle(
|
||||
sketchDetails.sketchEntryNodePath,
|
||||
sketchDetails.sketchNodePaths,
|
||||
sketchDetails.planeNodePath,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin,
|
||||
data
|
||||
)
|
||||
if (err(result)) return reject(result)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||
|
||||
return result
|
||||
}
|
||||
),
|
||||
'set-up-draft-circle-three-point': fromPromise(
|
||||
async ({ input: { sketchDetails, data } }) => {
|
||||
if (!sketchDetails || !data)
|
||||
return reject('No sketch details or data')
|
||||
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||
|
||||
const result =
|
||||
await sceneEntitiesManager.setupDraftCircleThreePoint(
|
||||
sketchDetails.sketchEntryNodePath,
|
||||
sketchDetails.sketchNodePaths,
|
||||
sketchDetails.planeNodePath,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin,
|
||||
data.p1,
|
||||
data.p2
|
||||
)
|
||||
if (err(result)) return reject(result)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||
|
||||
return result
|
||||
}
|
||||
),
|
||||
'set-up-draft-rectangle': fromPromise(
|
||||
async ({ input: { sketchDetails, data } }) => {
|
||||
if (!sketchDetails || !data)
|
||||
return reject('No sketch details or data')
|
||||
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||
|
||||
const result = await sceneEntitiesManager.setupDraftRectangle(
|
||||
sketchDetails.sketchEntryNodePath,
|
||||
sketchDetails.sketchNodePaths,
|
||||
sketchDetails.planeNodePath,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin,
|
||||
data
|
||||
)
|
||||
if (err(result)) return reject(result)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||
|
||||
return result
|
||||
}
|
||||
),
|
||||
'set-up-draft-center-rectangle': fromPromise(
|
||||
async ({ input: { sketchDetails, data } }) => {
|
||||
if (!sketchDetails || !data)
|
||||
return reject('No sketch details or data')
|
||||
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||
const result = await sceneEntitiesManager.setupDraftCenterRectangle(
|
||||
sketchDetails.sketchEntryNodePath,
|
||||
sketchDetails.sketchNodePaths,
|
||||
sketchDetails.planeNodePath,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin,
|
||||
data
|
||||
)
|
||||
if (err(result)) return reject(result)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||
|
||||
return result
|
||||
}
|
||||
),
|
||||
'setup-client-side-sketch-segments': fromPromise(
|
||||
async ({ input: { sketchDetails, selectionRanges } }) => {
|
||||
if (!sketchDetails) return
|
||||
if (!sketchDetails.sketchEntryNodePath.length) return
|
||||
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
|
||||
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||
}
|
||||
sceneInfra.resetMouseListeners()
|
||||
await sceneEntitiesManager.setupSketch({
|
||||
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
forward: sketchDetails.zAxis,
|
||||
up: sketchDetails.yAxis,
|
||||
position: sketchDetails.origin,
|
||||
maybeModdedAst: kclManager.ast,
|
||||
selectionRanges,
|
||||
})
|
||||
sceneInfra.resetMouseListeners()
|
||||
|
||||
sceneEntitiesManager.setupSketchIdleCallbacks({
|
||||
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
|
||||
forward: sketchDetails.zAxis,
|
||||
up: sketchDetails.yAxis,
|
||||
position: sketchDetails.origin,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
// We will want to pass sketchTools here
|
||||
// to add their interactions
|
||||
})
|
||||
|
||||
// We will want to update the context with sketchTools.
|
||||
// They'll be used for their .destroy() in tearDownSketch
|
||||
return undefined
|
||||
}
|
||||
),
|
||||
'split-sketch-pipe-if-needed': fromPromise(
|
||||
async ({ input: { sketchDetails } }) => {
|
||||
if (!sketchDetails) return reject('No sketch details')
|
||||
const existingSketchInfoNoOp = {
|
||||
updatedEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
updatedSketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
updatedPlaneNodePath: sketchDetails.planeNodePath,
|
||||
} as const
|
||||
if (
|
||||
!sketchDetails.sketchNodePaths.length &&
|
||||
sketchDetails.planeNodePath.length
|
||||
) {
|
||||
// new sketch, no profiles yet
|
||||
return existingSketchInfoNoOp
|
||||
}
|
||||
const doesNeedSplitting = doesSketchPipeNeedSplitting(
|
||||
kclManager.ast,
|
||||
sketchDetails.sketchEntryNodePath
|
||||
)
|
||||
if (err(doesNeedSplitting)) return reject(doesNeedSplitting)
|
||||
if (!doesNeedSplitting) return existingSketchInfoNoOp
|
||||
|
||||
const splitResult = splitPipedProfile(
|
||||
kclManager.ast,
|
||||
sketchDetails.sketchEntryNodePath
|
||||
)
|
||||
if (err(splitResult)) return reject(splitResult)
|
||||
|
||||
await kclManager.executeAstMock(splitResult.modifiedAst)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||
splitResult.modifiedAst
|
||||
)
|
||||
return {
|
||||
updatedEntryNodePath: splitResult.pathToProfile,
|
||||
updatedSketchNodePaths: [splitResult.pathToProfile],
|
||||
updatedPlaneNodePath: sketchDetails.planeNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
@ -187,7 +187,7 @@ export const SettingsAuthProviderBase = ({
|
||||
) {
|
||||
// Unit changes requires a re-exec of code
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
kclManager.executeCode(true)
|
||||
kclManager.executeCode({ zoomToFit: true })
|
||||
} else {
|
||||
// For any future logging we'd like to do
|
||||
// console.log(
|
||||
|
||||
@ -2,7 +2,12 @@ import { SVGProps } from 'react'
|
||||
|
||||
export const Spinner = (props: SVGProps<SVGSVGElement>) => {
|
||||
return (
|
||||
<svg viewBox="0 0 10 10" className={'w-8 h-8'} {...props}>
|
||||
<svg
|
||||
data-testid="spinner"
|
||||
viewBox="0 0 10 10"
|
||||
className={'w-8 h-8'}
|
||||
{...props}
|
||||
>
|
||||
<circle
|
||||
cx="5"
|
||||
cy="5"
|
||||
|
||||
@ -60,7 +60,7 @@ export const Stream = () => {
|
||||
*/
|
||||
function executeCodeAndPlayStream() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
kclManager.executeCode(true).then(async () => {
|
||||
kclManager.executeCode({ zoomToFit: true }).then(async () => {
|
||||
await videoRef.current?.play().catch((e) => {
|
||||
console.warn('Video playing was prevented', e, videoRef.current)
|
||||
})
|
||||
|
||||
@ -136,6 +136,7 @@ export async function applyConstraintIntersect({
|
||||
}): Promise<{
|
||||
modifiedAst: Node<Program>
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const info = intersectInfo({
|
||||
selectionRanges,
|
||||
@ -174,6 +175,7 @@ export async function applyConstraintIntersect({
|
||||
return {
|
||||
modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex: -1,
|
||||
}
|
||||
}
|
||||
// transform again but forcing certain values
|
||||
@ -192,6 +194,7 @@ export async function applyConstraintIntersect({
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
||||
transform2
|
||||
|
||||
let exprInsertIndex = -1
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
@ -204,9 +207,11 @@ export async function applyConstraintIntersect({
|
||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||
})
|
||||
exprInsertIndex = newVariableInsertIndex
|
||||
}
|
||||
return {
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap: _pathToNodeMap,
|
||||
exprInsertIndex,
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ export function removeConstrainingValuesInfo({
|
||||
| Error {
|
||||
const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
|
||||
const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
|
||||
if (err(tmp)) return tmp
|
||||
if (tmp instanceof Error) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err1 = _nodes.find(err)
|
||||
|
||||
@ -92,6 +92,7 @@ export async function applyConstraintAbsDistance({
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const info = absDistanceInfo({
|
||||
selectionRanges,
|
||||
@ -131,6 +132,7 @@ export async function applyConstraintAbsDistance({
|
||||
if (err(transform2)) return Promise.reject(transform2)
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap } = transform2
|
||||
|
||||
let exprInsertIndex = -1
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
@ -143,8 +145,9 @@ export async function applyConstraintAbsDistance({
|
||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||
})
|
||||
exprInsertIndex = newVariableInsertIndex
|
||||
}
|
||||
return { modifiedAst: _modifiedAst, pathToNodeMap }
|
||||
return { modifiedAst: _modifiedAst, pathToNodeMap, exprInsertIndex }
|
||||
}
|
||||
|
||||
export function applyConstraintAxisAlign({
|
||||
|
||||
@ -86,6 +86,7 @@ export async function applyConstraintAngleBetween({
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const info = angleBetweenInfo({ selectionRanges })
|
||||
if (err(info)) return Promise.reject(info)
|
||||
@ -122,6 +123,7 @@ export async function applyConstraintAngleBetween({
|
||||
return {
|
||||
modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex: -1,
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,6 +143,7 @@ export async function applyConstraintAngleBetween({
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
||||
transformed2
|
||||
|
||||
let exprInsertIndex = -1
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
@ -153,9 +156,11 @@ export async function applyConstraintAngleBetween({
|
||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||
})
|
||||
exprInsertIndex = newVariableInsertIndex
|
||||
}
|
||||
return {
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap: _pathToNodeMap,
|
||||
exprInsertIndex,
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,15 +87,13 @@ export function horzVertDistanceInfo({
|
||||
export async function applyConstraintHorzVertDistance({
|
||||
selectionRanges,
|
||||
constraint,
|
||||
// TODO align will always be false (covered by synconous applyConstraintHorzVertAlign), remove it
|
||||
isAlign = false,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||
isAlign?: false
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const info = horzVertDistanceInfo({
|
||||
selectionRanges: selectionRanges,
|
||||
@ -133,13 +131,12 @@ export async function applyConstraintHorzVertDistance({
|
||||
return {
|
||||
modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex: -1,
|
||||
}
|
||||
} else {
|
||||
if (!isExprBinaryPart(valueNode))
|
||||
return Promise.reject('Invalid valueNode, is not a BinaryPart')
|
||||
let finalValue = isAlign
|
||||
? createLiteral(0)
|
||||
: removeDoubleNegatives(valueNode, sign, variableName)
|
||||
let finalValue = removeDoubleNegatives(valueNode, sign, variableName)
|
||||
// transform again but forcing certain values
|
||||
const transformed = transformSecondarySketchLinesTagFirst({
|
||||
ast: kclManager.ast,
|
||||
@ -152,6 +149,7 @@ export async function applyConstraintHorzVertDistance({
|
||||
|
||||
if (err(transformed)) return Promise.reject(transformed)
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap } = transformed
|
||||
let exprInsertIndex = -1
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
@ -164,10 +162,12 @@ export async function applyConstraintHorzVertDistance({
|
||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||
})
|
||||
exprInsertIndex = newVariableInsertIndex
|
||||
}
|
||||
return {
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,10 +74,14 @@ export async function applyConstraintLength({
|
||||
}: {
|
||||
length: KclCommandValue
|
||||
selectionRanges: Selections
|
||||
}) {
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const ast = kclManager.ast
|
||||
const angleLength = angleLengthInfo({ selectionRanges })
|
||||
if (err(angleLength)) return angleLength
|
||||
if (err(angleLength)) return Promise.reject(angleLength)
|
||||
const { transforms } = angleLength
|
||||
|
||||
let distanceExpression: Expr = length.valueAst
|
||||
@ -98,7 +102,7 @@ export async function applyConstraintLength({
|
||||
}
|
||||
|
||||
if (!isExprBinaryPart(distanceExpression)) {
|
||||
return new Error('Invalid valueNode, is not a BinaryPart')
|
||||
return Promise.reject('Invalid valueNode, is not a BinaryPart')
|
||||
}
|
||||
|
||||
const retval = transformAstSketchLines({
|
||||
@ -116,6 +120,12 @@ export async function applyConstraintLength({
|
||||
return {
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex:
|
||||
'variableName' in length &&
|
||||
length.variableName &&
|
||||
length.insertIndex !== undefined
|
||||
? length.insertIndex
|
||||
: -1,
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,6 +138,7 @@ export async function applyConstraintAngleLength({
|
||||
}): Promise<{
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
exprInsertIndex: number
|
||||
}> {
|
||||
const angleLength = angleLengthInfo({ selectionRanges, angleOrLength })
|
||||
if (err(angleLength)) return Promise.reject(angleLength)
|
||||
@ -212,5 +223,6 @@ export async function applyConstraintAngleLength({
|
||||
return {
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap,
|
||||
exprInsertIndex: variableName ? newVariableInsertIndex : -1,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,31 +1,9 @@
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { changeKclSettings, unitLengthToUnitLen } from 'lang/wasm'
|
||||
import { baseUnitLabels, baseUnitsUnion } from 'lib/settings/settingsTypes'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { err, reportRejection } from 'lib/trap'
|
||||
import { useEffect, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
export function UnitsMenu() {
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const [hasPerFileLengthUnit, setHasPerFileLengthUnit] = useState(
|
||||
Boolean(kclManager.fileSettings.defaultLengthUnit)
|
||||
)
|
||||
const [lengthSetting, setLengthSetting] = useState(
|
||||
kclManager.fileSettings.defaultLengthUnit ||
|
||||
settings.context.modeling.defaultUnit.current
|
||||
)
|
||||
useEffect(() => {
|
||||
setHasPerFileLengthUnit(Boolean(kclManager.fileSettings.defaultLengthUnit))
|
||||
setLengthSetting(
|
||||
kclManager.fileSettings.defaultLengthUnit ||
|
||||
settings.context.modeling.defaultUnit.current
|
||||
)
|
||||
}, [
|
||||
kclManager.fileSettings.defaultLengthUnit,
|
||||
settings.context.modeling.defaultUnit.current,
|
||||
])
|
||||
return (
|
||||
<Popover className="relative pointer-events-auto">
|
||||
{({ close }) => (
|
||||
@ -40,7 +18,7 @@ export function UnitsMenu() {
|
||||
<div className="absolute w-[1px] h-[1em] bg-primary right-0 top-1/2 -translate-y-1/2"></div>
|
||||
</div>
|
||||
<span className="sr-only">Current units are: </span>
|
||||
{lengthSetting}
|
||||
{settings.context.modeling.defaultUnit.current}
|
||||
</Popover.Button>
|
||||
<Popover.Panel
|
||||
className={`absolute bottom-full right-0 mb-2 w-48 bg-chalkboard-10 dark:bg-chalkboard-90
|
||||
@ -53,40 +31,18 @@ export function UnitsMenu() {
|
||||
<button
|
||||
className="flex items-center gap-2 m-0 py-1.5 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left"
|
||||
onClick={() => {
|
||||
if (hasPerFileLengthUnit) {
|
||||
const newCode = changeKclSettings(codeManager.code, {
|
||||
defaultLengthUnits: unitLengthToUnitLen(unit),
|
||||
defaultAngleUnits: { type: 'Degrees' },
|
||||
})
|
||||
if (err(newCode)) {
|
||||
toast.error(
|
||||
`Failed to set per-file units: ${newCode.message}`
|
||||
)
|
||||
} else {
|
||||
codeManager.updateCodeStateEditor(newCode)
|
||||
Promise.all([
|
||||
codeManager.writeToFile(),
|
||||
kclManager.executeCode(),
|
||||
])
|
||||
.then(() => {
|
||||
toast.success(`Updated per-file units to ${unit}`)
|
||||
})
|
||||
.catch(reportRejection)
|
||||
}
|
||||
} else {
|
||||
settings.send({
|
||||
type: 'set.modeling.defaultUnit',
|
||||
data: {
|
||||
level: 'project',
|
||||
value: unit,
|
||||
},
|
||||
})
|
||||
}
|
||||
settings.send({
|
||||
type: 'set.modeling.defaultUnit',
|
||||
data: {
|
||||
level: 'project',
|
||||
value: unit,
|
||||
},
|
||||
})
|
||||
close()
|
||||
}}
|
||||
>
|
||||
<span className="flex-1">{baseUnitLabels[unit]}</span>
|
||||
{unit === lengthSetting && (
|
||||
{unit === settings.context.modeling.defaultUnit.current && (
|
||||
<span className="text-chalkboard-60">current</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
@ -25,7 +25,7 @@ import {
|
||||
SourceRange,
|
||||
topLevelRange,
|
||||
} from 'lang/wasm'
|
||||
import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
|
||||
import { getNodeFromPath } from './queryAst'
|
||||
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
||||
import { Diagnostic } from '@codemirror/lint'
|
||||
import { markOnce } from 'lib/performance'
|
||||
@ -35,11 +35,11 @@ import {
|
||||
ModelingCmdReq_type,
|
||||
} from '@kittycad/lib/dist/types/src/models'
|
||||
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
||||
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
|
||||
|
||||
interface ExecuteArgs {
|
||||
ast?: Node<Program>
|
||||
zoomToFit?: boolean
|
||||
isPartialExecution?: boolean
|
||||
executionId?: number
|
||||
zoomOnRangeAndType?: {
|
||||
range: SourceRange
|
||||
@ -71,7 +71,6 @@ export class KclManager {
|
||||
private _wasmInitFailed = true
|
||||
private _hasErrors = false
|
||||
private _switchedFiles = false
|
||||
private _fileSettings: KclSettingsAnnotation = {}
|
||||
|
||||
engineCommandManager: EngineCommandManager
|
||||
|
||||
@ -370,13 +369,6 @@ export class KclManager {
|
||||
await this.disableSketchMode()
|
||||
}
|
||||
|
||||
let fileSettings = getSettingsAnnotation(ast)
|
||||
if (err(fileSettings)) {
|
||||
console.error(fileSettings)
|
||||
fileSettings = {}
|
||||
}
|
||||
this.fileSettings = fileSettings
|
||||
|
||||
this.logs = logs
|
||||
this.errors = errors
|
||||
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
||||
@ -388,12 +380,10 @@ export class KclManager {
|
||||
}
|
||||
this.ast = { ...ast }
|
||||
// updateArtifactGraph relies on updated executeState/programMemory
|
||||
this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
|
||||
await this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
|
||||
this._executeCallback()
|
||||
if (!isInterrupted) {
|
||||
if (!isInterrupted)
|
||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||
}
|
||||
|
||||
this.engineCommandManager.addCommandLog({
|
||||
type: 'execution-done',
|
||||
data: null,
|
||||
@ -422,7 +412,14 @@ export class KclManager {
|
||||
|
||||
// NOTE: this always updates the code state and editor.
|
||||
// DO NOT CALL THIS from codemirror ever.
|
||||
async executeAstMock(ast: Program = this._ast) {
|
||||
async executeAstMock(
|
||||
ast: Program = this._ast,
|
||||
{
|
||||
updates,
|
||||
}: {
|
||||
updates: 'none' | 'artifactRanges'
|
||||
} = { updates: 'none' }
|
||||
) {
|
||||
await this.ensureWasmInit()
|
||||
|
||||
const newCode = recast(ast)
|
||||
@ -446,19 +443,51 @@ export class KclManager {
|
||||
|
||||
this._logs = logs
|
||||
this.addDiagnostics(kclErrorsToDiagnostics(errors))
|
||||
|
||||
this._execState = execState
|
||||
this._programMemory = execState.memory
|
||||
if (!errors.length) {
|
||||
this.lastSuccessfulProgramMemory = execState.memory
|
||||
this.lastSuccessfulOperations = execState.operations
|
||||
}
|
||||
if (updates !== 'artifactRanges') return
|
||||
|
||||
// TODO the below seems like a work around, I wish there's a comment explaining exactly what
|
||||
// problem this solves, but either way we should strive to remove it.
|
||||
Array.from(this.engineCommandManager.artifactGraph).forEach(
|
||||
([commandId, artifact]) => {
|
||||
if (!('codeRef' in artifact && artifact.codeRef)) return
|
||||
const _node1 = getNodeFromPath<Node<CallExpression | CallExpressionKw>>(
|
||||
this.ast,
|
||||
artifact.codeRef.pathToNode,
|
||||
['CallExpression', 'CallExpressionKw']
|
||||
)
|
||||
if (err(_node1)) return
|
||||
const { node } = _node1
|
||||
if (node.type !== 'CallExpression' && node.type !== 'CallExpressionKw')
|
||||
return
|
||||
const [oldStart, oldEnd] = artifact.codeRef.range
|
||||
if (oldStart === 0 && oldEnd === 0) return
|
||||
if (oldStart === node.start && oldEnd === node.end) return
|
||||
this.engineCommandManager.artifactGraph.set(commandId, {
|
||||
...artifact,
|
||||
codeRef: {
|
||||
...artifact.codeRef,
|
||||
range: topLevelRange(node.start, node.end),
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
cancelAllExecutions() {
|
||||
this._cancelTokens.forEach((_, key) => {
|
||||
this._cancelTokens.set(key, true)
|
||||
})
|
||||
}
|
||||
async executeCode(zoomToFit?: boolean): Promise<void> {
|
||||
async executeCode(opts?: {
|
||||
zoomToFit?: true
|
||||
isPartialExecution?: true
|
||||
}): Promise<void> {
|
||||
const ast = await this.safeParse(codeManager.code)
|
||||
|
||||
if (!ast) {
|
||||
@ -466,10 +495,10 @@ export class KclManager {
|
||||
return
|
||||
}
|
||||
|
||||
zoomToFit = this.tryToZoomToFitOnCodeUpdate(ast, zoomToFit)
|
||||
// zoomToFit = this.tryToZoomToFitOnCodeUpdate(ast, opts?.zoomToFit)
|
||||
|
||||
this.ast = { ...ast }
|
||||
return this.executeAst({ zoomToFit })
|
||||
return this.executeAst(opts)
|
||||
}
|
||||
/**
|
||||
* This will override the zoom to fit to zoom into the model if the previous AST was empty.
|
||||
@ -673,14 +702,6 @@ export class KclManager {
|
||||
_isAstEmpty(ast: Node<Program>) {
|
||||
return ast.start === 0 && ast.end === 0 && ast.body.length === 0
|
||||
}
|
||||
|
||||
get fileSettings() {
|
||||
return this._fileSettings
|
||||
}
|
||||
|
||||
set fileSettings(settings: KclSettingsAnnotation) {
|
||||
this._fileSettings = settings
|
||||
}
|
||||
}
|
||||
|
||||
const defaultSelectionFilter: EntityType_type[] = [
|
||||
|
||||
@ -55,7 +55,6 @@ const mySketch001 = startSketchOn('XY')
|
||||
],
|
||||
id: expect.any(String),
|
||||
artifactId: expect.any(String),
|
||||
originalId: expect.any(String),
|
||||
units: {
|
||||
type: 'Mm',
|
||||
},
|
||||
@ -99,7 +98,6 @@ const mySketch001 = startSketchOn('XY')
|
||||
],
|
||||
sketch: {
|
||||
id: expect.any(String),
|
||||
originalId: expect.any(String),
|
||||
artifactId: expect.any(String),
|
||||
units: {
|
||||
type: 'Mm',
|
||||
@ -205,7 +203,6 @@ const sk2 = startSketchOn('XY')
|
||||
],
|
||||
sketch: {
|
||||
id: expect.any(String),
|
||||
originalId: expect.any(String),
|
||||
artifactId: expect.any(String),
|
||||
__meta: expect.any(Array),
|
||||
on: expect.any(Object),
|
||||
@ -311,7 +308,6 @@ const sk2 = startSketchOn('XY')
|
||||
],
|
||||
sketch: {
|
||||
id: expect.any(String),
|
||||
originalId: expect.any(String),
|
||||
artifactId: expect.any(String),
|
||||
units: {
|
||||
type: 'Mm',
|
||||
|
||||
@ -157,7 +157,7 @@ export default class CodeManager {
|
||||
toast.error('Error saving file, please check file permissions')
|
||||
reject(err)
|
||||
})
|
||||
}, 1000)
|
||||
}, 10)
|
||||
})
|
||||
} else {
|
||||
safeLSSetItem(PERSIST_CODE_KEY, this.code)
|
||||
|
||||
@ -221,7 +221,6 @@ const newVar = myVar + 1`
|
||||
},
|
||||
],
|
||||
id: expect.any(String),
|
||||
originalId: expect.any(String),
|
||||
artifactId: expect.any(String),
|
||||
units: {
|
||||
type: 'Mm',
|
||||
|
||||
@ -28,14 +28,7 @@ try {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
child_process.spawnSync('git', [
|
||||
'clone',
|
||||
'--single-branch',
|
||||
'--branch',
|
||||
'achalmers/kw-shell',
|
||||
URL_GIT_KCL_SAMPLES,
|
||||
DIR_KCL_SAMPLES,
|
||||
])
|
||||
child_process.spawnSync('git', ['clone', URL_GIT_KCL_SAMPLES, DIR_KCL_SAMPLES])
|
||||
|
||||
// @ts-expect-error
|
||||
let files = await fs.readdir(DIR_KCL_SAMPLES)
|
||||
|
||||
@ -27,6 +27,7 @@ export type ToolTip =
|
||||
| 'angledLineThatIntersects'
|
||||
| 'tangentialArcTo'
|
||||
| 'circle'
|
||||
| 'circleThreePoint'
|
||||
|
||||
export const toolTips: Array<ToolTip> = [
|
||||
'line',
|
||||
@ -42,6 +43,7 @@ export const toolTips: Array<ToolTip> = [
|
||||
'yLineTo',
|
||||
'angledLineThatIntersects',
|
||||
'tangentialArcTo',
|
||||
'circleThreePoint',
|
||||
]
|
||||
|
||||
export async function executeAst({
|
||||
@ -69,7 +71,6 @@ export async function executeAst({
|
||||
: executor(ast, engineCommandManager, path))
|
||||
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
|
||||
return {
|
||||
logs: [],
|
||||
errors: [],
|
||||
|
||||
@ -25,6 +25,7 @@ import {
|
||||
deleteSegmentFromPipeExpression,
|
||||
removeSingleConstraintInfo,
|
||||
deleteFromSelection,
|
||||
splitPipedProfile,
|
||||
} from './modifyAst'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { findUsesOfTagInPipe } from './queryAst'
|
||||
@ -931,3 +932,63 @@ sketch002 = startSketchOn({
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('Testing splitPipedProfile', () => {
|
||||
it('should split the pipe expression correctly', () => {
|
||||
const codeBefore = `part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([1, 2], %)
|
||||
|> line([3, 4], %)
|
||||
|> line([5, 6], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, part001)
|
||||
`
|
||||
|
||||
const expectedCodeAfter = `sketch001 = startSketchOn('XZ')
|
||||
part001 = startProfileAt([1, 2], sketch001)
|
||||
|> line([3, 4], %)
|
||||
|> line([5, 6], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, part001)
|
||||
`
|
||||
|
||||
const ast = assertParse(codeBefore)
|
||||
|
||||
const codeOfInterest = `startSketchOn('XZ')`
|
||||
const range: [number, number, number] = [
|
||||
codeBefore.indexOf(codeOfInterest),
|
||||
codeBefore.indexOf(codeOfInterest) + codeOfInterest.length,
|
||||
0,
|
||||
]
|
||||
const pathToPipe = getNodePathFromSourceRange(ast, range)
|
||||
|
||||
const result = splitPipedProfile(ast, pathToPipe)
|
||||
|
||||
if (err(result)) throw result
|
||||
|
||||
const newCode = recast(result.modifiedAst)
|
||||
if (err(newCode)) throw newCode
|
||||
expect(newCode.trim()).toBe(expectedCodeAfter.trim())
|
||||
})
|
||||
it('should return error for already split pipe', () => {
|
||||
const codeBefore = `sketch001 = startSketchOn('XZ')
|
||||
part001 = startProfileAt([1, 2], sketch001)
|
||||
|> line([3, 4], %)
|
||||
|> line([5, 6], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, part001)
|
||||
`
|
||||
|
||||
const ast = assertParse(codeBefore)
|
||||
|
||||
const codeOfInterest = `startProfileAt([1, 2], sketch001)`
|
||||
const range: [number, number, number] = [
|
||||
codeBefore.indexOf(codeOfInterest),
|
||||
codeBefore.indexOf(codeOfInterest) + codeOfInterest.length,
|
||||
0,
|
||||
]
|
||||
const pathToPipe = getNodePathFromSourceRange(ast, range)
|
||||
|
||||
const result = splitPipedProfile(ast, pathToPipe)
|
||||
expect(result instanceof Error).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
SourceRange,
|
||||
sketchFromKclValue,
|
||||
isPathToNodeNumber,
|
||||
parse,
|
||||
formatNumber,
|
||||
} from './wasm'
|
||||
import {
|
||||
@ -31,6 +32,8 @@ import {
|
||||
getNodeFromPath,
|
||||
isNodeSafeToReplace,
|
||||
traverse,
|
||||
getBodyIndex,
|
||||
isCallExprWithName,
|
||||
ARG_INDEX_FIELD,
|
||||
LABELED_ARG_FIELD,
|
||||
} from './queryAst'
|
||||
@ -56,6 +59,8 @@ import { Models } from '@kittycad/lib'
|
||||
import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
||||
import { Artifact, getPathsFromArtifact } from './std/artifactGraph'
|
||||
import { BodyItem } from 'wasm-lib/kcl/bindings/BodyItem'
|
||||
import { findKwArg } from './util'
|
||||
import { deleteEdgeTreatment } from './modifyAst/addEdgeTreatment'
|
||||
|
||||
@ -90,41 +95,54 @@ export function startSketchOnDefault(
|
||||
}
|
||||
}
|
||||
|
||||
export function addStartProfileAt(
|
||||
export function insertNewStartProfileAt(
|
||||
node: Node<Program>,
|
||||
pathToNode: PathToNode,
|
||||
at: [number, number]
|
||||
): { modifiedAst: Node<Program>; pathToNode: PathToNode } | Error {
|
||||
const _node1 = getNodeFromPath<VariableDeclaration>(
|
||||
sketchEntryNodePath: PathToNode,
|
||||
sketchNodePaths: PathToNode[],
|
||||
planeNodePath: PathToNode,
|
||||
at: [number, number],
|
||||
insertType: 'start' | 'end' = 'end'
|
||||
):
|
||||
| {
|
||||
modifiedAst: Node<Program>
|
||||
updatedSketchNodePaths: PathToNode[]
|
||||
updatedEntryNodePath: PathToNode
|
||||
}
|
||||
| Error {
|
||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||
node,
|
||||
pathToNode,
|
||||
'VariableDeclaration'
|
||||
planeNodePath,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(_node1)) return _node1
|
||||
const variableDeclaration = _node1.node
|
||||
if (variableDeclaration.type !== 'VariableDeclaration') {
|
||||
return new Error('variableDeclaration.init.type !== PipeExpression')
|
||||
}
|
||||
const _node = { ...node }
|
||||
const init = variableDeclaration.declaration.init
|
||||
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
||||
createArrayExpression([
|
||||
createLiteral(roundOff(at[0])),
|
||||
createLiteral(roundOff(at[1])),
|
||||
]),
|
||||
createPipeSubstitution(),
|
||||
])
|
||||
if (init.type === 'PipeExpression') {
|
||||
init.body.splice(1, 0, startProfileAt)
|
||||
} else {
|
||||
variableDeclaration.declaration.init = createPipeExpression([
|
||||
init,
|
||||
startProfileAt,
|
||||
if (err(varDec)) return varDec
|
||||
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
|
||||
|
||||
const newExpression = createVariableDeclaration(
|
||||
findUniqueName(node, 'profile'),
|
||||
createCallExpressionStdLib('startProfileAt', [
|
||||
createArrayExpression([
|
||||
createLiteral(roundOff(at[0])),
|
||||
createLiteral(roundOff(at[1])),
|
||||
]),
|
||||
createIdentifier(varDec.node.id.name),
|
||||
])
|
||||
}
|
||||
)
|
||||
const insertIndex = getInsertIndex(sketchNodePaths, planeNodePath, insertType)
|
||||
|
||||
const _node = structuredClone(node)
|
||||
// TODO the rest of this function will not be robust to work for sketches defined within a function declaration
|
||||
_node.body.splice(insertIndex, 0, newExpression)
|
||||
|
||||
const { updatedEntryNodePath, updatedSketchNodePaths } =
|
||||
updateSketchNodePathsWithInsertIndex({
|
||||
insertIndex,
|
||||
insertType,
|
||||
sketchNodePaths,
|
||||
})
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
updatedSketchNodePaths,
|
||||
updatedEntryNodePath,
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,8 +242,21 @@ export function mutateKwArg(
|
||||
for (let i = 0; i < node.arguments.length; i++) {
|
||||
const arg = node.arguments[i]
|
||||
if (arg.label.name === label) {
|
||||
node.arguments[i].arg = val
|
||||
return true
|
||||
if (isLiteralArrayOrStatic(val) && isLiteralArrayOrStatic(arg.arg)) {
|
||||
node.arguments[i].arg = val
|
||||
return true
|
||||
} else if (
|
||||
arg.arg.type === 'ArrayExpression' &&
|
||||
val.type === 'ArrayExpression'
|
||||
) {
|
||||
const arrExp = arg.arg
|
||||
arrExp.elements.forEach((element, i) => {
|
||||
if (isLiteralArrayOrStatic(element)) {
|
||||
arrExp.elements[i] = val.elements[i]
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
node.arguments.push(createLabeledArg(label, val))
|
||||
@ -287,15 +318,16 @@ export function mutateObjExpProp(
|
||||
export function extrudeSketch({
|
||||
node,
|
||||
pathToNode,
|
||||
shouldPipe = false,
|
||||
distance = createLiteral(4),
|
||||
extrudeName,
|
||||
artifact
|
||||
}: {
|
||||
node: Node<Program>
|
||||
pathToNode: PathToNode
|
||||
shouldPipe?: boolean
|
||||
distance: Expr
|
||||
extrudeName?: string
|
||||
artifact?: Artifact,
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Node<Program>
|
||||
@ -303,10 +335,14 @@ export function extrudeSketch({
|
||||
pathToExtrudeArg: PathToNode
|
||||
}
|
||||
| Error {
|
||||
const orderedSketchNodePaths = getPathsFromArtifact({
|
||||
artifact: artifact,
|
||||
sketchPathToNode: pathToNode,
|
||||
})
|
||||
if (err(orderedSketchNodePaths)) return orderedSketchNodePaths
|
||||
const _node = structuredClone(node)
|
||||
const _node1 = getNodeFromPath(_node, pathToNode)
|
||||
if (err(_node1)) return _node1
|
||||
const { node: sketchExpression } = _node1
|
||||
|
||||
// determine if sketchExpression is in a pipeExpression or not
|
||||
const _node2 = getNodeFromPath<PipeExpression>(
|
||||
@ -315,9 +351,6 @@ export function extrudeSketch({
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(_node2)) return _node2
|
||||
const { node: pipeExpression } = _node2
|
||||
|
||||
const isInPipeExpression = pipeExpression.type === 'PipeExpression'
|
||||
|
||||
const _node3 = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
@ -325,54 +358,27 @@ export function extrudeSketch({
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(_node3)) return _node3
|
||||
const { node: variableDeclarator, shallowPath: pathToDecleration } = _node3
|
||||
const { node: variableDeclarator } = _node3
|
||||
|
||||
const sketchToExtrude = shouldPipe
|
||||
? createPipeSubstitution()
|
||||
: createIdentifier(variableDeclarator.id.name)
|
||||
const extrudeCall = createCallExpressionStdLibKw('extrude', sketchToExtrude, [
|
||||
createLabeledArg('length', distance),
|
||||
])
|
||||
const extrudeCall = createCallExpressionStdLibKw(
|
||||
'extrude',
|
||||
createIdentifier(variableDeclarator.id.name),
|
||||
[createLabeledArg('length', distance)]
|
||||
)
|
||||
// index of the 'length' arg above. If you reorder the labeled args above,
|
||||
// make sure to update this too.
|
||||
const argIndex = 0
|
||||
|
||||
if (shouldPipe) {
|
||||
const pipeChain = createPipeExpression(
|
||||
isInPipeExpression
|
||||
? [...pipeExpression.body, extrudeCall]
|
||||
: [sketchExpression as any, extrudeCall]
|
||||
)
|
||||
|
||||
variableDeclarator.init = pipeChain
|
||||
const pathToExtrudeArg: PathToNode = [
|
||||
...pathToDecleration,
|
||||
['init', 'VariableDeclarator'],
|
||||
['body', ''],
|
||||
[pipeChain.body.length - 1, 'index'],
|
||||
['arguments', 'CallExpressionKw'],
|
||||
[argIndex, ARG_INDEX_FIELD],
|
||||
['arg', LABELED_ARG_FIELD],
|
||||
]
|
||||
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
pathToExtrudeArg,
|
||||
}
|
||||
}
|
||||
|
||||
// We're not creating a pipe expression,
|
||||
// but rather a separate constant for the extrusion
|
||||
const name =
|
||||
extrudeName ?? findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.EXTRUDE)
|
||||
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
|
||||
|
||||
const sketchIndexInPathToNode =
|
||||
pathToDecleration.findIndex((a) => a[0] === 'body') + 1
|
||||
const sketchIndexInBody = pathToDecleration[
|
||||
sketchIndexInPathToNode
|
||||
][0] as number
|
||||
const lastSketchNodePath =
|
||||
orderedSketchNodePaths[orderedSketchNodePaths.length - 1]
|
||||
|
||||
const sketchIndexInBody = Number(lastSketchNodePath[1][0])
|
||||
_node.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
||||
|
||||
const pathToExtrudeArg: PathToNode = [
|
||||
@ -1496,13 +1502,21 @@ export async function deleteFromSelection(
|
||||
const pipeBody = varDec.node.init.body
|
||||
if (
|
||||
pipeBody[0].type === 'CallExpression' &&
|
||||
pipeBody[0].callee.name === 'startSketchOn'
|
||||
(pipeBody[0].callee.name === 'startSketchOn' ||
|
||||
pipeBody[0].callee.name === 'startProfileAt')
|
||||
) {
|
||||
// remove varDec
|
||||
const varDecIndex = varDec.shallowPath[1][0] as number
|
||||
astClone.body.splice(varDecIndex, 1)
|
||||
return astClone
|
||||
}
|
||||
} else if (
|
||||
varDec.node.init.type === 'CallExpressionKw' &&
|
||||
varDec.node.init.callee.name === 'circleThreePoint'
|
||||
) {
|
||||
const varDecIndex = varDec.shallowPath[1][0] as number
|
||||
astClone.body.splice(varDecIndex, 1)
|
||||
return astClone
|
||||
}
|
||||
|
||||
return new Error('Selection not recognised, could not delete')
|
||||
@ -1512,6 +1526,167 @@ const nonCodeMetaEmpty = () => {
|
||||
return { nonCodeNodes: {}, startNodes: [], start: 0, end: 0 }
|
||||
}
|
||||
|
||||
export const createLabeledArg = (name: string, arg: Expr): LabeledArg => {
|
||||
return { label: createIdentifier(name), arg, type: 'LabeledArg' }
|
||||
export function getInsertIndex(
|
||||
sketchNodePaths: PathToNode[],
|
||||
planeNodePath: PathToNode,
|
||||
insertType: 'start' | 'end'
|
||||
) {
|
||||
let minIndex = 0
|
||||
let maxIndex = 0
|
||||
for (const path of sketchNodePaths) {
|
||||
const index = Number(path[1][0])
|
||||
if (index < minIndex) minIndex = index
|
||||
if (index > maxIndex) maxIndex = index
|
||||
}
|
||||
|
||||
const insertIndex = !sketchNodePaths.length
|
||||
? Number(planeNodePath[1][0]) + 1
|
||||
: insertType === 'start'
|
||||
? minIndex
|
||||
: maxIndex + 1
|
||||
return insertIndex
|
||||
}
|
||||
|
||||
export function updateSketchNodePathsWithInsertIndex({
|
||||
insertIndex,
|
||||
insertType,
|
||||
sketchNodePaths,
|
||||
}: {
|
||||
insertIndex: number
|
||||
insertType: 'start' | 'end'
|
||||
sketchNodePaths: PathToNode[]
|
||||
}): {
|
||||
updatedEntryNodePath: PathToNode
|
||||
updatedSketchNodePaths: PathToNode[]
|
||||
} {
|
||||
// TODO the rest of this function will not be robust to work for sketches defined within a function declaration
|
||||
const newExpressionPathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[insertIndex, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
]
|
||||
let updatedSketchNodePaths = structuredClone(sketchNodePaths)
|
||||
if (insertType === 'start') {
|
||||
updatedSketchNodePaths = updatedSketchNodePaths.map((path) => {
|
||||
path[1][0] = Number(path[1][0]) + 1
|
||||
return path
|
||||
})
|
||||
updatedSketchNodePaths.unshift(newExpressionPathToNode)
|
||||
} else {
|
||||
updatedSketchNodePaths.push(newExpressionPathToNode)
|
||||
}
|
||||
return {
|
||||
updatedSketchNodePaths,
|
||||
updatedEntryNodePath: newExpressionPathToNode,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Split the following pipe expression into
|
||||
* ```ts
|
||||
* part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([1, 2], %)
|
||||
|> line([3, 4], %)
|
||||
|> line([5, 6], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, part001)
|
||||
```
|
||||
into
|
||||
```ts
|
||||
sketch001 = startSketchOn('XZ')
|
||||
part001 = startProfileAt([1, 2], sketch001)
|
||||
|> line([3, 4], %)
|
||||
|> line([5, 6], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(5, part001)
|
||||
```
|
||||
Notice that the `startSketchOn` is what gets the new variable name, this is so part001 still has the same data as before
|
||||
making it safe for later code that uses part001 (the extrude in this example)
|
||||
*
|
||||
*/
|
||||
export function splitPipedProfile(
|
||||
ast: Program,
|
||||
pathToPipe: PathToNode
|
||||
):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToProfile: PathToNode
|
||||
pathToPlane: PathToNode
|
||||
}
|
||||
| Error {
|
||||
const _ast = structuredClone(ast)
|
||||
const varDec = getNodeFromPath<VariableDeclaration>(
|
||||
_ast,
|
||||
pathToPipe,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(varDec)) return varDec
|
||||
if (
|
||||
varDec.node.type !== 'VariableDeclaration' ||
|
||||
varDec.node.declaration.init.type !== 'PipeExpression'
|
||||
) {
|
||||
return new Error('pathToNode does not point to pipe')
|
||||
}
|
||||
const init = varDec.node.declaration.init
|
||||
const firstCall = init.body[0]
|
||||
if (!isCallExprWithName(firstCall, 'startSketchOn'))
|
||||
return new Error('First call is not startSketchOn')
|
||||
const secondCall = init.body[1]
|
||||
if (!isCallExprWithName(secondCall, 'startProfileAt'))
|
||||
return new Error('Second call is not startProfileAt')
|
||||
|
||||
const varName = varDec.node.declaration.id.name
|
||||
const newVarName = findUniqueName(_ast, 'sketch')
|
||||
const secondCallArgs = structuredClone(secondCall.arguments)
|
||||
secondCallArgs[1] = createIdentifier(newVarName)
|
||||
const firstCallOfNewPipe = createCallExpression(
|
||||
'startProfileAt',
|
||||
secondCallArgs
|
||||
)
|
||||
const newSketch = createVariableDeclaration(
|
||||
newVarName,
|
||||
varDec.node.declaration.init.body[0]
|
||||
)
|
||||
const newProfile = createVariableDeclaration(
|
||||
varName,
|
||||
varDec.node.declaration.init.body.length <= 2
|
||||
? firstCallOfNewPipe
|
||||
: createPipeExpression([
|
||||
firstCallOfNewPipe,
|
||||
...varDec.node.declaration.init.body.slice(2),
|
||||
])
|
||||
)
|
||||
const index = getBodyIndex(pathToPipe)
|
||||
if (err(index)) return index
|
||||
_ast.body.splice(index, 1, newSketch, newProfile)
|
||||
const pathToPlane = structuredClone(pathToPipe)
|
||||
const pathToProfile = structuredClone(pathToPipe)
|
||||
pathToProfile[1][0] = index + 1
|
||||
|
||||
return {
|
||||
modifiedAst: _ast,
|
||||
pathToProfile,
|
||||
pathToPlane,
|
||||
}
|
||||
}
|
||||
|
||||
export function createNodeFromExprSnippet(
|
||||
strings: TemplateStringsArray,
|
||||
...expressions: any[]
|
||||
): Node<BodyItem> | Error {
|
||||
const code = strings.reduce(
|
||||
(acc, str, i) => acc + str + (expressions[i] || ''),
|
||||
''
|
||||
)
|
||||
let program = parse(code)
|
||||
if (err(program)) return program
|
||||
const node = program.program?.body[0]
|
||||
if (!node) return new Error('No node found')
|
||||
return node
|
||||
}
|
||||
|
||||
export const createLabeledArg = (label: string, arg: Expr): LabeledArg => {
|
||||
return { label: createIdentifier(label), arg, type: 'LabeledArg' }
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ import {
|
||||
PathToNode,
|
||||
Expr,
|
||||
CallExpression,
|
||||
PipeExpression,
|
||||
VariableDeclarator,
|
||||
CallExpressionKw,
|
||||
} from 'lang/wasm'
|
||||
@ -16,7 +15,6 @@ import {
|
||||
createCallExpressionStdLib,
|
||||
createObjectExpression,
|
||||
createIdentifier,
|
||||
createPipeExpression,
|
||||
findUniqueName,
|
||||
createVariableDeclaration,
|
||||
} from 'lang/modifyAst'
|
||||
@ -26,14 +24,15 @@ import {
|
||||
mutateAstWithTagForSketchSegment,
|
||||
getEdgeTagCall,
|
||||
} from 'lang/modifyAst/addEdgeTreatment'
|
||||
import { Artifact, getPathsFromArtifact } from 'lang/std/artifactGraph'
|
||||
export function revolveSketch(
|
||||
ast: Node<Program>,
|
||||
pathToSketchNode: PathToNode,
|
||||
shouldPipe = false,
|
||||
angle: Expr = createLiteral(4),
|
||||
axisOrEdge: string,
|
||||
axis: string,
|
||||
edge: Selections
|
||||
edge: Selections,
|
||||
artifact?: Artifact
|
||||
):
|
||||
| {
|
||||
modifiedAst: Node<Program>
|
||||
@ -41,6 +40,11 @@ export function revolveSketch(
|
||||
pathToRevolveArg: PathToNode
|
||||
}
|
||||
| Error {
|
||||
const orderedSketchNodePaths = getPathsFromArtifact({
|
||||
artifact: artifact,
|
||||
sketchPathToNode: pathToSketchNode,
|
||||
})
|
||||
if (err(orderedSketchNodePaths)) return orderedSketchNodePaths
|
||||
const clonedAst = structuredClone(ast)
|
||||
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
|
||||
if (err(sketchNode)) return sketchNode
|
||||
@ -74,29 +78,13 @@ export function revolveSketch(
|
||||
generatedAxis = createLiteral(axis)
|
||||
}
|
||||
|
||||
/* Original Code */
|
||||
const { node: sketchExpression } = sketchNode
|
||||
|
||||
// determine if sketchExpression is in a pipeExpression or not
|
||||
const sketchPipeExpressionNode = getNodeFromPath<PipeExpression>(
|
||||
clonedAst,
|
||||
pathToSketchNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(sketchPipeExpressionNode)) return sketchPipeExpressionNode
|
||||
const { node: sketchPipeExpression } = sketchPipeExpressionNode
|
||||
const isInPipeExpression = sketchPipeExpression.type === 'PipeExpression'
|
||||
|
||||
const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>(
|
||||
clonedAst,
|
||||
pathToSketchNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode
|
||||
const {
|
||||
node: sketchVariableDeclarator,
|
||||
shallowPath: sketchPathToDecleration,
|
||||
} = sketchVariableDeclaratorNode
|
||||
const { node: sketchVariableDeclarator } = sketchVariableDeclaratorNode
|
||||
|
||||
if (!generatedAxis) return new Error('Generated axis selection is missing.')
|
||||
|
||||
@ -108,37 +96,13 @@ export function revolveSketch(
|
||||
createIdentifier(sketchVariableDeclarator.id.name),
|
||||
])
|
||||
|
||||
if (shouldPipe) {
|
||||
const pipeChain = createPipeExpression(
|
||||
isInPipeExpression
|
||||
? [...sketchPipeExpression.body, revolveCall]
|
||||
: [sketchExpression as any, revolveCall]
|
||||
)
|
||||
|
||||
sketchVariableDeclarator.init = pipeChain
|
||||
const pathToRevolveArg: PathToNode = [
|
||||
...sketchPathToDecleration,
|
||||
['init', 'VariableDeclarator'],
|
||||
['body', ''],
|
||||
[pipeChain.body.length - 1, 'index'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
]
|
||||
|
||||
return {
|
||||
modifiedAst: clonedAst,
|
||||
pathToSketchNode,
|
||||
pathToRevolveArg,
|
||||
}
|
||||
}
|
||||
|
||||
// We're not creating a pipe expression,
|
||||
// but rather a separate constant for the extrusion
|
||||
const name = findUniqueName(clonedAst, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE)
|
||||
const VariableDeclaration = createVariableDeclaration(name, revolveCall)
|
||||
const sketchIndexInPathToNode =
|
||||
sketchPathToDecleration.findIndex((a) => a[0] === 'body') + 1
|
||||
const sketchIndexInBody = sketchPathToDecleration[sketchIndexInPathToNode][0]
|
||||
const lastSketchNodePath =
|
||||
orderedSketchNodePaths[orderedSketchNodePaths.length - 1]
|
||||
const sketchIndexInBody = Number(lastSketchNodePath[1][0])
|
||||
if (typeof sketchIndexInBody !== 'number')
|
||||
return new Error('expected sketchIndexInBody to be a number')
|
||||
clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
||||
|
||||
@ -17,8 +17,6 @@ import {
|
||||
createObjectExpression,
|
||||
createArrayExpression,
|
||||
createVariableDeclaration,
|
||||
createCallExpressionStdLibKw,
|
||||
createLabeledArg,
|
||||
} from 'lang/modifyAst'
|
||||
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
||||
import { KclManager } from 'lang/KclSingleton'
|
||||
@ -123,14 +121,13 @@ export function addShell({
|
||||
}
|
||||
|
||||
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SHELL)
|
||||
const shell = createCallExpressionStdLibKw(
|
||||
'shell',
|
||||
const shell = createCallExpressionStdLib('shell', [
|
||||
createObjectExpression({
|
||||
faces: createArrayExpression(expressions),
|
||||
thickness,
|
||||
}),
|
||||
createIdentifier(extrudeNode.node.id.name),
|
||||
[
|
||||
createLabeledArg('faces', createArrayExpression(expressions)),
|
||||
createLabeledArg('thickness', thickness),
|
||||
]
|
||||
)
|
||||
])
|
||||
const declaration = createVariableDeclaration(name, shell)
|
||||
|
||||
// TODO: check if we should append at the end like here or right after the extrude
|
||||
@ -140,7 +137,8 @@ export function addShell({
|
||||
[modifiedAst.body.length - 1, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['unlabeled', 'CallExpressionKw'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
]
|
||||
return {
|
||||
modifiedAst,
|
||||
|
||||
@ -2,7 +2,6 @@ import { ToolTip } from 'lang/langHelpers'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import {
|
||||
ArrayExpression,
|
||||
ArtifactGraph,
|
||||
BinaryExpression,
|
||||
CallExpression,
|
||||
CallExpressionKw,
|
||||
@ -23,9 +22,7 @@ import {
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
recast,
|
||||
kclSettings,
|
||||
unitLenToUnitLength,
|
||||
unitAngToUnitAngle,
|
||||
ArtifactGraph,
|
||||
} from './wasm'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||
@ -37,11 +34,10 @@ import {
|
||||
getConstraintType,
|
||||
} from './std/sketchcombos'
|
||||
import { err, Reason } from 'lib/trap'
|
||||
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { findKwArg } from './util'
|
||||
import { codeRefFromRange } from './std/artifactGraph'
|
||||
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
|
||||
import { FunctionExpression } from 'wasm-lib/kcl/bindings/FunctionExpression'
|
||||
|
||||
export const LABELED_ARG_FIELD = 'LabeledArg -> Arg'
|
||||
export const ARG_INDEX_FIELD = 'arg index'
|
||||
@ -357,7 +353,13 @@ export function findAllPreviousVariables(
|
||||
type ReplacerFn = (
|
||||
_ast: Node<Program>,
|
||||
varName: string
|
||||
) => { modifiedAst: Node<Program>; pathToReplaced: PathToNode } | Error
|
||||
) =>
|
||||
| {
|
||||
modifiedAst: Node<Program>
|
||||
pathToReplaced: PathToNode
|
||||
exprInsertIndex: number
|
||||
}
|
||||
| Error
|
||||
|
||||
export function isNodeSafeToReplacePath(
|
||||
ast: Program,
|
||||
@ -409,7 +411,7 @@ export function isNodeSafeToReplacePath(
|
||||
if (err(_nodeToReplace)) return _nodeToReplace
|
||||
const nodeToReplace = _nodeToReplace.node as any
|
||||
nodeToReplace[last[0]] = identifier
|
||||
return { modifiedAst: _ast, pathToReplaced }
|
||||
return { modifiedAst: _ast, pathToReplaced, exprInsertIndex: index }
|
||||
}
|
||||
|
||||
const hasPipeSub = isTypeInValue(finVal as Expr, 'PipeSubstitution')
|
||||
@ -518,8 +520,15 @@ export function isLinesParallelAndConstrained(
|
||||
if (err(_primarySegment)) return _primarySegment
|
||||
const primarySegment = _primarySegment.segment
|
||||
|
||||
const _varDec2 = getNodeFromPath(ast, secondaryPath, 'VariableDeclaration')
|
||||
if (err(_varDec2)) return _varDec2
|
||||
const varDec2 = _varDec2.node
|
||||
const varName2 = (varDec2 as VariableDeclaration)?.declaration.id?.name
|
||||
const sg2 = sketchFromKclValue(programMemory?.get(varName2), varName2)
|
||||
if (err(sg2)) return sg2
|
||||
|
||||
const _segment = getSketchSegmentFromSourceRange(
|
||||
sg,
|
||||
sg2,
|
||||
secondaryLine?.codeRef?.range
|
||||
)
|
||||
if (err(_segment)) return _segment
|
||||
@ -871,23 +880,56 @@ export function getObjExprProperty(
|
||||
return { expr: node.properties[index].value, index }
|
||||
}
|
||||
|
||||
/**
|
||||
* Given KCL, returns the settings annotation object if it exists.
|
||||
*/
|
||||
export function getSettingsAnnotation(
|
||||
kcl: string | Node<Program>
|
||||
): KclSettingsAnnotation | Error {
|
||||
const metaSettings = kclSettings(kcl)
|
||||
if (err(metaSettings)) return metaSettings
|
||||
|
||||
const settings: KclSettingsAnnotation = {}
|
||||
// No settings in the KCL.
|
||||
if (!metaSettings) return settings
|
||||
|
||||
settings.defaultLengthUnit = unitLenToUnitLength(
|
||||
metaSettings.defaultLengthUnits
|
||||
export function isCursorInFunctionDefinition(
|
||||
ast: Node<Program>,
|
||||
selectionRanges: Selection
|
||||
): boolean {
|
||||
if (!selectionRanges?.codeRef?.pathToNode) return false
|
||||
const node = getNodeFromPath<FunctionExpression>(
|
||||
ast,
|
||||
selectionRanges.codeRef.pathToNode,
|
||||
'FunctionExpression'
|
||||
)
|
||||
settings.defaultAngleUnit = unitAngToUnitAngle(metaSettings.defaultAngleUnits)
|
||||
|
||||
return settings
|
||||
if (err(node)) return false
|
||||
if (node.node.type === 'FunctionExpression') return true
|
||||
return false
|
||||
}
|
||||
|
||||
export function getBodyIndex(pathToNode: PathToNode): number | Error {
|
||||
const index = Number(pathToNode[1][0])
|
||||
if (Number.isInteger(index)) return index
|
||||
return new Error('Expected number index')
|
||||
}
|
||||
|
||||
export function isCallExprWithName(
|
||||
expr: Expr | CallExpression,
|
||||
name: string
|
||||
): expr is CallExpression {
|
||||
if (expr.type === 'CallExpression' && expr.callee.type === 'Identifier') {
|
||||
return expr.callee.name === name
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function doesSketchPipeNeedSplitting(
|
||||
ast: Node<Program>,
|
||||
pathToPipe: PathToNode
|
||||
): boolean | Error {
|
||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||
ast,
|
||||
pathToPipe,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(varDec)) return varDec
|
||||
if (varDec.node.type !== 'VariableDeclarator') return new Error('Not a var')
|
||||
const pipeExpression = varDec.node.init
|
||||
if (pipeExpression.type !== 'PipeExpression') return false
|
||||
const [firstPipe, secondPipe] = pipeExpression.body
|
||||
if (!firstPipe || !secondPipe) return false
|
||||
if (
|
||||
isCallExprWithName(firstPipe, 'startSketchOn') &&
|
||||
isCallExprWithName(secondPipe, 'startProfileAt')
|
||||
)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
@ -82,6 +82,7 @@ function moreNodePathFromSourceRange(
|
||||
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {
|
||||
Expr,
|
||||
Artifact,
|
||||
ArtifactGraph,
|
||||
ArtifactId,
|
||||
@ -18,7 +19,7 @@ import {
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { err } from 'lib/trap'
|
||||
import { codeManager } from 'lib/singletons'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
|
||||
export type { Artifact, ArtifactId, SegmentArtifact } from 'lang/wasm'
|
||||
|
||||
@ -40,7 +41,7 @@ export interface PlaneArtifactRich extends BaseArtifact {
|
||||
export interface PathArtifactRich extends BaseArtifact {
|
||||
type: 'path'
|
||||
/** A path must always lie on a plane */
|
||||
plane: PlaneArtifact | WallArtifact
|
||||
plane: PlaneArtifact | WallArtifact | CapArtifact
|
||||
/** A path must always contain 0 or more segments */
|
||||
segments: Array<SegmentArtifact>
|
||||
/** A path may not result in a sweep artifact */
|
||||
@ -51,7 +52,7 @@ export interface PathArtifactRich extends BaseArtifact {
|
||||
interface SegmentArtifactRich extends BaseArtifact {
|
||||
type: 'segment'
|
||||
path: PathArtifact
|
||||
surf?: WallArtifact
|
||||
surf: WallArtifact
|
||||
edges: Array<SweepEdge>
|
||||
edgeCut?: EdgeCut
|
||||
codeRef: CodeRef
|
||||
@ -239,6 +240,7 @@ export function expandSegment(
|
||||
if (err(path)) return path
|
||||
if (err(surf)) return surf
|
||||
if (err(edgeCut)) return edgeCut
|
||||
if (!surf) return new Error('Segment does not have a surface')
|
||||
|
||||
return {
|
||||
type: 'segment',
|
||||
@ -410,6 +412,205 @@ export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
|
||||
}
|
||||
}
|
||||
|
||||
function getPlaneFromPath(
|
||||
path: PathArtifact,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | CapArtifact | Error {
|
||||
const plane = getArtifactOfTypes(
|
||||
{ key: path.planeId, types: ['plane', 'wall', 'cap'] },
|
||||
graph
|
||||
)
|
||||
if (err(plane)) return plane
|
||||
return plane
|
||||
}
|
||||
|
||||
function getPlaneFromSegment(
|
||||
segment: SegmentArtifact,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | CapArtifact | Error {
|
||||
const path = getArtifactOfTypes(
|
||||
{ key: segment.pathId, types: ['path'] },
|
||||
graph
|
||||
)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
function getPlaneFromSolid2D(
|
||||
solid2D: Solid2D,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | CapArtifact | Error {
|
||||
const path = getArtifactOfTypes(
|
||||
{ key: solid2D.pathId, types: ['path'] },
|
||||
graph
|
||||
)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
function getPlaneFromCap(
|
||||
cap: CapArtifact,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | CapArtifact | Error {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: cap.sweepId, types: ['sweep'] },
|
||||
graph
|
||||
)
|
||||
if (err(sweep)) return sweep
|
||||
const path = getArtifactOfTypes({ key: sweep.pathId, types: ['path'] }, graph)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
function getPlaneFromWall(
|
||||
wall: WallArtifact,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | CapArtifact | Error {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: wall.sweepId, types: ['sweep'] },
|
||||
graph
|
||||
)
|
||||
if (err(sweep)) return sweep
|
||||
const path = getArtifactOfTypes({ key: sweep.pathId, types: ['path'] }, graph)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
function getPlaneFromSweepEdge(edge: SweepEdge, graph: ArtifactGraph) {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: edge.sweepId, types: ['sweep'] },
|
||||
graph
|
||||
)
|
||||
if (err(sweep)) return sweep
|
||||
const path = getArtifactOfTypes({ key: sweep.pathId, types: ['path'] }, graph)
|
||||
if (err(path)) return path
|
||||
return getPlaneFromPath(path, graph)
|
||||
}
|
||||
|
||||
export function getPlaneFromArtifact(
|
||||
artifact: Artifact | undefined,
|
||||
graph: ArtifactGraph
|
||||
): PlaneArtifact | WallArtifact | CapArtifact | Error {
|
||||
if (!artifact) return new Error(`Artifact is undefined`)
|
||||
if (artifact.type === 'plane') return artifact
|
||||
if (artifact.type === 'path') return getPlaneFromPath(artifact, graph)
|
||||
if (artifact.type === 'segment') return getPlaneFromSegment(artifact, graph)
|
||||
if (artifact.type === 'solid2d') return getPlaneFromSolid2D(artifact, graph)
|
||||
if (artifact.type === 'cap') return getPlaneFromCap(artifact, graph)
|
||||
if (artifact.type === 'wall') return getPlaneFromWall(artifact, graph)
|
||||
if (artifact.type === 'sweepEdge')
|
||||
return getPlaneFromSweepEdge(artifact, graph)
|
||||
return new Error(`Artifact type ${artifact.type} does not have a plane`)
|
||||
}
|
||||
|
||||
const isExprSafe = (index: number): boolean => {
|
||||
const expr = kclManager.ast.body?.[index]
|
||||
if (!expr) {
|
||||
return false
|
||||
}
|
||||
if (expr.type === 'ImportStatement' || expr.type === 'ReturnStatement') {
|
||||
return false
|
||||
}
|
||||
if (expr.type === 'VariableDeclaration') {
|
||||
const init = expr.declaration?.init
|
||||
if (!init) return false
|
||||
if (init.type === 'CallExpression') {
|
||||
return false
|
||||
}
|
||||
if (init.type === 'BinaryExpression' && isNodeSafe(init)) {
|
||||
return true
|
||||
}
|
||||
if (init.type === 'Literal' || init.type === 'MemberExpression') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const onlyConsecutivePaths = (
|
||||
orderedNodePaths: PathToNode[],
|
||||
originalPath: PathToNode
|
||||
): PathToNode[] => {
|
||||
const originalIndex = Number(
|
||||
orderedNodePaths.find(
|
||||
(path) => path[1][0] === originalPath[1][0]
|
||||
)?.[1]?.[0] || 0
|
||||
)
|
||||
|
||||
const minIndex = Number(orderedNodePaths[0][1][0])
|
||||
const maxIndex = Number(orderedNodePaths[orderedNodePaths.length - 1][1][0])
|
||||
const pathIndexMap: any = {}
|
||||
orderedNodePaths.forEach((path) => {
|
||||
const bodyIndex = Number(path[1][0])
|
||||
pathIndexMap[bodyIndex] = path
|
||||
})
|
||||
const safePaths: PathToNode[] = []
|
||||
|
||||
// traverse expressions in either direction from the profile selected
|
||||
// when the user entered sketch mode
|
||||
for (let i = originalIndex; i <= maxIndex; i++) {
|
||||
if (pathIndexMap[i]) {
|
||||
safePaths.push(pathIndexMap[i])
|
||||
} else if (!isExprSafe(i)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for (let i = originalIndex - 1; i >= minIndex; i--) {
|
||||
if (pathIndexMap[i]) {
|
||||
safePaths.unshift(pathIndexMap[i])
|
||||
} else if (!isExprSafe(i)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return safePaths
|
||||
}
|
||||
|
||||
export function getPathsFromPlaneArtifact(planeArtifact: PlaneArtifact) {
|
||||
const nodePaths: PathToNode[] = []
|
||||
for (const pathId of planeArtifact.pathIds) {
|
||||
const path = engineCommandManager.artifactGraph.get(pathId)
|
||||
if (!path) continue
|
||||
if ('codeRef' in path && path.codeRef) {
|
||||
// TODO should figure out why upstream the path is bad
|
||||
const isNodePathBad = path.codeRef.pathToNode.length < 2
|
||||
nodePaths.push(
|
||||
isNodePathBad
|
||||
? getNodePathFromSourceRange(kclManager.ast, path.codeRef.range)
|
||||
: path.codeRef.pathToNode
|
||||
)
|
||||
}
|
||||
}
|
||||
return onlyConsecutivePaths(nodePaths, nodePaths[0])
|
||||
}
|
||||
|
||||
export function getPathsFromArtifact({
|
||||
sketchPathToNode,
|
||||
artifact,
|
||||
}: {
|
||||
sketchPathToNode: PathToNode
|
||||
artifact?: Artifact
|
||||
}): PathToNode[] | Error {
|
||||
const plane = getPlaneFromArtifact(
|
||||
artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(plane)) return plane
|
||||
const paths = getArtifactsOfTypes(
|
||||
{ keys: plane.pathIds, types: ['path'] },
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
let nodePaths = [...paths.values()]
|
||||
.map((path) => path.codeRef.pathToNode)
|
||||
.sort((a, b) => Number(a[1][0]) - Number(b[1][0]))
|
||||
return onlyConsecutivePaths(nodePaths, sketchPathToNode)
|
||||
}
|
||||
|
||||
function isNodeSafe(node: Expr): boolean {
|
||||
if (node.type === 'Literal' || node.type === 'MemberExpression') {
|
||||
return true
|
||||
}
|
||||
if (node.type === 'BinaryExpression') {
|
||||
return isNodeSafe(node.left) && isNodeSafe(node.right)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an artifact from a code source range
|
||||
*/
|
||||
@ -418,7 +619,7 @@ export function getArtifactFromRange(
|
||||
artifactGraph: ArtifactGraph
|
||||
): Artifact | null {
|
||||
for (const artifact of artifactGraph.values()) {
|
||||
if ('codeRef' in artifact) {
|
||||
if ('codeRef' in artifact && artifact.codeRef) {
|
||||
const match =
|
||||
artifact.codeRef?.range[0] === range[0] &&
|
||||
artifact.codeRef.range[1] === range[1]
|
||||
|
||||
|
Before Width: | Height: | Size: 569 KiB After Width: | Height: | Size: 560 KiB |
@ -1406,8 +1406,6 @@ export class EngineCommandManager extends EventTarget {
|
||||
commandId: string
|
||||
}
|
||||
settings: SettingsViaQueryString
|
||||
width: number = 1337
|
||||
height: number = 1337
|
||||
|
||||
/**
|
||||
* Export intent traxcks the intent of the export. If it is null there is no
|
||||
@ -1513,9 +1511,6 @@ export class EngineCommandManager extends EventTarget {
|
||||
return
|
||||
}
|
||||
|
||||
this.width = width
|
||||
this.height = height
|
||||
|
||||
// If we already have an engine connection, just need to resize the stream.
|
||||
if (this.engineConnection) {
|
||||
this.handleResize({
|
||||
@ -1828,9 +1823,6 @@ export class EngineCommandManager extends EventTarget {
|
||||
return
|
||||
}
|
||||
|
||||
this.width = streamWidth
|
||||
this.height = streamHeight
|
||||
|
||||
const resizeCmd: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
|
||||
@ -66,7 +66,12 @@ import { perpendicularDistance } from 'sketch-helpers'
|
||||
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
||||
import { EdgeCutInfo } from 'machines/modelingMachine'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { findKwArg, findKwArgAny, findKwArgAnyIndex } from 'lang/util'
|
||||
import {
|
||||
findKwArg,
|
||||
findKwArgWithIndex,
|
||||
findKwArgAny,
|
||||
findKwArgAnyIndex,
|
||||
} from 'lang/util'
|
||||
|
||||
export const ARG_TAG = 'tag'
|
||||
export const ARG_END = 'end'
|
||||
@ -76,6 +81,9 @@ const STRAIGHT_SEGMENT_ERR = new Error(
|
||||
'Invalid input, expected "straight-segment"'
|
||||
)
|
||||
const ARC_SEGMENT_ERR = new Error('Invalid input, expected "arc-segment"')
|
||||
const CIRCLE_THREE_POINT_SEGMENT_ERR = new Error(
|
||||
'Invalid input, expected "circle-three-point-segment"'
|
||||
)
|
||||
|
||||
export type Coords2d = [number, number]
|
||||
|
||||
@ -171,7 +179,8 @@ const commonConstraintInfoHelper = (
|
||||
}
|
||||
],
|
||||
code: string,
|
||||
pathToNode: PathToNode
|
||||
pathToNode: PathToNode,
|
||||
filterValue?: string
|
||||
) => {
|
||||
if (callExp.type !== 'CallExpression' && callExp.type !== 'CallExpressionKw')
|
||||
return []
|
||||
@ -295,7 +304,8 @@ const horzVertConstraintInfoHelper = (
|
||||
stdLibFnName: ConstrainInfo['stdLibFnName'],
|
||||
abbreviatedInput: AbbreviatedInput,
|
||||
code: string,
|
||||
pathToNode: PathToNode
|
||||
pathToNode: PathToNode,
|
||||
filterValue?: string
|
||||
) => {
|
||||
if (callExp.type !== 'CallExpression') return []
|
||||
const firstArg = callExp.arguments?.[0]
|
||||
@ -502,13 +512,14 @@ export const lineTo: SketchLineHelperKw = {
|
||||
}) => {
|
||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||
const to = segmentInput.to
|
||||
const _node = { ...node }
|
||||
const _node = structuredClone(node)
|
||||
const nodeMeta = getNodeFromPath<PipeExpression | CallExpressionKw>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const { node: pipe } = nodeMeta
|
||||
const nodeMeta2 = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
@ -783,11 +794,11 @@ export const xLine: SketchLineHelper = {
|
||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||
const { from, to } = segmentInput
|
||||
const _node = { ...node }
|
||||
const _node = structuredClone(node)
|
||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||
const _node1 = getNode<PipeExpression>('PipeExpression')
|
||||
if (err(_node1)) return _node1
|
||||
const { node: pipe } = _node1
|
||||
const varDec = getNode<VariableDeclaration>('VariableDeclaration')
|
||||
if (err(varDec)) return varDec
|
||||
const dec = varDec.node.declaration
|
||||
|
||||
const newVal = createLiteral(roundOff(to[0] - from[0], 2))
|
||||
|
||||
@ -802,7 +813,11 @@ export const xLine: SketchLineHelper = {
|
||||
])
|
||||
if (err(result)) return result
|
||||
const { callExp, valueUsedInTransform } = result
|
||||
pipe.body[callIndex] = callExp
|
||||
if (dec.init.type === 'PipeExpression') {
|
||||
dec.init.body[callIndex] = callExp
|
||||
} else {
|
||||
dec.init = callExp
|
||||
}
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
@ -814,7 +829,11 @@ export const xLine: SketchLineHelper = {
|
||||
newVal,
|
||||
createPipeSubstitution(),
|
||||
])
|
||||
pipe.body = [...pipe.body, newLine]
|
||||
if (dec.init.type === 'PipeExpression') {
|
||||
dec.init.body = [...dec.init.body, newLine]
|
||||
} else {
|
||||
dec.init = createPipeExpression([dec.init, newLine])
|
||||
}
|
||||
return { modifiedAst: _node, pathToNode }
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, input }) => {
|
||||
@ -851,11 +870,11 @@ export const yLine: SketchLineHelper = {
|
||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||
const { from, to } = segmentInput
|
||||
const _node = { ...node }
|
||||
const _node = structuredClone(node)
|
||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||
const _node1 = getNode<PipeExpression>('PipeExpression')
|
||||
if (err(_node1)) return _node1
|
||||
const { node: pipe } = _node1
|
||||
const varDec = getNode<VariableDeclaration>('VariableDeclaration')
|
||||
if (err(varDec)) return varDec
|
||||
const dec = varDec.node.declaration
|
||||
const newVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||
if (replaceExistingCallback) {
|
||||
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
||||
@ -868,7 +887,11 @@ export const yLine: SketchLineHelper = {
|
||||
])
|
||||
if (err(result)) return result
|
||||
const { callExp, valueUsedInTransform } = result
|
||||
pipe.body[callIndex] = callExp
|
||||
if (dec.init.type === 'PipeExpression') {
|
||||
dec.init.body[callIndex] = callExp
|
||||
} else {
|
||||
dec.init = callExp
|
||||
}
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
@ -880,7 +903,11 @@ export const yLine: SketchLineHelper = {
|
||||
newVal,
|
||||
createPipeSubstitution(),
|
||||
])
|
||||
pipe.body = [...pipe.body, newLine]
|
||||
if (dec.init.type === 'PipeExpression') {
|
||||
dec.init.body = [...dec.init.body, newLine]
|
||||
} else {
|
||||
dec.init = createPipeExpression([dec.init, newLine])
|
||||
}
|
||||
return { modifiedAst: _node, pathToNode }
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, input }) => {
|
||||
@ -1220,6 +1247,295 @@ export const circle: SketchLineHelper = {
|
||||
]
|
||||
},
|
||||
}
|
||||
export const circleThreePoint: SketchLineHelperKw = {
|
||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||
if (segmentInput.type !== 'circle-three-point-segment') {
|
||||
return CIRCLE_THREE_POINT_SEGMENT_ERR
|
||||
}
|
||||
|
||||
const { p1, p2, p3 } = segmentInput
|
||||
const _node = structuredClone(node)
|
||||
const nodeMeta = getNodeFromPath<VariableDeclaration>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const { node: varDec } = nodeMeta
|
||||
|
||||
const createRoundedLiteral = (val: number) =>
|
||||
createLiteral(roundOff(val, 2))
|
||||
if (replaceExistingCallback) {
|
||||
const result = replaceExistingCallback([
|
||||
{
|
||||
type: 'arrayInObject',
|
||||
index: 0,
|
||||
key: 'p1',
|
||||
argType: 'xAbsolute',
|
||||
expr: createRoundedLiteral(p1[0]),
|
||||
},
|
||||
{
|
||||
type: 'arrayInObject',
|
||||
index: 1,
|
||||
key: 'p1',
|
||||
argType: 'yAbsolute',
|
||||
expr: createRoundedLiteral(p1[1]),
|
||||
},
|
||||
{
|
||||
type: 'arrayInObject',
|
||||
index: 0,
|
||||
key: 'p2',
|
||||
argType: 'xAbsolute',
|
||||
expr: createRoundedLiteral(p2[0]),
|
||||
},
|
||||
{
|
||||
type: 'arrayInObject',
|
||||
index: 1,
|
||||
key: 'p2',
|
||||
argType: 'yAbsolute',
|
||||
expr: createRoundedLiteral(p2[1]),
|
||||
},
|
||||
{
|
||||
type: 'arrayInObject',
|
||||
index: 0,
|
||||
key: 'p3',
|
||||
argType: 'xAbsolute',
|
||||
expr: createRoundedLiteral(p3[0]),
|
||||
},
|
||||
{
|
||||
type: 'arrayInObject',
|
||||
index: 1,
|
||||
key: 'p3',
|
||||
argType: 'yAbsolute',
|
||||
expr: createRoundedLiteral(p3[1]),
|
||||
},
|
||||
])
|
||||
if (err(result)) return result
|
||||
const { callExp, valueUsedInTransform } = result
|
||||
|
||||
varDec.declaration.init = callExp
|
||||
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
valueUsedInTransform,
|
||||
}
|
||||
}
|
||||
return new Error('not implemented')
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, input }) => {
|
||||
if (input.type !== 'circle-three-point-segment') {
|
||||
return CIRCLE_THREE_POINT_SEGMENT_ERR
|
||||
}
|
||||
const { p1, p2, p3 } = input
|
||||
const _node = { ...node }
|
||||
const nodeMeta = getNodeFromPath<CallExpressionKw>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const { node: callExpression, shallowPath } = nodeMeta
|
||||
const createRounded2DPointArr = (point: [number, number]) =>
|
||||
createArrayExpression([
|
||||
createLiteral(roundOff(point[0], 2)),
|
||||
createLiteral(roundOff(point[1], 2)),
|
||||
])
|
||||
|
||||
const newP1 = createRounded2DPointArr(p1)
|
||||
const newP2 = createRounded2DPointArr(p2)
|
||||
const newP3 = createRounded2DPointArr(p3)
|
||||
mutateKwArg('p1', callExpression, newP1)
|
||||
mutateKwArg('p2', callExpression, newP2)
|
||||
mutateKwArg('p3', callExpression, newP3)
|
||||
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode: shallowPath,
|
||||
}
|
||||
},
|
||||
getTag: getTagKwArg(),
|
||||
addTag: addTagKw(),
|
||||
getConstraintInfo: (callExp, code, pathToNode, filterValue) => {
|
||||
if (callExp.type !== 'CallExpressionKw') return []
|
||||
const p1Details = findKwArgWithIndex('p1', callExp)
|
||||
const p2Details = findKwArgWithIndex('p2', callExp)
|
||||
const p3Details = findKwArgWithIndex('p3', callExp)
|
||||
if (!p1Details || !p2Details || !p3Details) return []
|
||||
if (
|
||||
p1Details.expr.type !== 'ArrayExpression' ||
|
||||
p2Details.expr.type !== 'ArrayExpression' ||
|
||||
p3Details.expr.type !== 'ArrayExpression'
|
||||
)
|
||||
return []
|
||||
|
||||
const pathToP1ArrayExpression: PathToNode = [
|
||||
...pathToNode,
|
||||
['arguments', 'CallExpressionKw'],
|
||||
[p1Details.argIndex, 'arg index'],
|
||||
['arg', 'labeledArg -> Arg'],
|
||||
['elements', 'ArrayExpression'],
|
||||
]
|
||||
const pathToP2ArrayExpression: PathToNode = [
|
||||
...pathToNode,
|
||||
['arguments', 'CallExpressionKw'],
|
||||
[p2Details.argIndex, 'arg index'],
|
||||
['arg', 'labeledArg -> Arg'],
|
||||
['elements', 'ArrayExpression'],
|
||||
]
|
||||
const pathToP3ArrayExpression: PathToNode = [
|
||||
...pathToNode,
|
||||
['arguments', 'CallExpressionKw'],
|
||||
[p3Details.argIndex, 'arg index'],
|
||||
['arg', 'labeledArg -> Arg'],
|
||||
['elements', 'ArrayExpression'],
|
||||
]
|
||||
|
||||
const pathToP1XArg: PathToNode = [...pathToP1ArrayExpression, [0, 'index']]
|
||||
const pathToP1YArg: PathToNode = [...pathToP1ArrayExpression, [1, 'index']]
|
||||
const pathToP2XArg: PathToNode = [...pathToP2ArrayExpression, [0, 'index']]
|
||||
const pathToP2YArg: PathToNode = [...pathToP2ArrayExpression, [1, 'index']]
|
||||
const pathToP3XArg: PathToNode = [...pathToP3ArrayExpression, [0, 'index']]
|
||||
const pathToP3YArg: PathToNode = [...pathToP3ArrayExpression, [1, 'index']]
|
||||
|
||||
const constraints: (ConstrainInfo & { filterValue: string })[] = [
|
||||
{
|
||||
stdLibFnName: 'circleThreePoint',
|
||||
type: 'xAbsolute',
|
||||
isConstrained: isNotLiteralArrayOrStatic(p1Details.expr.elements[0]),
|
||||
sourceRange: [
|
||||
p1Details.expr.elements[0].start,
|
||||
p1Details.expr.elements[0].end,
|
||||
0,
|
||||
],
|
||||
pathToNode: pathToP1XArg,
|
||||
value: code.slice(
|
||||
p1Details.expr.elements[0].start,
|
||||
p1Details.expr.elements[0].end
|
||||
),
|
||||
argPosition: {
|
||||
type: 'arrayInObject',
|
||||
index: 0,
|
||||
key: 'p1',
|
||||
},
|
||||
filterValue: 'p1',
|
||||
},
|
||||
{
|
||||
stdLibFnName: 'circleThreePoint',
|
||||
type: 'yAbsolute',
|
||||
isConstrained: isNotLiteralArrayOrStatic(p1Details.expr.elements[1]),
|
||||
sourceRange: [
|
||||
p1Details.expr.elements[1].start,
|
||||
p1Details.expr.elements[1].end,
|
||||
0,
|
||||
],
|
||||
pathToNode: pathToP1YArg,
|
||||
value: code.slice(
|
||||
p1Details.expr.elements[1].start,
|
||||
p1Details.expr.elements[1].end
|
||||
),
|
||||
argPosition: {
|
||||
type: 'arrayInObject',
|
||||
index: 1,
|
||||
key: 'p1',
|
||||
},
|
||||
filterValue: 'p1',
|
||||
},
|
||||
{
|
||||
stdLibFnName: 'circleThreePoint',
|
||||
type: 'xAbsolute',
|
||||
isConstrained: isNotLiteralArrayOrStatic(p2Details.expr.elements[0]),
|
||||
sourceRange: [
|
||||
p2Details.expr.elements[0].start,
|
||||
p2Details.expr.elements[0].end,
|
||||
0,
|
||||
],
|
||||
pathToNode: pathToP2XArg,
|
||||
value: code.slice(
|
||||
p2Details.expr.elements[0].start,
|
||||
p2Details.expr.elements[0].end
|
||||
),
|
||||
argPosition: {
|
||||
type: 'arrayInObject',
|
||||
index: 0,
|
||||
key: 'p2',
|
||||
},
|
||||
filterValue: 'p2',
|
||||
},
|
||||
{
|
||||
stdLibFnName: 'circleThreePoint',
|
||||
type: 'yAbsolute',
|
||||
isConstrained: isNotLiteralArrayOrStatic(p2Details.expr.elements[1]),
|
||||
sourceRange: [
|
||||
p2Details.expr.elements[1].start,
|
||||
p2Details.expr.elements[1].end,
|
||||
0,
|
||||
],
|
||||
pathToNode: pathToP2YArg,
|
||||
value: code.slice(
|
||||
p2Details.expr.elements[1].start,
|
||||
p2Details.expr.elements[1].end
|
||||
),
|
||||
argPosition: {
|
||||
type: 'arrayInObject',
|
||||
index: 1,
|
||||
key: 'p2',
|
||||
},
|
||||
filterValue: 'p2',
|
||||
},
|
||||
{
|
||||
stdLibFnName: 'circleThreePoint',
|
||||
type: 'xAbsolute',
|
||||
isConstrained: isNotLiteralArrayOrStatic(p3Details.expr.elements[0]),
|
||||
sourceRange: [
|
||||
p3Details.expr.elements[0].start,
|
||||
p3Details.expr.elements[0].end,
|
||||
0,
|
||||
],
|
||||
pathToNode: pathToP3XArg,
|
||||
value: code.slice(
|
||||
p3Details.expr.elements[0].start,
|
||||
p3Details.expr.elements[0].end
|
||||
),
|
||||
argPosition: {
|
||||
type: 'arrayInObject',
|
||||
index: 0,
|
||||
key: 'p3',
|
||||
},
|
||||
filterValue: 'p3',
|
||||
},
|
||||
{
|
||||
stdLibFnName: 'circleThreePoint',
|
||||
type: 'yAbsolute',
|
||||
isConstrained: isNotLiteralArrayOrStatic(p3Details.expr.elements[1]),
|
||||
sourceRange: [
|
||||
p3Details.expr.elements[1].start,
|
||||
p3Details.expr.elements[1].end,
|
||||
0,
|
||||
],
|
||||
pathToNode: pathToP3YArg,
|
||||
value: code.slice(
|
||||
p3Details.expr.elements[1].start,
|
||||
p3Details.expr.elements[1].end
|
||||
),
|
||||
argPosition: {
|
||||
type: 'arrayInObject',
|
||||
index: 1,
|
||||
key: 'p3',
|
||||
},
|
||||
filterValue: 'p3',
|
||||
},
|
||||
]
|
||||
const finalConstraints: ConstrainInfo[] = []
|
||||
constraints.forEach((constraint) => {
|
||||
if (!filterValue) {
|
||||
finalConstraints.push(constraint)
|
||||
}
|
||||
if (filterValue && constraint.filterValue === filterValue) {
|
||||
finalConstraints.push(constraint)
|
||||
}
|
||||
})
|
||||
return finalConstraints
|
||||
},
|
||||
}
|
||||
export const angledLine: SketchLineHelper = {
|
||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||
@ -1991,6 +2307,7 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
|
||||
export const sketchLineHelperMapKw: { [key: string]: SketchLineHelperKw } = {
|
||||
line,
|
||||
lineTo,
|
||||
circleThreePoint,
|
||||
} as const
|
||||
|
||||
export function changeSketchArguments(
|
||||
@ -2058,30 +2375,36 @@ export function changeSketchArguments(
|
||||
export function getConstraintInfo(
|
||||
callExpression: Node<CallExpression>,
|
||||
code: string,
|
||||
pathToNode: PathToNode
|
||||
pathToNode: PathToNode,
|
||||
filterValue?: string
|
||||
): ConstrainInfo[] {
|
||||
const fnName = callExpression?.callee?.name || ''
|
||||
if (!(fnName in sketchLineHelperMap)) return []
|
||||
return sketchLineHelperMap[fnName].getConstraintInfo(
|
||||
callExpression,
|
||||
code,
|
||||
pathToNode
|
||||
pathToNode,
|
||||
filterValue
|
||||
)
|
||||
}
|
||||
|
||||
export function getConstraintInfoKw(
|
||||
callExpression: Node<CallExpressionKw>,
|
||||
code: string,
|
||||
pathToNode: PathToNode
|
||||
pathToNode: PathToNode,
|
||||
filterValue?: string
|
||||
): ConstrainInfo[] {
|
||||
const fnName = callExpression?.callee?.name || ''
|
||||
const isAbsolute = findKwArg('endAbsolute', callExpression) !== undefined
|
||||
const isAbsolute =
|
||||
fnName === 'circleThreePoint' ||
|
||||
findKwArg('endAbsolute', callExpression) !== undefined
|
||||
if (!(fnName in sketchLineHelperMapKw)) return []
|
||||
const correctFnName = fnName === 'line' && isAbsolute ? 'lineTo' : fnName
|
||||
return sketchLineHelperMapKw[correctFnName].getConstraintInfo(
|
||||
callExpression,
|
||||
code,
|
||||
pathToNode
|
||||
pathToNode,
|
||||
filterValue
|
||||
)
|
||||
}
|
||||
|
||||
@ -2305,8 +2628,6 @@ function addTagToChamfer(
|
||||
if (err(variableDec)) return variableDec
|
||||
const isPipeExpression = pipeExpr.node.type === 'PipeExpression'
|
||||
|
||||
console.log('pipeExpr', pipeExpr, variableDec)
|
||||
// const callExpr = isPipeExpression ? pipeExpr.node.body[pipeIndex] : variableDec.node.init
|
||||
const callExpr = isPipeExpression
|
||||
? pipeExpr.node.body[pipeIndex]
|
||||
: variableDec.node.init
|
||||
@ -2387,7 +2708,6 @@ function addTagToChamfer(
|
||||
if (isPipeExpression) {
|
||||
pipeExpr.node.body.splice(pipeIndex, 0, newExpressionToInsert)
|
||||
} else {
|
||||
console.log('yo', createPipeExpression([newExpressionToInsert, callExpr]))
|
||||
callExpr.arguments[1] = createPipeSubstitution()
|
||||
variableDec.node.init = createPipeExpression([
|
||||
newExpressionToInsert,
|
||||
@ -2724,6 +3044,8 @@ export function isAbsoluteLine(lineCall: CallExpressionKw): boolean | Error {
|
||||
return new Error(
|
||||
`line call has neither ${ARG_END} nor ${ARG_END_ABSOLUTE} params`
|
||||
)
|
||||
case 'circleThreePoint':
|
||||
return false
|
||||
}
|
||||
return new Error(`Unknown sketch function ${name}`)
|
||||
}
|
||||
|
||||
@ -22,7 +22,6 @@ import {
|
||||
Literal,
|
||||
SourceRange,
|
||||
LiteralValue,
|
||||
recast,
|
||||
LabeledArg,
|
||||
} from '../wasm'
|
||||
import { getNodeFromPath, getNodeFromPathCurry } from '../queryAst'
|
||||
@ -217,14 +216,19 @@ function createStdlibCallExpressionKw(
|
||||
tool: ToolTip,
|
||||
labeled: LabeledArg[],
|
||||
tag?: Expr,
|
||||
valueUsedInTransform?: number
|
||||
valueUsedInTransform?: number,
|
||||
unlabeled?: Expr
|
||||
): CreatedSketchExprResult {
|
||||
const args = labeled
|
||||
if (tag) {
|
||||
args.push(createLabeledArg(ARG_TAG, tag))
|
||||
}
|
||||
return {
|
||||
callExp: createCallExpressionStdLibKw(tool, null, args),
|
||||
callExp: createCallExpressionStdLibKw(
|
||||
tool,
|
||||
unlabeled ? unlabeled : null,
|
||||
args
|
||||
),
|
||||
valueUsedInTransform,
|
||||
}
|
||||
}
|
||||
@ -1306,6 +1310,12 @@ export function getRemoveConstraintsTransform(
|
||||
},
|
||||
}
|
||||
|
||||
if (
|
||||
sketchFnExp.type === 'CallExpressionKw' &&
|
||||
sketchFnExp.callee.name === 'circleThreePoint'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
const isAbsolute =
|
||||
// isAbsolute doesn't matter if the call is positional.
|
||||
sketchFnExp.type === 'CallExpression' ? false : isAbsoluteLine(sketchFnExp)
|
||||
@ -1320,7 +1330,6 @@ export function getRemoveConstraintsTransform(
|
||||
? getFirstArg(sketchFnExp)
|
||||
: getArgForEnd(sketchFnExp)
|
||||
if (err(firstArg)) {
|
||||
console.error(firstArg)
|
||||
return false
|
||||
}
|
||||
|
||||
@ -1351,7 +1360,7 @@ export function getRemoveConstraintsTransform(
|
||||
|
||||
export function removeSingleConstraint({
|
||||
pathToCallExp,
|
||||
inputDetails,
|
||||
inputDetails: inputToReplace,
|
||||
ast,
|
||||
}: {
|
||||
pathToCallExp: PathToNode
|
||||
@ -1384,12 +1393,12 @@ export function removeSingleConstraint({
|
||||
// So we should update the call expression to use the inputs, except for
|
||||
// the inputDetails, input where we should use the rawValue(s)
|
||||
|
||||
if (inputDetails.type === 'arrayItem') {
|
||||
if (inputToReplace.type === 'arrayItem') {
|
||||
const values = inputs.map((arg) => {
|
||||
if (
|
||||
!(
|
||||
(arg.type === 'arrayItem' || arg.type === 'arrayOrObjItem') &&
|
||||
arg.index === inputDetails.index
|
||||
arg.index === inputToReplace.index
|
||||
)
|
||||
)
|
||||
return arg.expr
|
||||
@ -1397,9 +1406,9 @@ export function removeSingleConstraint({
|
||||
(rawValue) =>
|
||||
(rawValue.type === 'arrayItem' ||
|
||||
rawValue.type === 'arrayOrObjItem') &&
|
||||
rawValue.index === inputDetails.index
|
||||
rawValue.index === inputToReplace.index
|
||||
)?.expr
|
||||
return (arg.index === inputDetails.index && literal) || arg.expr
|
||||
return (arg.index === inputToReplace.index && literal) || arg.expr
|
||||
})
|
||||
if (callExp.node.type === 'CallExpression') {
|
||||
return createStdlibCallExpression(
|
||||
@ -1428,66 +1437,110 @@ export function removeSingleConstraint({
|
||||
}
|
||||
}
|
||||
if (
|
||||
inputDetails.type === 'arrayInObject' ||
|
||||
inputDetails.type === 'objectProperty'
|
||||
inputToReplace.type === 'arrayInObject' ||
|
||||
inputToReplace.type === 'objectProperty'
|
||||
) {
|
||||
const arrayDetailsNameBetterLater: {
|
||||
const arrayInput: {
|
||||
[key: string]: Parameters<typeof createArrayExpression>[0]
|
||||
} = {}
|
||||
const otherThing: Parameters<typeof createObjectExpression>[0] = {}
|
||||
inputs.forEach((arg) => {
|
||||
const objInput: Parameters<typeof createObjectExpression>[0] = {}
|
||||
const kwArgInput: ReturnType<typeof createLabeledArg>[] = []
|
||||
inputs.forEach((currentArg) => {
|
||||
if (
|
||||
arg.type !== 'objectProperty' &&
|
||||
arg.type !== 'arrayOrObjItem' &&
|
||||
arg.type !== 'arrayInObject'
|
||||
// should be one of these, return early to make TS happy.
|
||||
currentArg.type !== 'objectProperty' &&
|
||||
currentArg.type !== 'arrayOrObjItem' &&
|
||||
currentArg.type !== 'arrayInObject'
|
||||
)
|
||||
return
|
||||
const rawLiteralArrayInObject = rawArgs.find(
|
||||
(rawValue) =>
|
||||
rawValue.type === 'arrayInObject' &&
|
||||
rawValue.key === inputDetails.key &&
|
||||
rawValue.index === (arg.type === 'arrayInObject' ? arg.index : -1)
|
||||
rawValue.key === currentArg.key &&
|
||||
rawValue.index ===
|
||||
(currentArg.type === 'arrayInObject' ? currentArg.index : -1)
|
||||
)
|
||||
const rawLiteralObjProp = rawArgs.find(
|
||||
(rawValue) =>
|
||||
(rawValue.type === 'objectProperty' ||
|
||||
rawValue.type === 'arrayOrObjItem' ||
|
||||
rawValue.type === 'arrayInObject') &&
|
||||
rawValue.key === inputDetails.key
|
||||
rawValue.key === inputToReplace.key
|
||||
)
|
||||
if (
|
||||
inputDetails.type === 'arrayInObject' &&
|
||||
inputToReplace.type === 'arrayInObject' &&
|
||||
rawLiteralArrayInObject?.type === 'arrayInObject' &&
|
||||
rawLiteralArrayInObject?.index === inputDetails.index &&
|
||||
rawLiteralArrayInObject?.key === inputDetails.key
|
||||
rawLiteralArrayInObject?.index === inputToReplace.index &&
|
||||
rawLiteralArrayInObject?.key === inputToReplace.key
|
||||
) {
|
||||
if (!arrayDetailsNameBetterLater[arg.key])
|
||||
arrayDetailsNameBetterLater[arg.key] = []
|
||||
arrayDetailsNameBetterLater[inputDetails.key][inputDetails.index] =
|
||||
if (!arrayInput[currentArg.key]) {
|
||||
arrayInput[currentArg.key] = []
|
||||
}
|
||||
arrayInput[inputToReplace.key][inputToReplace.index] =
|
||||
rawLiteralArrayInObject.expr
|
||||
let existingKwgForKey = kwArgInput.find(
|
||||
(kwArg) => kwArg.label.name === currentArg.key
|
||||
)
|
||||
if (!existingKwgForKey) {
|
||||
existingKwgForKey = createLabeledArg(
|
||||
currentArg.key,
|
||||
createArrayExpression([])
|
||||
)
|
||||
kwArgInput.push(existingKwgForKey)
|
||||
}
|
||||
if (existingKwgForKey.arg.type === 'ArrayExpression') {
|
||||
existingKwgForKey.arg.elements[inputToReplace.index] =
|
||||
rawLiteralArrayInObject.expr
|
||||
}
|
||||
} else if (
|
||||
inputDetails.type === 'objectProperty' &&
|
||||
inputToReplace.type === 'objectProperty' &&
|
||||
(rawLiteralObjProp?.type === 'objectProperty' ||
|
||||
rawLiteralObjProp?.type === 'arrayOrObjItem') &&
|
||||
rawLiteralObjProp?.key === inputDetails.key &&
|
||||
arg.key === inputDetails.key
|
||||
rawLiteralObjProp?.key === inputToReplace.key &&
|
||||
currentArg.key === inputToReplace.key
|
||||
) {
|
||||
otherThing[inputDetails.key] = rawLiteralObjProp.expr
|
||||
} else if (arg.type === 'arrayInObject') {
|
||||
if (!arrayDetailsNameBetterLater[arg.key])
|
||||
arrayDetailsNameBetterLater[arg.key] = []
|
||||
arrayDetailsNameBetterLater[arg.key][arg.index] = arg.expr
|
||||
} else if (arg.type === 'objectProperty') {
|
||||
otherThing[arg.key] = arg.expr
|
||||
objInput[inputToReplace.key] = rawLiteralObjProp.expr
|
||||
} else if (currentArg.type === 'arrayInObject') {
|
||||
if (!arrayInput[currentArg.key]) arrayInput[currentArg.key] = []
|
||||
arrayInput[currentArg.key][currentArg.index] = currentArg.expr
|
||||
let existingKwgForKey = kwArgInput.find(
|
||||
(kwArg) => kwArg.label.name === currentArg.key
|
||||
)
|
||||
if (!existingKwgForKey) {
|
||||
existingKwgForKey = createLabeledArg(
|
||||
currentArg.key,
|
||||
createArrayExpression([])
|
||||
)
|
||||
kwArgInput.push(existingKwgForKey)
|
||||
}
|
||||
if (existingKwgForKey.arg.type === 'ArrayExpression') {
|
||||
existingKwgForKey.arg.elements[currentArg.index] = currentArg.expr
|
||||
}
|
||||
} else if (currentArg.type === 'objectProperty') {
|
||||
objInput[currentArg.key] = currentArg.expr
|
||||
}
|
||||
})
|
||||
const createObjParam: Parameters<typeof createObjectExpression>[0] = {}
|
||||
Object.entries(arrayDetailsNameBetterLater).forEach(([key, value]) => {
|
||||
Object.entries(arrayInput).forEach(([key, value]) => {
|
||||
createObjParam[key] = createArrayExpression(value)
|
||||
})
|
||||
if (
|
||||
callExp.node.callee.name === 'circleThreePoint' &&
|
||||
callExp.node.type === 'CallExpressionKw'
|
||||
) {
|
||||
// it's kwarg
|
||||
const inputPlane = callExp.node.unlabeled as Expr
|
||||
return createStdlibCallExpressionKw(
|
||||
callExp.node.callee.name as any,
|
||||
kwArgInput,
|
||||
tag,
|
||||
undefined,
|
||||
inputPlane
|
||||
)
|
||||
}
|
||||
const objExp = createObjectExpression({
|
||||
...createObjParam,
|
||||
...otherThing,
|
||||
...objInput,
|
||||
})
|
||||
return createStdlibCallExpression(
|
||||
callExp.node.callee.name as any,
|
||||
@ -1571,6 +1624,16 @@ function getTransformMapPathKw(
|
||||
}
|
||||
| false {
|
||||
const name = sketchFnExp.callee.name as ToolTip
|
||||
if (name === 'circleThreePoint') {
|
||||
const info = transformMap?.circleThreePoint?.free?.[constraintType]
|
||||
if (info)
|
||||
return {
|
||||
toolTip: 'circleThreePoint',
|
||||
lineInputType: 'free',
|
||||
constraintType,
|
||||
}
|
||||
return false
|
||||
}
|
||||
const isAbsolute = findKwArg(ARG_END_ABSOLUTE, sketchFnExp) !== undefined
|
||||
const nameAbsolute = name === 'line' ? 'lineTo' : name
|
||||
if (!toolTips.includes(name)) {
|
||||
@ -1989,6 +2052,13 @@ export function transformAstSketchLines({
|
||||
radius: seg.radius,
|
||||
from,
|
||||
}
|
||||
: seg.type === 'CircleThreePoint'
|
||||
? {
|
||||
type: 'circle-three-point-segment',
|
||||
p1: seg.p1,
|
||||
p2: seg.p2,
|
||||
p3: seg.p3,
|
||||
}
|
||||
: {
|
||||
type: 'straight-segment',
|
||||
to,
|
||||
|
||||
@ -45,6 +45,13 @@ interface ArcSegmentInput {
|
||||
center: [number, number]
|
||||
radius: number
|
||||
}
|
||||
/** Inputs for three point circle */
|
||||
interface CircleThreePointSegmentInput {
|
||||
type: 'circle-three-point-segment'
|
||||
p1: [number, number]
|
||||
p2: [number, number]
|
||||
p3: [number, number]
|
||||
}
|
||||
|
||||
/**
|
||||
* SegmentInputs is a union type that can be either a StraightSegmentInput or an ArcSegmentInput.
|
||||
@ -52,7 +59,10 @@ interface ArcSegmentInput {
|
||||
* - StraightSegmentInput: Represents a straight segment with a starting point (from) and an ending point (to).
|
||||
* - ArcSegmentInput: Represents an arc segment with a starting point (from), a center point, and a radius.
|
||||
*/
|
||||
export type SegmentInputs = StraightSegmentInput | ArcSegmentInput
|
||||
export type SegmentInputs =
|
||||
| StraightSegmentInput
|
||||
| ArcSegmentInput
|
||||
| CircleThreePointSegmentInput
|
||||
|
||||
/**
|
||||
* Interface for adding or replacing a sketch stblib call expression to a sketch.
|
||||
@ -85,6 +95,9 @@ export type InputArgKeys =
|
||||
| 'intersectTag'
|
||||
| 'radius'
|
||||
| 'center'
|
||||
| 'p1'
|
||||
| 'p2'
|
||||
| 'p3'
|
||||
export interface SingleValueInput<T> {
|
||||
type: 'singleValue'
|
||||
argType: LineInputsType
|
||||
@ -239,7 +252,8 @@ export interface SketchLineHelper {
|
||||
getConstraintInfo: (
|
||||
callExp: Node<CallExpression>,
|
||||
code: string,
|
||||
pathToNode: PathToNode
|
||||
pathToNode: PathToNode,
|
||||
filterValue?: string
|
||||
) => ConstrainInfo[]
|
||||
}
|
||||
|
||||
@ -267,6 +281,7 @@ export interface SketchLineHelperKw {
|
||||
getConstraintInfo: (
|
||||
callExp: Node<CallExpressionKw>,
|
||||
code: string,
|
||||
pathToNode: PathToNode
|
||||
pathToNode: PathToNode,
|
||||
filterValue?: string
|
||||
) => ConstrainInfo[]
|
||||
}
|
||||
|
||||
@ -14,20 +14,47 @@ import {
|
||||
import { filterArtifacts } from 'lang/std/artifactGraph'
|
||||
import { isArray, isOverlap } from 'lib/utils'
|
||||
|
||||
export function updatePathToNodeFromMap(
|
||||
oldPath: PathToNode,
|
||||
pathToNodeMap: { [key: number]: PathToNode }
|
||||
/**
|
||||
* Updates pathToNode body indices to account for the insertion of an expression
|
||||
* PathToNode expression is after the insertion index, that the body index is incremented
|
||||
* Negative insertion index means no insertion
|
||||
*/
|
||||
export function updatePathToNodePostExprInjection(
|
||||
pathToNode: PathToNode,
|
||||
exprInsertIndex: number
|
||||
): PathToNode {
|
||||
const updatedPathToNode = structuredClone(oldPath)
|
||||
let max = 0
|
||||
Object.values(pathToNodeMap).forEach((path) => {
|
||||
const index = Number(path[1][0])
|
||||
if (index > max) {
|
||||
max = index
|
||||
}
|
||||
})
|
||||
updatedPathToNode[1][0] = max
|
||||
return updatedPathToNode
|
||||
if (exprInsertIndex < 0) return pathToNode
|
||||
const bodyIndex = Number(pathToNode[1][0])
|
||||
if (bodyIndex < exprInsertIndex) return pathToNode
|
||||
const clone = structuredClone(pathToNode)
|
||||
clone[1][0] = bodyIndex + 1
|
||||
return clone
|
||||
}
|
||||
|
||||
export function updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath,
|
||||
sketchNodePaths,
|
||||
planeNodePath,
|
||||
exprInsertIndex,
|
||||
}: {
|
||||
sketchEntryNodePath: PathToNode
|
||||
sketchNodePaths: Array<PathToNode>
|
||||
planeNodePath: PathToNode
|
||||
exprInsertIndex: number
|
||||
}) {
|
||||
return {
|
||||
updatedSketchEntryNodePath: updatePathToNodePostExprInjection(
|
||||
sketchEntryNodePath,
|
||||
exprInsertIndex
|
||||
),
|
||||
updatedSketchNodePaths: sketchNodePaths.map((path) =>
|
||||
updatePathToNodePostExprInjection(path, exprInsertIndex)
|
||||
),
|
||||
updatedPlaneNodePath: updatePathToNodePostExprInjection(
|
||||
planeNodePath,
|
||||
exprInsertIndex
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export function isCursorInSketchCommandRange(
|
||||
@ -36,7 +63,7 @@ export function isCursorInSketchCommandRange(
|
||||
): string | false {
|
||||
const overlappingEntries = filterArtifacts(
|
||||
{
|
||||
types: ['segment', 'path'],
|
||||
types: ['segment', 'path', 'plane'],
|
||||
predicate: (artifact) => {
|
||||
return selectionRanges.graphSelections.some(
|
||||
(selection) =>
|
||||
@ -81,11 +108,27 @@ export function findKwArg(
|
||||
label: string,
|
||||
call: CallExpressionKw
|
||||
): Expr | undefined {
|
||||
return call.arguments.find((arg) => {
|
||||
return call?.arguments?.find((arg) => {
|
||||
return arg.label.name === label
|
||||
})?.arg
|
||||
}
|
||||
|
||||
/**
|
||||
Search the keyword arguments from a call for an argument with this label,
|
||||
returns the index of the argument as well.
|
||||
*/
|
||||
export function findKwArgWithIndex(
|
||||
label: string,
|
||||
call: CallExpressionKw
|
||||
): { expr: Expr; argIndex: number } | undefined {
|
||||
const index = call.arguments.findIndex((arg) => {
|
||||
return arg.label.name === label
|
||||
})
|
||||
return index >= 0
|
||||
? { expr: call.arguments[index].arg, argIndex: index }
|
||||
: undefined
|
||||
}
|
||||
|
||||
/**
|
||||
Search the keyword arguments from a call for an argument with one of these labels.
|
||||
*/
|
||||
|
||||
@ -18,7 +18,6 @@ import {
|
||||
default_project_settings,
|
||||
base64_decode,
|
||||
clear_scene_and_bust_cache,
|
||||
kcl_settings,
|
||||
change_kcl_settings,
|
||||
reloadModule,
|
||||
} from 'lib/wasm_lib_wrapper'
|
||||
@ -59,9 +58,6 @@ import { Artifact } from './std/artifactGraph'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
|
||||
import { MetaSettings } from 'wasm-lib/kcl/bindings/MetaSettings'
|
||||
import { UnitAngle, UnitLength } from 'wasm-lib/kcl/bindings/ModelingCmd'
|
||||
import { UnitLen } from 'wasm-lib/kcl/bindings/UnitLen'
|
||||
import { UnitAngle as UnitAng } from 'wasm-lib/kcl/bindings/UnitAngle'
|
||||
|
||||
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
@ -603,10 +599,6 @@ export const executor = async (
|
||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
||||
return Promise.reject(programMemoryOverride)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
||||
return Promise.reject(programMemoryOverride)
|
||||
|
||||
try {
|
||||
let jsAppSettings = default_app_settings()
|
||||
if (!TEST) {
|
||||
@ -861,35 +853,8 @@ export function base64Decode(base64: string): ArrayBuffer | Error {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the meta settings for the KCL. If no settings were set in the file,
|
||||
* returns null.
|
||||
*/
|
||||
export function kclSettings(
|
||||
kcl: string | Node<Program>
|
||||
): MetaSettings | null | Error {
|
||||
let program: Node<Program>
|
||||
if (typeof kcl === 'string') {
|
||||
const parseResult = parse(kcl)
|
||||
if (err(parseResult)) return parseResult
|
||||
if (!resultIsOk(parseResult)) {
|
||||
return new Error(`parse result had errors`, { cause: parseResult })
|
||||
}
|
||||
program = parseResult.program
|
||||
} else {
|
||||
program = kcl
|
||||
}
|
||||
try {
|
||||
return kcl_settings(JSON.stringify(program))
|
||||
} catch (e) {
|
||||
return new Error('Caught error getting kcl settings', { cause: e })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the meta settings for the kcl file.
|
||||
* @returns the new kcl string with the updated settings.
|
||||
*/
|
||||
/// Change the meta settings for the kcl file.
|
||||
/// Returns the new kcl string with the updated settings.
|
||||
export function changeKclSettings(
|
||||
kcl: string,
|
||||
settings: MetaSettings
|
||||
@ -897,59 +862,7 @@ export function changeKclSettings(
|
||||
try {
|
||||
return change_kcl_settings(kcl, JSON.stringify(settings))
|
||||
} catch (e) {
|
||||
console.error('Caught error changing kcl settings', e)
|
||||
return new Error('Caught error changing kcl settings', { cause: e })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a `UnitLength_type` to a `UnitLen`
|
||||
*/
|
||||
export function unitLengthToUnitLen(input: UnitLength): UnitLen {
|
||||
switch (input) {
|
||||
case 'm':
|
||||
return { type: 'M' }
|
||||
case 'cm':
|
||||
return { type: 'Cm' }
|
||||
case 'yd':
|
||||
return { type: 'Yards' }
|
||||
case 'ft':
|
||||
return { type: 'Feet' }
|
||||
case 'in':
|
||||
return { type: 'Inches' }
|
||||
default:
|
||||
return { type: 'Mm' }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert `UnitLen` to `UnitLength_type`.
|
||||
*/
|
||||
export function unitLenToUnitLength(input: UnitLen): UnitLength {
|
||||
switch (input.type) {
|
||||
case 'M':
|
||||
return 'm'
|
||||
case 'Cm':
|
||||
return 'cm'
|
||||
case 'Yards':
|
||||
return 'yd'
|
||||
case 'Feet':
|
||||
return 'ft'
|
||||
case 'Inches':
|
||||
return 'in'
|
||||
default:
|
||||
return 'mm'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert `UnitAngle` to `UnitAngle_type`.
|
||||
*/
|
||||
export function unitAngToUnitAngle(input: UnitAng): UnitAngle {
|
||||
switch (input.type) {
|
||||
case 'Radians':
|
||||
return 'radians'
|
||||
default:
|
||||
return 'degrees'
|
||||
console.error('Caught error changing kcl settings: ' + e)
|
||||
return new Error('Caught error changing kcl settings: ' + e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ export type ModelingCommandSchema = {
|
||||
Revolve: {
|
||||
selection: Selections
|
||||
angle: KclCommandValue
|
||||
axisOrEdge: string
|
||||
axisOrEdge: 'Axis' | 'Edge'
|
||||
axis: string
|
||||
edge: Selections
|
||||
}
|
||||
|
||||
@ -12,68 +12,104 @@ wallMountL = 2 // inches
|
||||
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
|
||||
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
|
||||
|
||||
|
||||
filletRadius = .375 // inches
|
||||
extFilletRadius = .25 // inches
|
||||
mountingHoleDiameter = 0.5 // inches
|
||||
|
||||
|
||||
// Calculate required thickness of bracket
|
||||
thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
|
||||
|
||||
filletRadius = .25
|
||||
extFilletRadius = filletRadius + thickness
|
||||
mountingHoleDiameter = 0.5
|
||||
|
||||
sketch001 = startSketchOn('XZ')
|
||||
// Sketch the bracket body and fillet the inner and outer edges of the bend
|
||||
bracketLeg1Sketch = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> xLine(shelfMountL - thickness, %, $seg01)
|
||||
|> yLine(thickness, %, $seg02)
|
||||
|> xLine(-shelfMountL, %, $seg03)
|
||||
|> yLine(-wallMountL, %, $seg04)
|
||||
|> xLine(thickness, %, $seg05)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg06)
|
||||
|> line(end = [shelfMountL - filletRadius, 0], tag = $fillet1)
|
||||
|> line(end = [0, width], tag = $fillet2)
|
||||
|> line(end = [-shelfMountL + filletRadius, 0])
|
||||
|> close()
|
||||
|> extrude(%, length = width)
|
||||
|> hole(circle({
|
||||
center = [1, 1],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|> hole(circle({
|
||||
center = [shelfMountL - 1.5, width - 1],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|> hole(circle({
|
||||
center = [1, width - 1],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|> hole(circle({
|
||||
center = [shelfMountL - 1.5, 1],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|
||||
// Extrude the leg 2 bracket sketch
|
||||
bracketLeg1Extrude = extrude(bracketLeg1Sketch, length = thickness)
|
||||
|> fillet({
|
||||
radius = extFilletRadius,
|
||||
tags = [getNextAdjacentEdge(seg03)]
|
||||
tags = [
|
||||
getNextAdjacentEdge(fillet1),
|
||||
getNextAdjacentEdge(fillet2)
|
||||
]
|
||||
}, %)
|
||||
|> fillet({
|
||||
radius = filletRadius,
|
||||
tags = [getNextAdjacentEdge(seg06)]
|
||||
|
||||
// Sketch the fillet arc
|
||||
filletSketch = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line(end = [0, thickness])
|
||||
|> arc({
|
||||
angleEnd = 180,
|
||||
angleStart = 90,
|
||||
radius = filletRadius + thickness
|
||||
}, %)
|
||||
|> line(end = [thickness, 0])
|
||||
|> arc({
|
||||
angleEnd = 90,
|
||||
angleStart = 180,
|
||||
radius = filletRadius
|
||||
}, %)
|
||||
|> fillet({
|
||||
radius = filletRadius,
|
||||
tags = [seg02, getOppositeEdge(seg02)],
|
||||
}, %)
|
||||
|> fillet({
|
||||
radius = filletRadius,
|
||||
tags = [seg05, getOppositeEdge(seg05)],
|
||||
}, %)
|
||||
|
||||
sketch002 = startSketchOn(sketch001, seg03)
|
||||
|> circle({
|
||||
center = [-1.25, 1],
|
||||
radius = mountingHoleDiameter / 2,
|
||||
}, %)
|
||||
|> patternLinear2d({
|
||||
instances = 2,
|
||||
distance = 2.5,
|
||||
axis = [-1, 0],
|
||||
}, %)
|
||||
|> patternLinear2d({
|
||||
instances = 2,
|
||||
distance = 4,
|
||||
axis = [0, 1],
|
||||
}, %)
|
||||
|> extrude(%, length = -thickness-.01)
|
||||
// Sketch the bend
|
||||
filletExtrude = extrude(filletSketch, length = -width)
|
||||
|
||||
sketch003 = startSketchOn(sketch001, seg04)
|
||||
|> circle({
|
||||
center = [1, -1],
|
||||
radius = mountingHoleDiameter / 2,
|
||||
}, %)
|
||||
|> patternLinear2d({
|
||||
instances = 2,
|
||||
distance = 4,
|
||||
axis = [1, 0],
|
||||
}, %)
|
||||
|> extrude(%, length = -thickness-0.1)
|
||||
// Create a custom plane for the leg that sits on the wall
|
||||
customPlane = {
|
||||
plane = {
|
||||
origin = { x = -filletRadius, y = 0, z = 0 },
|
||||
xAxis = { x = 0, y = 1, z = 0 },
|
||||
yAxis = { x = 0, y = 0, z = 1 },
|
||||
zAxis = { x = 1, y = 0, z = 0 }
|
||||
}
|
||||
}
|
||||
|
||||
// Create a sketch for the second leg
|
||||
bracketLeg2Sketch = startSketchOn(customPlane)
|
||||
|> startProfileAt([0, -filletRadius], %)
|
||||
|> line(end = [width, 0])
|
||||
|> line(end = [0, -wallMountL], tag = $fillet3)
|
||||
|> line(end = [-width, 0], tag = $fillet4)
|
||||
|> close()
|
||||
|> hole(circle({
|
||||
center = [1, -1.5],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|> hole(circle({
|
||||
center = [5, -1.5],
|
||||
radius = mountingHoleDiameter / 2
|
||||
}, %), %)
|
||||
|
||||
// Extrude the second leg
|
||||
bracketLeg2Extrude = extrude(bracketLeg2Sketch, length = -thickness)
|
||||
|> fillet({
|
||||
radius = extFilletRadius,
|
||||
tags = [
|
||||
getNextAdjacentEdge(fillet3),
|
||||
getNextAdjacentEdge(fillet4)
|
||||
]
|
||||
}, %)
|
||||
`
|
||||
|
||||
/**
|
||||
|
||||
@ -37,7 +37,7 @@ import {
|
||||
*/
|
||||
export const getRectangleCallExpressions = (
|
||||
rectangleOrigin: [number, number],
|
||||
tags: [string, string, string]
|
||||
tag: string
|
||||
) => [
|
||||
createCallExpressionStdLib('angledLine', [
|
||||
createArrayExpression([
|
||||
@ -45,30 +45,28 @@ export const getRectangleCallExpressions = (
|
||||
createLiteral(0), // This will be the width of the rectangle
|
||||
]),
|
||||
createPipeSubstitution(),
|
||||
createTagDeclarator(tags[0]),
|
||||
createTagDeclarator(tag),
|
||||
]),
|
||||
createCallExpressionStdLib('angledLine', [
|
||||
createArrayExpression([
|
||||
createBinaryExpression([
|
||||
createCallExpressionStdLib('segAng', [createIdentifier(tags[0])]),
|
||||
createCallExpressionStdLib('segAng', [createIdentifier(tag)]),
|
||||
'+',
|
||||
createLiteral(90),
|
||||
]), // 90 offset from the previous line
|
||||
createLiteral(0), // This will be the height of the rectangle
|
||||
]),
|
||||
createPipeSubstitution(),
|
||||
createTagDeclarator(tags[1]),
|
||||
]),
|
||||
createCallExpressionStdLib('angledLine', [
|
||||
createArrayExpression([
|
||||
createCallExpressionStdLib('segAng', [createIdentifier(tags[0])]), // same angle as the first line
|
||||
createCallExpressionStdLib('segAng', [createIdentifier(tag)]), // same angle as the first line
|
||||
createUnaryExpression(
|
||||
createCallExpressionStdLib('segLen', [createIdentifier(tags[0])]),
|
||||
createCallExpressionStdLib('segLen', [createIdentifier(tag)]),
|
||||
'-'
|
||||
), // negative height
|
||||
]),
|
||||
createPipeSubstitution(),
|
||||
createTagDeclarator(tags[2]),
|
||||
]),
|
||||
createCallExpressionStdLibKw('line', null, [
|
||||
createLabeledArg(
|
||||
@ -95,12 +93,12 @@ export function updateRectangleSketch(
|
||||
y: number,
|
||||
tag: string
|
||||
) {
|
||||
;((pipeExpression.body[2] as CallExpression)
|
||||
;((pipeExpression.body[1] as CallExpression)
|
||||
.arguments[0] as ArrayExpression) = createArrayExpression([
|
||||
createLiteral(x >= 0 ? 0 : 180),
|
||||
createLiteral(Math.abs(x)),
|
||||
])
|
||||
;((pipeExpression.body[3] as CallExpression)
|
||||
;((pipeExpression.body[2] as CallExpression)
|
||||
.arguments[0] as ArrayExpression) = createArrayExpression([
|
||||
createBinaryExpression([
|
||||
createCallExpressionStdLib('segAng', [createIdentifier(tag)]),
|
||||
@ -130,7 +128,7 @@ export function updateCenterRectangleSketch(
|
||||
let startY = originY - Math.abs(deltaY)
|
||||
|
||||
// pipeExpression.body[1] is startProfileAt
|
||||
let callExpression = pipeExpression.body[1]
|
||||
let callExpression = pipeExpression.body[0]
|
||||
if (isCallExpression(callExpression)) {
|
||||
const arrayExpression = callExpression.arguments[0]
|
||||
if (isArrayExpression(arrayExpression)) {
|
||||
@ -144,7 +142,7 @@ export function updateCenterRectangleSketch(
|
||||
const twoX = deltaX * 2
|
||||
const twoY = deltaY * 2
|
||||
|
||||
callExpression = pipeExpression.body[2]
|
||||
callExpression = pipeExpression.body[1]
|
||||
if (isCallExpression(callExpression)) {
|
||||
const arrayExpression = callExpression.arguments[0]
|
||||
if (isArrayExpression(arrayExpression)) {
|
||||
@ -160,7 +158,7 @@ export function updateCenterRectangleSketch(
|
||||
}
|
||||
}
|
||||
|
||||
callExpression = pipeExpression.body[3]
|
||||
callExpression = pipeExpression.body[2]
|
||||
if (isCallExpression(callExpression)) {
|
||||
const arrayExpression = callExpression.arguments[0]
|
||||
if (isArrayExpression(arrayExpression)) {
|
||||
|
||||
@ -276,18 +276,19 @@ export function getEventForSegmentSelection(
|
||||
}
|
||||
if (!id || !group) return null
|
||||
const artifact = engineCommandManager.artifactGraph.get(id)
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (!artifact || !codeRefs) return null
|
||||
if (!artifact) return null
|
||||
const node = getNodeFromPath<Expr>(kclManager.ast, group.userData.pathToNode)
|
||||
if (err(node)) return null
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: {
|
||||
artifact,
|
||||
codeRef: codeRefs[0],
|
||||
codeRef: {
|
||||
pathToNode: group?.userData?.pathToNode,
|
||||
range: [node.node.start, node.node.end, 0],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -572,8 +573,7 @@ export function getSelectionTypeDisplayText(
|
||||
const selectionsByType = getSelectionCountByType(selection)
|
||||
if (selectionsByType === 'none') return null
|
||||
|
||||
return selectionsByType
|
||||
.entries()
|
||||
return [...selectionsByType.entries()]
|
||||
.map(
|
||||
// Hack for showing "face" instead of "extrude-wall" in command bar text
|
||||
([type, count]) =>
|
||||
@ -581,7 +581,6 @@ export function getSelectionTypeDisplayText(
|
||||
count > 1 ? 's' : ''
|
||||
}`
|
||||
)
|
||||
.toArray()
|
||||
.join(', ')
|
||||
}
|
||||
|
||||
@ -591,7 +590,7 @@ export function canSubmitSelectionArg(
|
||||
) {
|
||||
return (
|
||||
selectionsByType !== 'none' &&
|
||||
selectionsByType.entries().every(([type, count]) => {
|
||||
[...selectionsByType.entries()].every(([type, count]) => {
|
||||
const foundIndex = argument.selectionTypes.findIndex((s) => s === type)
|
||||
return (
|
||||
foundIndex !== -1 &&
|
||||
@ -819,8 +818,8 @@ export async function sendSelectEventToEngine(
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
el,
|
||||
streamWidth: engineCommandManager.width,
|
||||
streamHeight: engineCommandManager.height,
|
||||
streamWidth: el.clientWidth,
|
||||
streamHeight: el.clientHeight,
|
||||
})
|
||||
const res = await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
@ -867,7 +866,6 @@ export function updateSelections(
|
||||
JSON.stringify(pathToNode)
|
||||
) {
|
||||
artifact = a
|
||||
console.log('found artifact', a)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,10 +4,6 @@ import { AtLeast, PathValue, Paths } from 'lib/types'
|
||||
import { CommandArgumentConfig } from 'lib/commandTypes'
|
||||
import { Themes } from 'lib/theme'
|
||||
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
||||
import {
|
||||
UnitAngle_type,
|
||||
UnitLength_type,
|
||||
} from '@kittycad/lib/dist/types/src/models'
|
||||
import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType'
|
||||
|
||||
export interface SettingsViaQueryString {
|
||||
@ -142,12 +138,3 @@ type RecursiveSettingsPayloads<T> = {
|
||||
}
|
||||
|
||||
export type SaveSettingsPayload = RecursiveSettingsPayloads<typeof settings>
|
||||
|
||||
/**
|
||||
* Annotation names for default units are defined on rust side in
|
||||
* src/wasm-lib/kcl/src/execution/annotations.rs
|
||||
*/
|
||||
export interface KclSettingsAnnotation {
|
||||
defaultLengthUnit?: UnitLength_type
|
||||
defaultAngleUnit?: UnitAngle_type
|
||||
}
|
||||
|
||||
@ -2,8 +2,6 @@ import { CustomIconName } from 'components/CustomIcon'
|
||||
import { DEV } from 'env'
|
||||
import { commandBarActor, commandBarMachine } from 'machines/commandBarMachine'
|
||||
import {
|
||||
canRectangleOrCircleTool,
|
||||
isClosedSketch,
|
||||
isEditingExistingSketch,
|
||||
modelingMachine,
|
||||
pipeHasCircle,
|
||||
@ -72,7 +70,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
icon: 'sketch',
|
||||
status: 'available',
|
||||
title: ({ sketchPathId }) =>
|
||||
`${sketchPathId ? 'Edit' : 'Start'} Sketch`,
|
||||
sketchPathId ? 'Edit Sketch' : 'Start Sketch',
|
||||
showTitle: true,
|
||||
hotkey: 'S',
|
||||
description: 'Start drawing a 2D sketch',
|
||||
@ -360,22 +358,14 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
{
|
||||
id: 'line',
|
||||
onClick: ({ modelingState, modelingSend }) => {
|
||||
if (modelingState.matches({ Sketch: { 'Line tool': 'No Points' } })) {
|
||||
// Exit the sketch state if there are no points and they press ESC
|
||||
modelingSend({
|
||||
type: 'Cancel',
|
||||
})
|
||||
} else {
|
||||
// Exit the tool if there are points and they press ESC
|
||||
modelingSend({
|
||||
type: 'change tool',
|
||||
data: {
|
||||
tool: !modelingState.matches({ Sketch: 'Line tool' })
|
||||
? 'line'
|
||||
: 'none',
|
||||
},
|
||||
})
|
||||
}
|
||||
modelingSend({
|
||||
type: 'change tool',
|
||||
data: {
|
||||
tool: !modelingState.matches({ Sketch: 'Line tool' })
|
||||
? 'line'
|
||||
: 'none',
|
||||
},
|
||||
})
|
||||
},
|
||||
icon: 'line',
|
||||
status: 'available',
|
||||
@ -386,8 +376,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
}) ||
|
||||
state.matches({
|
||||
Sketch: { 'Circle tool': 'Awaiting Radius' },
|
||||
}) ||
|
||||
isClosedSketch(state.context),
|
||||
}),
|
||||
title: 'Line',
|
||||
hotkey: (state) =>
|
||||
state.matches({ Sketch: 'Line tool' }) ? ['Esc', 'L'] : 'L',
|
||||
@ -467,14 +456,10 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
icon: 'circle',
|
||||
status: 'available',
|
||||
title: 'Center circle',
|
||||
disabled: (state) =>
|
||||
state.matches('Sketch no face') ||
|
||||
(!canRectangleOrCircleTool(state.context) &&
|
||||
!state.matches({ Sketch: 'Circle tool' }) &&
|
||||
!state.matches({ Sketch: 'circle3PointToolSelect' })),
|
||||
disabled: (state) => state.matches('Sketch no face'),
|
||||
isActive: (state) =>
|
||||
state.matches({ Sketch: 'Circle tool' }) ||
|
||||
state.matches({ Sketch: 'circle3PointToolSelect' }),
|
||||
state.matches({ Sketch: 'Circle three point tool' }),
|
||||
hotkey: (state) =>
|
||||
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
|
||||
showTitle: false,
|
||||
@ -488,9 +473,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
type: 'change tool',
|
||||
data: {
|
||||
tool: !modelingState.matches({
|
||||
Sketch: 'circle3PointToolSelect',
|
||||
Sketch: 'Circle three point tool',
|
||||
})
|
||||
? 'circle3Points'
|
||||
? 'circleThreePointNeo'
|
||||
: 'none',
|
||||
},
|
||||
}),
|
||||
@ -516,10 +501,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
}),
|
||||
icon: 'rectangle',
|
||||
status: 'available',
|
||||
disabled: (state) =>
|
||||
state.matches('Sketch no face') ||
|
||||
(!canRectangleOrCircleTool(state.context) &&
|
||||
!state.matches({ Sketch: 'Rectangle tool' })),
|
||||
disabled: (state) => state.matches('Sketch no face'),
|
||||
title: 'Corner rectangle',
|
||||
hotkey: (state) =>
|
||||
state.matches({ Sketch: 'Rectangle tool' }) ? ['Esc', 'R'] : 'R',
|
||||
@ -542,10 +524,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
}),
|
||||
icon: 'arc',
|
||||
status: 'available',
|
||||
disabled: (state) =>
|
||||
state.matches('Sketch no face') ||
|
||||
(!canRectangleOrCircleTool(state.context) &&
|
||||
!state.matches({ Sketch: 'Center Rectangle tool' })),
|
||||
disabled: (state) => state.matches('Sketch no face'),
|
||||
title: 'Center rectangle',
|
||||
hotkey: (state) =>
|
||||
state.matches({ Sketch: 'Center Rectangle tool' })
|
||||
|
||||
@ -97,3 +97,7 @@ export function trap<T>(
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
export function reject(errOrString: Error | string): Promise<never> {
|
||||
return Promise.reject(errOrString)
|
||||
}
|
||||
|
||||
@ -26,7 +26,6 @@ import {
|
||||
default_project_settings as DefaultProjectSettings,
|
||||
base64_decode as Base64Decode,
|
||||
clear_scene_and_bust_cache as ClearSceneAndBustCache,
|
||||
kcl_settings as KclSettings,
|
||||
change_kcl_settings as ChangeKclSettings,
|
||||
} from '../wasm-lib/pkg/wasm_lib'
|
||||
|
||||
@ -112,9 +111,6 @@ export const clear_scene_and_bust_cache: typeof ClearSceneAndBustCache = (
|
||||
) => {
|
||||
return getModule().clear_scene_and_bust_cache(...args)
|
||||
}
|
||||
export const kcl_settings: typeof KclSettings = (...args) => {
|
||||
return getModule().kcl_settings(...args)
|
||||
}
|
||||
export const change_kcl_settings: typeof ChangeKclSettings = (...args) => {
|
||||
return getModule().change_kcl_settings(...args)
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ function OnboardingWarningWeb(props: OnboardingResetWarningProps) {
|
||||
codeManager.updateCodeStateEditor(bracket)
|
||||
await codeManager.writeToFile()
|
||||
|
||||
await kclManager.executeCode(true)
|
||||
await kclManager.executeCode({ zoomToFit: true })
|
||||
props.setShouldShowWarning(false)
|
||||
}, reportRejection)}
|
||||
nextText="Overwrite code and continue"
|
||||
|
||||
@ -11,7 +11,7 @@ export default function Sketching() {
|
||||
async function clearEditor() {
|
||||
// We do want to update both the state and editor here.
|
||||
codeManager.updateCodeStateEditor('')
|
||||
await kclManager.executeCode(true)
|
||||
await kclManager.executeCode({ zoomToFit: true })
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
|
||||
@ -100,7 +100,7 @@ export function useDemoCode() {
|
||||
setTimeout(
|
||||
toSync(async () => {
|
||||
codeManager.updateCodeStateEditor(bracket)
|
||||
await kclManager.executeCode(true)
|
||||
await kclManager.executeCode({ zoomToFit: true })
|
||||
await codeManager.writeToFile()
|
||||
}, reportRejection)
|
||||
)
|
||||
|
||||
8
src/wasm-lib/Cargo.lock
generated
@ -2014,9 +2014,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "miette"
|
||||
version = "7.5.0"
|
||||
version = "7.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a955165f87b37fd1862df2a59547ac542c77ef6d17c666f619d1ad22dd89484"
|
||||
checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"backtrace-ext",
|
||||
@ -2034,9 +2034,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "miette-derive"
|
||||
version = "7.5.0"
|
||||
version = "7.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf45bf44ab49be92fd1227a3be6fc6f617f1a337c06af54981048574d8783147"
|
||||
checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@ -37,7 +37,7 @@ kittycad = { workspace = true }
|
||||
kittycad-modeling-cmds = { workspace = true }
|
||||
lazy_static = "1.5.0"
|
||||
measurements = "0.11.0"
|
||||
miette = "7.5.0"
|
||||
miette = "7.2.0"
|
||||
mime_guess = "2.0.5"
|
||||
parse-display = "0.9.1"
|
||||
pyo3 = { version = "0.22.6", optional = true }
|
||||
@ -116,7 +116,7 @@ expectorate = "1.1.0"
|
||||
handlebars = "6.3.0"
|
||||
image = { version = "0.25.5", default-features = false, features = ["png"] }
|
||||
insta = { version = "1.41.1", features = ["json", "filters", "redactions"] }
|
||||
miette = { version = "7.5.0", features = ["fancy"] }
|
||||
miette = { version = "7.2.0", features = ["fancy"] }
|
||||
pretty_assertions = "1.4.1"
|
||||
tokio = { version = "1.41.1", features = ["rt-multi-thread", "macros", "time"] }
|
||||
twenty-twenty = "0.8.0"
|
||||
|
||||
@ -188,6 +188,9 @@ pub struct Wall {
|
||||
pub sweep_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub path_ids: Vec<ArtifactId>,
|
||||
/// This is for the sketch-on-face plane, not for the wall itself. Traverse
|
||||
/// to the extrude and/or segment to get the wall's code_ref.
|
||||
pub face_code_ref: CodeRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
@ -201,6 +204,9 @@ pub struct Cap {
|
||||
pub sweep_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub path_ids: Vec<ArtifactId>,
|
||||
/// This is for the sketch-on-face plane, not for the cap itself. Traverse
|
||||
/// to the extrude and/or segment to get the cap's code_ref.
|
||||
pub face_code_ref: CodeRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
@ -619,6 +625,17 @@ fn artifacts_to_update(
|
||||
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
|
||||
sweep_id: wall.sweep_id,
|
||||
path_ids: wall.path_ids.clone(),
|
||||
face_code_ref: wall.face_code_ref.clone(),
|
||||
})]);
|
||||
}
|
||||
Some(Artifact::Cap(cap)) => {
|
||||
return Ok(vec![Artifact::Cap(Cap {
|
||||
id: current_plane_id.into(),
|
||||
sub_type: cap.sub_type,
|
||||
edge_cut_edge_ids: cap.edge_cut_edge_ids.clone(),
|
||||
sweep_id: cap.sweep_id,
|
||||
path_ids: cap.path_ids.clone(),
|
||||
face_code_ref: cap.face_code_ref.clone(),
|
||||
})]);
|
||||
}
|
||||
Some(_) | None => {
|
||||
@ -668,6 +685,7 @@ fn artifacts_to_update(
|
||||
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
|
||||
sweep_id: wall.sweep_id,
|
||||
path_ids: vec![id],
|
||||
face_code_ref: wall.face_code_ref.clone(),
|
||||
}));
|
||||
}
|
||||
return Ok(return_arr);
|
||||
@ -794,13 +812,48 @@ fn artifacts_to_update(
|
||||
source_ranges: vec![range],
|
||||
})
|
||||
})?;
|
||||
return_arr.push(Artifact::Wall(Wall {
|
||||
let extra_artifact = _exec_artifacts.values().find_map(|a| {
|
||||
if let Artifact::StartSketchOnFace { face_id: id, .. } = a {
|
||||
if *id == face_id.0 {
|
||||
return Some(a.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
let sketch_on_face_source_range = extra_artifact.and_then(|a| match a {
|
||||
Artifact::StartSketchOnFace { source_range, .. } => Some(source_range),
|
||||
_ => None,
|
||||
});
|
||||
let mut wall_artifact = Wall {
|
||||
id: face_id,
|
||||
seg_id: curve_id,
|
||||
edge_cut_edge_ids: Vec::new(),
|
||||
sweep_id: path.sweep_id.expect("Expected sweep_id to be Some"),
|
||||
path_ids: Vec::new(),
|
||||
face_code_ref: CodeRef {
|
||||
range,
|
||||
path_to_node: path_to_node.clone(),
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(sketch_on_face_source_range) = sketch_on_face_source_range {
|
||||
wall_artifact.face_code_ref = CodeRef {
|
||||
range: sketch_on_face_source_range,
|
||||
path_to_node: path_to_node.clone(),
|
||||
};
|
||||
}
|
||||
let wall = Wall {
|
||||
id: face_id,
|
||||
seg_id: curve_id,
|
||||
edge_cut_edge_ids: Vec::new(),
|
||||
sweep_id: path_sweep_id,
|
||||
path_ids: vec![],
|
||||
}));
|
||||
face_code_ref: CodeRef {
|
||||
range,
|
||||
path_to_node: path_to_node.clone(),
|
||||
},
|
||||
};
|
||||
return_arr.push(Artifact::Wall(wall));
|
||||
let mut new_seg = seg.clone();
|
||||
new_seg.surface_id = Some(face_id);
|
||||
return_arr.push(Artifact::Segment(new_seg));
|
||||
@ -820,6 +873,7 @@ fn artifacts_to_update(
|
||||
let Some(face_id) = face.face_id.map(ArtifactId::new) else {
|
||||
continue;
|
||||
};
|
||||
let cap = face.clone().cap;
|
||||
let path_sweep_id = path.sweep_id.ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails {
|
||||
message:format!(
|
||||
@ -828,13 +882,39 @@ fn artifacts_to_update(
|
||||
source_ranges: vec![range],
|
||||
})
|
||||
})?;
|
||||
return_arr.push(Artifact::Cap(Cap {
|
||||
let extra_artifact = _exec_artifacts.values().find(|a| {
|
||||
if let Artifact::StartSketchOnFace { face_id: id, .. } = a {
|
||||
*id == face_id.0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
let sketch_on_face_source_range = extra_artifact.and_then(|a| match a {
|
||||
Artifact::StartSketchOnFace { source_range, .. } => Some(source_range),
|
||||
_ => None,
|
||||
});
|
||||
let mut cap_artifact = Cap {
|
||||
id: face_id,
|
||||
sub_type,
|
||||
sub_type: match cap {
|
||||
ExtrusionFaceCapType::Bottom => CapSubType::Start,
|
||||
_ => CapSubType::End,
|
||||
},
|
||||
edge_cut_edge_ids: Vec::new(),
|
||||
sweep_id: path_sweep_id,
|
||||
sweep_id: path.sweep_id.expect("Expected sweep_id to be Some"),
|
||||
path_ids: Vec::new(),
|
||||
}));
|
||||
face_code_ref: CodeRef {
|
||||
range,
|
||||
path_to_node: path_to_node.clone(),
|
||||
},
|
||||
};
|
||||
if let Some(sketch_on_face_source_range) = sketch_on_face_source_range {
|
||||
let range = sketch_on_face_source_range;
|
||||
cap_artifact.face_code_ref = CodeRef {
|
||||
range: *range,
|
||||
path_to_node: path_to_node.clone(),
|
||||
};
|
||||
}
|
||||
return_arr.push(Artifact::Cap(cap_artifact));
|
||||
let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -242,7 +242,7 @@ firstSketch = startSketchOn('XY')
|
||||
|> extrude(length = 6)
|
||||
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
|
||||
|
||||
let (program, ctx, _) = parse_execute(new).await.unwrap();
|
||||
|
||||
@ -273,7 +273,7 @@ firstSketch = startSketchOn('XY')
|
||||
|> extrude(length = 6)
|
||||
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25) "#;
|
||||
shell({ faces = ['end'], thickness = 0.25 }, firstSketch) "#;
|
||||
|
||||
let new = r#"// Remove the end face for the extrusion.
|
||||
firstSketch = startSketchOn('XY')
|
||||
@ -285,7 +285,7 @@ firstSketch = startSketchOn('XY')
|
||||
|> extrude(length = 6)
|
||||
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
|
||||
|
||||
let (program_old, ctx, _) = parse_execute(old).await.unwrap();
|
||||
|
||||
@ -318,7 +318,7 @@ firstSketch = startSketchOn('XY')
|
||||
|> extrude(length = 6)
|
||||
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25) "#;
|
||||
shell({ faces = ['end'], thickness = 0.25 }, firstSketch) "#;
|
||||
|
||||
let new = r#"// Remove the end face for the extrusion.
|
||||
firstSketch = startSketchOn('XY')
|
||||
@ -330,7 +330,7 @@ firstSketch = startSketchOn('XY')
|
||||
|> extrude(length = 6)
|
||||
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
|
||||
|
||||
let (program, ctx, _) = parse_execute(old).await.unwrap();
|
||||
|
||||
@ -363,7 +363,7 @@ firstSketch = startSketchOn('XY')
|
||||
|> extrude(length = 6)
|
||||
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25) "#;
|
||||
shell({ faces = ['end'], thickness = 0.25 }, firstSketch) "#;
|
||||
|
||||
let new = r#"// Remove the end face for the extrusion.
|
||||
firstSketch = startSketchOn('XY')
|
||||
@ -375,7 +375,7 @@ firstSketch = startSketchOn('XY')
|
||||
|> extrude(length = 6)
|
||||
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
|
||||
|
||||
let (program, ctx, _) = parse_execute(old).await.unwrap();
|
||||
|
||||
@ -409,7 +409,7 @@ firstSketch = startSketchOn('XY')
|
||||
|> extrude(length = 6)
|
||||
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
|
||||
|
||||
let (program, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
|
||||
@ -451,7 +451,7 @@ firstSketch = startSketchOn('XY')
|
||||
|> extrude(length = 6)
|
||||
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
|
||||
|
||||
let (program, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
|
||||
@ -486,7 +486,7 @@ firstSketch = startSketchOn('XY')
|
||||
|> extrude(length = 6)
|
||||
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
|
||||
|
||||
let (program, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ use crate::{
|
||||
errors::KclError,
|
||||
execution::{ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
|
||||
parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
|
||||
std::shapes::circle_three_point,
|
||||
};
|
||||
|
||||
type Point2D = kcmc::shared::Point2d<f64>;
|
||||
@ -32,16 +33,6 @@ impl Geometry {
|
||||
Geometry::Solid(e) => e.id,
|
||||
}
|
||||
}
|
||||
|
||||
/// If this geometry is the result of a pattern, then return the ID of
|
||||
/// the original sketch which was patterned.
|
||||
/// Equivalent to the `id()` method if this isn't a pattern.
|
||||
pub fn original_id(&self) -> uuid::Uuid {
|
||||
match self {
|
||||
Geometry::Sketch(s) => s.original_id,
|
||||
Geometry::Solid(e) => e.sketch.original_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of geometry.
|
||||
@ -253,9 +244,9 @@ pub struct Plane {
|
||||
pub value: PlaneType,
|
||||
/// Origin of the plane.
|
||||
pub origin: Point3d,
|
||||
/// What should the plane’s X axis be?
|
||||
/// What should the plane's X axis be?
|
||||
pub x_axis: Point3d,
|
||||
/// What should the plane’s Y axis be?
|
||||
/// What should the plane's Y axis be?
|
||||
pub y_axis: Point3d,
|
||||
/// The z-axis (normal).
|
||||
pub z_axis: Point3d,
|
||||
@ -376,9 +367,9 @@ pub struct Face {
|
||||
pub artifact_id: ArtifactId,
|
||||
/// The tag of the face.
|
||||
pub value: String,
|
||||
/// What should the face’s X axis be?
|
||||
/// What should the face's X axis be?
|
||||
pub x_axis: Point3d,
|
||||
/// What should the face’s Y axis be?
|
||||
/// What should the face's Y axis be?
|
||||
pub y_axis: Point3d,
|
||||
/// The z-axis (normal).
|
||||
pub z_axis: Point3d,
|
||||
@ -429,8 +420,6 @@ pub struct Sketch {
|
||||
/// The original id of the sketch. This stays the same even if the sketch is
|
||||
/// is sketched on face etc.
|
||||
pub artifact_id: ArtifactId,
|
||||
#[ts(skip)]
|
||||
pub original_id: uuid::Uuid,
|
||||
pub units: UnitLen,
|
||||
/// Metadata.
|
||||
#[serde(rename = "__meta")]
|
||||
@ -785,6 +774,19 @@ pub enum Path {
|
||||
/// This is used to compute the tangential angle.
|
||||
ccw: bool,
|
||||
},
|
||||
CircleThreePoint {
|
||||
#[serde(flatten)]
|
||||
base: BasePath,
|
||||
/// Point 1 of the circle
|
||||
#[ts(type = "[number, number]")]
|
||||
p1: [f64; 2],
|
||||
/// Point 2 of the circle
|
||||
#[ts(type = "[number, number]")]
|
||||
p2: [f64; 2],
|
||||
/// Point 3 of the circle
|
||||
#[ts(type = "[number, number]")]
|
||||
p3: [f64; 2],
|
||||
},
|
||||
/// A path that is horizontal.
|
||||
Horizontal {
|
||||
#[serde(flatten)]
|
||||
@ -827,6 +829,7 @@ enum PathType {
|
||||
TangentialArc,
|
||||
TangentialArcTo,
|
||||
Circle,
|
||||
CircleThreePoint,
|
||||
Horizontal,
|
||||
AngledLineTo,
|
||||
Arc,
|
||||
@ -839,6 +842,7 @@ impl From<&Path> for PathType {
|
||||
Path::TangentialArcTo { .. } => Self::TangentialArcTo,
|
||||
Path::TangentialArc { .. } => Self::TangentialArc,
|
||||
Path::Circle { .. } => Self::Circle,
|
||||
Path::CircleThreePoint { .. } => Self::CircleThreePoint,
|
||||
Path::Horizontal { .. } => Self::Horizontal,
|
||||
Path::AngledLineTo { .. } => Self::AngledLineTo,
|
||||
Path::Base { .. } => Self::Base,
|
||||
@ -857,6 +861,7 @@ impl Path {
|
||||
Path::TangentialArcTo { base, .. } => base.geo_meta.id,
|
||||
Path::TangentialArc { base, .. } => base.geo_meta.id,
|
||||
Path::Circle { base, .. } => base.geo_meta.id,
|
||||
Path::CircleThreePoint { base, .. } => base.geo_meta.id,
|
||||
Path::Arc { base, .. } => base.geo_meta.id,
|
||||
}
|
||||
}
|
||||
@ -870,6 +875,7 @@ impl Path {
|
||||
Path::TangentialArcTo { base, .. } => base.tag.clone(),
|
||||
Path::TangentialArc { base, .. } => base.tag.clone(),
|
||||
Path::Circle { base, .. } => base.tag.clone(),
|
||||
Path::CircleThreePoint { base, .. } => base.tag.clone(),
|
||||
Path::Arc { base, .. } => base.tag.clone(),
|
||||
}
|
||||
}
|
||||
@ -883,6 +889,7 @@ impl Path {
|
||||
Path::TangentialArcTo { base, .. } => base,
|
||||
Path::TangentialArc { base, .. } => base,
|
||||
Path::Circle { base, .. } => base,
|
||||
Path::CircleThreePoint { base, .. } => base,
|
||||
Path::Arc { base, .. } => base,
|
||||
}
|
||||
}
|
||||
@ -920,6 +927,15 @@ impl Path {
|
||||
linear_distance(self.get_from(), self.get_to())
|
||||
}
|
||||
Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius,
|
||||
Self::CircleThreePoint { .. } => {
|
||||
let circle_center = crate::std::utils::calculate_circle_from_3_points([
|
||||
self.get_base().from.into(),
|
||||
self.get_base().to.into(),
|
||||
self.get_base().to.into(),
|
||||
]);
|
||||
let radius = linear_distance(&[circle_center.center.x, circle_center.center.y], &self.get_base().from);
|
||||
2.0 * std::f64::consts::PI * radius
|
||||
}
|
||||
Self::Arc { .. } => {
|
||||
// TODO: Call engine utils to figure this out.
|
||||
linear_distance(self.get_from(), self.get_to())
|
||||
@ -936,6 +952,7 @@ impl Path {
|
||||
Path::TangentialArcTo { base, .. } => Some(base),
|
||||
Path::TangentialArc { base, .. } => Some(base),
|
||||
Path::Circle { base, .. } => Some(base),
|
||||
Path::CircleThreePoint { base, .. } => Some(base),
|
||||
Path::Arc { base, .. } => Some(base),
|
||||
}
|
||||
}
|
||||
@ -955,6 +972,17 @@ impl Path {
|
||||
ccw: *ccw,
|
||||
radius: *radius,
|
||||
},
|
||||
Path::CircleThreePoint { p1, p2, p3, .. } => {
|
||||
let circle_center =
|
||||
crate::std::utils::calculate_circle_from_3_points([(*p1).into(), (*p2).into(), (*p3).into()]);
|
||||
let radius = linear_distance(&[circle_center.center.x, circle_center.center.y], &p1);
|
||||
let center_point = [circle_center.center.x, circle_center.center.y];
|
||||
GetTangentialInfoFromPathsResult::Circle {
|
||||
center: center_point,
|
||||
ccw: true,
|
||||
radius,
|
||||
}
|
||||
}
|
||||
Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {
|
||||
let base = self.get_base();
|
||||
GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
|
||||
|
||||
@ -1590,10 +1590,10 @@ let w = f() + f()
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(length = 40.14)
|
||||
|> shell(
|
||||
thickness = 3.14,
|
||||
faces = [seg01]
|
||||
)
|
||||
|> shell({
|
||||
faces: [seg01],
|
||||
thickness: 3.14,
|
||||
}, %)
|
||||
"#;
|
||||
|
||||
let ctx = crate::test_server::new_context(UnitLength::Mm, true, None)
|
||||
@ -1620,10 +1620,10 @@ let w = f() + f()
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(length = 40.14)
|
||||
|> shell(
|
||||
faces = [seg01],
|
||||
thickness = 3.14,
|
||||
)
|
||||
|> shell({
|
||||
faces: [seg01],
|
||||
thickness: 3.14,
|
||||
}, %)
|
||||
"#;
|
||||
|
||||
// Execute a slightly different program again.
|
||||
|
||||
@ -159,8 +159,8 @@ impl Program {
|
||||
}
|
||||
|
||||
/// Get the meta settings for the kcl file from the annotations.
|
||||
pub fn meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
|
||||
self.ast.meta_settings()
|
||||
pub fn get_meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
|
||||
self.ast.get_meta_settings()
|
||||
}
|
||||
|
||||
/// Change the meta settings for the kcl file.
|
||||
|
||||
@ -269,7 +269,7 @@ impl Node<Program> {
|
||||
}
|
||||
|
||||
/// Get the annotations for the meta settings from the kcl file.
|
||||
pub fn meta_settings(&self) -> Result<Option<crate::execution::MetaSettings>, KclError> {
|
||||
pub fn get_meta_settings(&self) -> Result<Option<crate::execution::MetaSettings>, KclError> {
|
||||
for annotation_node in self.annotations() {
|
||||
let annotation = &annotation_node.value;
|
||||
if annotation.annotation_name() == Some(annotations::SETTINGS) {
|
||||
@ -3881,7 +3881,7 @@ const cylinder = startSketchOn('-XZ')
|
||||
|
||||
startSketchOn('XY')"#;
|
||||
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||
let result = program.meta_settings().unwrap();
|
||||
let result = program.get_meta_settings().unwrap();
|
||||
assert!(result.is_some());
|
||||
let meta_settings = result.unwrap();
|
||||
|
||||
@ -3897,7 +3897,7 @@ startSketchOn('XY')"#;
|
||||
|
||||
startSketchOn('XY')"#;
|
||||
let mut program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||
let result = program.meta_settings().unwrap();
|
||||
let result = program.get_meta_settings().unwrap();
|
||||
assert!(result.is_some());
|
||||
let meta_settings = result.unwrap();
|
||||
|
||||
@ -3914,7 +3914,7 @@ startSketchOn('XY')"#;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = new_program.meta_settings().unwrap();
|
||||
let result = new_program.get_meta_settings().unwrap();
|
||||
assert!(result.is_some());
|
||||
let meta_settings = result.unwrap();
|
||||
|
||||
@ -3939,7 +3939,7 @@ startSketchOn('XY')
|
||||
async fn test_parse_get_meta_settings_nothing_to_mm() {
|
||||
let some_program_string = r#"startSketchOn('XY')"#;
|
||||
let mut program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||
let result = program.meta_settings().unwrap();
|
||||
let result = program.get_meta_settings().unwrap();
|
||||
assert!(result.is_none());
|
||||
|
||||
// Edit the ast.
|
||||
@ -3950,7 +3950,7 @@ startSketchOn('XY')
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = new_program.meta_settings().unwrap();
|
||||
let result = new_program.get_meta_settings().unwrap();
|
||||
assert!(result.is_some());
|
||||
let meta_settings = result.unwrap();
|
||||
|
||||
|
||||
@ -120,11 +120,10 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
||||
/// |> close()
|
||||
/// |> extrude(length = 6)
|
||||
///
|
||||
/// shell(
|
||||
/// firstSketch,
|
||||
/// shell({
|
||||
/// faces = ['end'],
|
||||
/// thickness = 0.25,
|
||||
/// )
|
||||
/// }, firstSketch)
|
||||
/// |> appearance({
|
||||
/// color = '#ff0000',
|
||||
/// metalness = 90,
|
||||
@ -148,11 +147,10 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
||||
/// roughness = 90
|
||||
/// }, %)
|
||||
///
|
||||
/// shell(
|
||||
/// firstSketch,
|
||||
/// shell({
|
||||
/// faces = ['end'],
|
||||
/// thickness = 0.25,
|
||||
/// )
|
||||
/// }, firstSketch)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
|
||||
@ -356,7 +356,7 @@ impl Args {
|
||||
Ok(numbers)
|
||||
}
|
||||
|
||||
pub(crate) fn get_pattern_transform_args(&self) -> Result<(u32, FnAsArg<'_>, SolidSet, Option<bool>), KclError> {
|
||||
pub(crate) fn get_pattern_transform_args(&self) -> Result<(u32, FnAsArg<'_>, SolidSet), KclError> {
|
||||
FromArgs::from_args(self, 0)
|
||||
}
|
||||
|
||||
@ -764,10 +764,6 @@ macro_rules! let_field_of {
|
||||
($obj:ident, $field:ident?) => {
|
||||
let $field = $obj.get(stringify!($field)).and_then(FromKclValue::from_kcl_val);
|
||||
};
|
||||
// Optional field but with a different string used as the key
|
||||
($obj:ident, $field:ident? $key:literal) => {
|
||||
let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val);
|
||||
};
|
||||
// Mandatory field, but with a different string used as the key.
|
||||
($obj:ident, $field:ident $key:literal) => {
|
||||
let $field = $obj.get($key).and_then(FromKclValue::from_kcl_val)?;
|
||||
@ -951,14 +947,12 @@ impl<'a> FromKclValue<'a> for super::patterns::CircularPattern3dData {
|
||||
let_field_of!(obj, rotate_duplicates "rotateDuplicates");
|
||||
let_field_of!(obj, axis);
|
||||
let_field_of!(obj, center);
|
||||
let_field_of!(obj, use_original? "useOriginal");
|
||||
Some(Self {
|
||||
instances,
|
||||
axis,
|
||||
center,
|
||||
arc_degrees,
|
||||
rotate_duplicates,
|
||||
use_original,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -970,13 +964,11 @@ impl<'a> FromKclValue<'a> for super::patterns::CircularPattern2dData {
|
||||
let_field_of!(obj, arc_degrees "arcDegrees");
|
||||
let_field_of!(obj, rotate_duplicates "rotateDuplicates");
|
||||
let_field_of!(obj, center);
|
||||
let_field_of!(obj, use_original? "useOriginal");
|
||||
Some(Self {
|
||||
instances,
|
||||
center,
|
||||
arc_degrees,
|
||||
rotate_duplicates,
|
||||
use_original,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1019,6 +1011,15 @@ impl<'a> FromKclValue<'a> for super::sketch::BezierData {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for super::shell::ShellData {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let obj = arg.as_object()?;
|
||||
let_field_of!(obj, thickness);
|
||||
let_field_of!(obj, faces);
|
||||
Some(Self { thickness, faces })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for super::chamfer::ChamferData {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let obj = arg.as_object()?;
|
||||
|
||||