Compare commits
128 Commits
franknoiro
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
df8977407c | |||
42016b7335 | |||
09f3e6e170 | |||
3747db01b8 | |||
bf1a42f6c0 | |||
b4d1a36bd9 | |||
b937934834 | |||
0208eecefa | |||
9999e4e62f | |||
b390e3e083 | |||
9a89926749 | |||
d7f834f23d | |||
500d92d1fc | |||
bd10c65bdb | |||
039092ec24 | |||
8a920b6cb7 | |||
c1db093e0f | |||
5f8ae22b04 | |||
dc7b901eb9 | |||
60dcf9d79e | |||
520f899ad4 | |||
41340c8bf0 | |||
a78ec6cd17 | |||
de526ae36e | |||
4a8e582865 | |||
1854064274 | |||
4a8897be4b | |||
83f458fc36 | |||
a686fe914b | |||
cba2349064 | |||
5ae92bcf5c | |||
ad8e306bdb | |||
508e1c919c | |||
58ec6100c4 | |||
11eceefedf | |||
11a678df5b | |||
22c0003eb1 | |||
8f0a40ba6e | |||
89b0ccb090 | |||
5d22308ad2 | |||
5580631c8f | |||
e1494c9f16 | |||
4a0d852a3c | |||
b213834316 | |||
1d8348c2cf | |||
2227287c9d | |||
680fc30682 | |||
40fb6a44d3 | |||
5713bfd9fa | |||
77902d550f | |||
db895d6801 | |||
3e1f8584ea | |||
2501a98cd9 | |||
e60b0e64ba | |||
3379cc489a | |||
a8b7328f65 | |||
ab6995bde3 | |||
6df5e70186 | |||
6b1cc36911 | |||
6a16e47491 | |||
f4f0533179 | |||
6360b8acac | |||
064a41d675 | |||
e075622a7f | |||
7a7929211a | |||
1f5f42963d | |||
235e6a1056 | |||
09cfbc1837 | |||
319029235c | |||
a7f4b0f037 | |||
d3afa38bd5 | |||
45416df215 | |||
ee54cdd27a | |||
84ae567f37 | |||
f94671f1bb | |||
bcf3790266 | |||
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 |
16260
docs/kcl/std.json
@ -19,8 +19,8 @@ A face.
|
|||||||
| `id` |`string`| The id of the face. | No |
|
| `id` |`string`| The id of the face. | No |
|
||||||
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
|
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
|
||||||
| `value` |`string`| The tag of the face. | No |
|
| `value` |`string`| The tag of the face. | No |
|
||||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s X axis be? | No |
|
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's X axis be? | No |
|
||||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s Y axis be? | No |
|
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No |
|
||||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A face. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A face. | No |
|
||||||
|
@ -98,6 +98,29 @@ a complete arc
|
|||||||
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
|
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
A base path.
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `CircleThreePoint`| | No |
|
||||||
|
| `p1` |`[number, number]`| Point 1 of the circle | No |
|
||||||
|
| `p2` |`[number, number]`| Point 2 of the circle | No |
|
||||||
|
| `p3` |`[number, number]`| Point 3 of the circle | No |
|
||||||
|
| `from` |`[number, number]`| The from point. | No |
|
||||||
|
| `to` |`[number, number]`| The to point. | No |
|
||||||
|
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
|
||||||
|
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
A path that is horizontal.
|
A path that is horizontal.
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ A plane.
|
|||||||
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
|
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
|
||||||
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A plane. | No |
|
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A plane. | No |
|
||||||
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
||||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No |
|
||||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y axis be? | No |
|
||||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A plane. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A plane. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
@ -29,8 +29,8 @@ A plane.
|
|||||||
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
|
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
|
||||||
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A sketch type. | No |
|
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A sketch type. | No |
|
||||||
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
||||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No |
|
||||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y axis be? | No |
|
||||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
@ -53,8 +53,8 @@ A face.
|
|||||||
| `id` |`string`| The id of the face. | No |
|
| `id` |`string`| The id of the face. | No |
|
||||||
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
|
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
|
||||||
| `value` |`string`| The tag of the face. | No |
|
| `value` |`string`| The tag of the face. | No |
|
||||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s X axis be? | No |
|
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's X axis be? | No |
|
||||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s Y axis be? | No |
|
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No |
|
||||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
|
||||||
|
@ -54,23 +54,26 @@ async function doBasicSketch(
|
|||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
if (openPanes.includes('code')) {
|
if (openPanes.includes('code')) {
|
||||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
await expect(u.codeLocator).toContainText(
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
if (openPanes.includes('code')) {
|
if (openPanes.includes('code')) {
|
||||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
await expect(u.codeLocator)
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||||
|> xLine(${commonPoints.num1}, %)`)
|
|> xLine(${commonPoints.num1}, %)`)
|
||||||
}
|
}
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
if (openPanes.includes('code')) {
|
if (openPanes.includes('code')) {
|
||||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
await expect(u.codeLocator)
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||||
|
commonPoints.startAt
|
||||||
|
}, sketch001)
|
||||||
|> xLine(${commonPoints.num1}, %)
|
|> xLine(${commonPoints.num1}, %)
|
||||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||||
} else {
|
} else {
|
||||||
@ -79,8 +82,10 @@ async function doBasicSketch(
|
|||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
if (openPanes.includes('code')) {
|
if (openPanes.includes('code')) {
|
||||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
await expect(u.codeLocator)
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||||
|
commonPoints.startAt
|
||||||
|
}, sketch001)
|
||||||
|> xLine(${commonPoints.num1}, %)
|
|> xLine(${commonPoints.num1}, %)
|
||||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||||
@ -137,8 +142,10 @@ async function doBasicSketch(
|
|||||||
|
|
||||||
// Open the code pane.
|
// Open the code pane.
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
await expect(u.codeLocator)
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||||
|
commonPoints.startAt
|
||||||
|
}, sketch001)
|
||||||
|> xLine(${commonPoints.num1}, %, $seg01)
|
|> xLine(${commonPoints.num1}, %, $seg01)
|
||||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||||
|> xLine(-segLen(seg01), %)`)
|
|> xLine(-segLen(seg01), %)`)
|
||||||
|
@ -43,8 +43,7 @@ test.describe(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = `sketch001 = startSketchOn('${plane}')
|
const code = `sketch001 = startSketchOn('${plane}')profile001 = startProfileAt([0.9, -1.22], sketch001)`
|
||||||
|> startProfileAt([0.9, -1.22], %)`
|
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Page, Locator } from '@playwright/test'
|
import type { Page, Locator } from '@playwright/test'
|
||||||
import { expect } from '@playwright/test'
|
import { expect } from '@playwright/test'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { isArray, uuidv4 } from 'lib/utils'
|
||||||
import {
|
import {
|
||||||
closeDebugPanel,
|
closeDebugPanel,
|
||||||
doAndWaitForImageDiff,
|
doAndWaitForImageDiff,
|
||||||
@ -9,13 +9,15 @@ import {
|
|||||||
sendCustomCmd,
|
sendCustomCmd,
|
||||||
} from '../test-utils'
|
} from '../test-utils'
|
||||||
|
|
||||||
type mouseParams = {
|
type MouseParams = {
|
||||||
pixelDiff?: number
|
pixelDiff?: number
|
||||||
|
shouldDbClick?: boolean
|
||||||
|
delay?: number
|
||||||
}
|
}
|
||||||
type mouseDragToParams = mouseParams & {
|
type MouseDragToParams = MouseParams & {
|
||||||
fromPoint: { x: number; y: number }
|
fromPoint: { x: number; y: number }
|
||||||
}
|
}
|
||||||
type mouseDragFromParams = mouseParams & {
|
type MouseDragFromParams = MouseParams & {
|
||||||
toPoint: { x: number; y: number }
|
toPoint: { x: number; y: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,12 +28,12 @@ type SceneSerialised = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
|
type ClickHandler = (clickParams?: MouseParams) => Promise<void | boolean>
|
||||||
type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean>
|
type MoveHandler = (moveParams?: MouseParams) => Promise<void | boolean>
|
||||||
type DblClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
|
type DblClickHandler = (clickParams?: MouseParams) => Promise<void | boolean>
|
||||||
type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean>
|
type DragToHandler = (dragParams: MouseDragToParams) => Promise<void | boolean>
|
||||||
type DragFromHandler = (
|
type DragFromHandler = (
|
||||||
dragParams: mouseDragFromParams
|
dragParams: MouseDragFromParams
|
||||||
) => Promise<void | boolean>
|
) => Promise<void | boolean>
|
||||||
|
|
||||||
export class SceneFixture {
|
export class SceneFixture {
|
||||||
@ -77,17 +79,26 @@ export class SceneFixture {
|
|||||||
{ steps }: { steps: number } = { steps: 20 }
|
{ steps }: { steps: number } = { steps: 20 }
|
||||||
): [ClickHandler, MoveHandler, DblClickHandler] =>
|
): [ClickHandler, MoveHandler, DblClickHandler] =>
|
||||||
[
|
[
|
||||||
(clickParams?: mouseParams) => {
|
(clickParams?: MouseParams) => {
|
||||||
if (clickParams?.pixelDiff) {
|
if (clickParams?.pixelDiff) {
|
||||||
return doAndWaitForImageDiff(
|
return doAndWaitForImageDiff(
|
||||||
this.page,
|
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
|
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) {
|
if (moveParams?.pixelDiff) {
|
||||||
return doAndWaitForImageDiff(
|
return doAndWaitForImageDiff(
|
||||||
this.page,
|
this.page,
|
||||||
@ -97,7 +108,7 @@ export class SceneFixture {
|
|||||||
}
|
}
|
||||||
return this.page.mouse.move(x, y, { steps })
|
return this.page.mouse.move(x, y, { steps })
|
||||||
},
|
},
|
||||||
(clickParams?: mouseParams) => {
|
(clickParams?: MouseParams) => {
|
||||||
if (clickParams?.pixelDiff) {
|
if (clickParams?.pixelDiff) {
|
||||||
return doAndWaitForImageDiff(
|
return doAndWaitForImageDiff(
|
||||||
this.page,
|
this.page,
|
||||||
@ -114,7 +125,7 @@ export class SceneFixture {
|
|||||||
{ steps }: { steps: number } = { steps: 20 }
|
{ steps }: { steps: number } = { steps: 20 }
|
||||||
): [DragToHandler, DragFromHandler] =>
|
): [DragToHandler, DragFromHandler] =>
|
||||||
[
|
[
|
||||||
(dragToParams: mouseDragToParams) => {
|
(dragToParams: MouseDragToParams) => {
|
||||||
if (dragToParams?.pixelDiff) {
|
if (dragToParams?.pixelDiff) {
|
||||||
return doAndWaitForImageDiff(
|
return doAndWaitForImageDiff(
|
||||||
this.page,
|
this.page,
|
||||||
@ -131,7 +142,7 @@ export class SceneFixture {
|
|||||||
targetPosition: { x, y },
|
targetPosition: { x, y },
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
(dragFromParams: mouseDragFromParams) => {
|
(dragFromParams: MouseDragFromParams) => {
|
||||||
if (dragFromParams?.pixelDiff) {
|
if (dragFromParams?.pixelDiff) {
|
||||||
return doAndWaitForImageDiff(
|
return doAndWaitForImageDiff(
|
||||||
this.page,
|
this.page,
|
||||||
@ -219,7 +230,7 @@ export class SceneFixture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expectPixelColor = async (
|
expectPixelColor = async (
|
||||||
colour: [number, number, number],
|
colour: [number, number, number] | [number, number, number][],
|
||||||
coords: { x: number; y: number },
|
coords: { x: number; y: number },
|
||||||
diff: 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 isArray(colour[0])
|
||||||
|
}
|
||||||
|
|
||||||
export async function expectPixelColor(
|
export async function expectPixelColor(
|
||||||
page: Page,
|
page: Page,
|
||||||
colour: [number, number, number],
|
colour: [number, number, number] | [number, number, number][],
|
||||||
coords: { x: number; y: number },
|
coords: { x: number; y: number },
|
||||||
diff: number
|
diff: number
|
||||||
) {
|
) {
|
||||||
let finalValue = colour
|
let finalValue = colour
|
||||||
await expect
|
await expect
|
||||||
.poll(async () => {
|
.poll(
|
||||||
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
|
async () => {
|
||||||
if (!pixel) return null
|
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
|
||||||
finalValue = pixel
|
if (!pixel) return null
|
||||||
return pixel.every(
|
finalValue = pixel
|
||||||
(channel, index) => Math.abs(channel - colour[index]) < diff
|
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()
|
.toBeTruthy()
|
||||||
.catch((cause) => {
|
.catch((cause) => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -23,7 +23,10 @@ export class ToolbarFixture {
|
|||||||
helixButton!: Locator
|
helixButton!: Locator
|
||||||
startSketchBtn!: Locator
|
startSketchBtn!: Locator
|
||||||
lineBtn!: Locator
|
lineBtn!: Locator
|
||||||
|
tangentialArcBtn!: Locator
|
||||||
|
circleBtn!: Locator
|
||||||
rectangleBtn!: Locator
|
rectangleBtn!: Locator
|
||||||
|
lengthConstraintBtn!: Locator
|
||||||
exitSketchBtn!: Locator
|
exitSketchBtn!: Locator
|
||||||
editSketchBtn!: Locator
|
editSketchBtn!: Locator
|
||||||
fileTreeBtn!: Locator
|
fileTreeBtn!: Locator
|
||||||
@ -53,7 +56,10 @@ export class ToolbarFixture {
|
|||||||
this.helixButton = page.getByTestId('helix')
|
this.helixButton = page.getByTestId('helix')
|
||||||
this.startSketchBtn = page.getByTestId('sketch')
|
this.startSketchBtn = page.getByTestId('sketch')
|
||||||
this.lineBtn = page.getByTestId('line')
|
this.lineBtn = page.getByTestId('line')
|
||||||
|
this.tangentialArcBtn = page.getByTestId('tangential-arc')
|
||||||
|
this.circleBtn = page.getByTestId('circle-center')
|
||||||
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
||||||
|
this.lengthConstraintBtn = page.getByTestId('constraint-length')
|
||||||
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
||||||
this.editSketchBtn = page.getByText('Edit Sketch')
|
this.editSketchBtn = page.getByText('Edit Sketch')
|
||||||
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
||||||
@ -119,6 +125,25 @@ export class ToolbarFixture {
|
|||||||
await expect(this.exeIndicator).toBeVisible({ timeout: 15_000 })
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
selectCircleThreePoint = async () => {
|
||||||
|
await this.page
|
||||||
|
.getByRole('button', { name: 'caret down Center circle:' })
|
||||||
|
.click()
|
||||||
|
await expect(
|
||||||
|
this.page.getByTestId('dropdown-circle-three-points')
|
||||||
|
).toBeVisible()
|
||||||
|
await this.page.getByTestId('dropdown-circle-three-points').click()
|
||||||
|
}
|
||||||
|
|
||||||
async closePane(paneId: SidebarType) {
|
async closePane(paneId: SidebarType) {
|
||||||
return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
||||||
|
@ -219,18 +219,13 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
afterChamferSelectSnippet:
|
afterChamferSelectSnippet:
|
||||||
'sketch002 = startSketchOn(extrude001, seg03)',
|
'sketch002 = startSketchOn(extrude001, seg03)',
|
||||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
afterRectangle1stClickSnippet:
|
||||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
'startProfileAt([205.96, 254.59], sketch002)',
|
||||||
|> angledLine([
|
afterRectangle2ndClickSnippet: `angledLine([0,11.39],%,$rectangleSegmentA002)
|
||||||
segAng(rectangleSegmentA002) - 90,
|
|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)
|
||||||
105.26
|
|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)
|
||||||
], %, $rectangleSegmentB001)
|
|>line(endAbsolute=[profileStartX(%),profileStartY(%)])
|
||||||
|> angledLine([
|
|>close()`,
|
||||||
segAng(rectangleSegmentA002),
|
|
||||||
-segLen(rectangleSegmentA002)
|
|
||||||
], %, $rectangleSegmentC001)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await sketchOnAChamfer({
|
await sketchOnAChamfer({
|
||||||
@ -251,19 +246,15 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
afterChamferSelectSnippet:
|
afterChamferSelectSnippet:
|
||||||
'sketch003 = startSketchOn(extrude001, seg04)',
|
'sketch003 = startSketchOn(extrude001, seg04)',
|
||||||
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
|
afterRectangle1stClickSnippet:
|
||||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
'startProfileAt([-209.64, 255.28], sketch003)',
|
||||||
|> angledLine([
|
afterRectangle2ndClickSnippet: `angledLine([0,11.56],%,$rectangleSegmentA003)
|
||||||
segAng(rectangleSegmentA003) - 90,
|
|>angledLine([segAng(rectangleSegmentA003)-90,106.84],%)
|
||||||
106.84
|
|>angledLine([segAng(rectangleSegmentA003),-segLen(rectangleSegmentA003)],%)
|
||||||
], %, $rectangleSegmentB002)
|
|>line(endAbsolute=[profileStartX(%),profileStartY(%)])
|
||||||
|> angledLine([
|
|>close()`,
|
||||||
segAng(rectangleSegmentA003),
|
|
||||||
-segLen(rectangleSegmentA003)
|
|
||||||
], %, $rectangleSegmentC002)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await sketchOnAChamfer({
|
await sketchOnAChamfer({
|
||||||
clickCoords: { x: 677, y: 87 },
|
clickCoords: { x: 677, y: 87 },
|
||||||
cameraPos: { x: -6200, y: 1500, z: 6200 },
|
cameraPos: { x: -6200, y: 1500, z: 6200 },
|
||||||
@ -276,19 +267,14 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
|||||||
]
|
]
|
||||||
}, %)`,
|
}, %)`,
|
||||||
afterChamferSelectSnippet:
|
afterChamferSelectSnippet:
|
||||||
'sketch003 = startSketchOn(extrude001, seg04)',
|
'sketch004 = startSketchOn(extrude001, seg05)',
|
||||||
afterRectangle1stClickSnippet: 'startProfileAt([75.8, 317.2], %)',
|
afterRectangle1stClickSnippet:
|
||||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|
'startProfileAt([82.57, 322.96], sketch004)',
|
||||||
|> angledLine([
|
afterRectangle2ndClickSnippet: `angledLine([0,11.16],%,$rectangleSegmentA004)
|
||||||
segAng(rectangleSegmentA003) - 90,
|
|>angledLine([segAng(rectangleSegmentA004)-90,103.07],%)
|
||||||
106.84
|
|>angledLine([segAng(rectangleSegmentA004),-segLen(rectangleSegmentA004)],%)
|
||||||
], %, $rectangleSegmentB002)
|
|>line(endAbsolute=[profileStartX(%),profileStartY(%)])
|
||||||
|> angledLine([
|
|>close()`,
|
||||||
segAng(rectangleSegmentA003),
|
|
||||||
-segLen(rectangleSegmentA003)
|
|
||||||
], %, $rectangleSegmentC002)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()`,
|
|
||||||
})
|
})
|
||||||
/// last one
|
/// last one
|
||||||
await sketchOnAChamfer({
|
await sketchOnAChamfer({
|
||||||
@ -301,104 +287,98 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
|||||||
}, %)`,
|
}, %)`,
|
||||||
afterChamferSelectSnippet:
|
afterChamferSelectSnippet:
|
||||||
'sketch005 = startSketchOn(extrude001, seg06)',
|
'sketch005 = startSketchOn(extrude001, seg06)',
|
||||||
afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)',
|
afterRectangle1stClickSnippet:
|
||||||
afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005)
|
'startProfileAt([-23.43, 19.69], sketch005)',
|
||||||
|
afterRectangle2ndClickSnippet: `angledLine([0,9.1],%,$rectangleSegmentA005)
|
||||||
|> angledLine([
|
|>angledLine([segAng(rectangleSegmentA005)-90,84.07],%)
|
||||||
segAng(rectangleSegmentA005) - 90,
|
|>angledLine([segAng(rectangleSegmentA005),-segLen(rectangleSegmentA005)],%)
|
||||||
84.07
|
|>line(endAbsolute=[profileStartX(%),profileStartY(%)])
|
||||||
], %, $rectangleSegmentB004)
|
|>close()`,
|
||||||
|> angledLine([
|
|
||||||
segAng(rectangleSegmentA005),
|
|
||||||
-segLen(rectangleSegmentA005)
|
|
||||||
], %, $rectangleSegmentC004)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()`,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('verify at the end of the test that final code is what is expected', async () => {
|
await test.step('verify at the end of the test that final code is what is expected', async () => {
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`sketch001 = startSketchOn('XZ')
|
`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)
|
||||||
|
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()
|
||||||
|
|
||||||
|> 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()
|
|
||||||
`,
|
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -443,18 +423,13 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
|||||||
beforeChamferSnippetEnd: '}, extrude001)',
|
beforeChamferSnippetEnd: '}, extrude001)',
|
||||||
afterChamferSelectSnippet:
|
afterChamferSelectSnippet:
|
||||||
'sketch002 = startSketchOn(extrude001, seg03)',
|
'sketch002 = startSketchOn(extrude001, seg03)',
|
||||||
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
|
afterRectangle1stClickSnippet:
|
||||||
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|
'startProfileAt([205.96, 254.59], sketch002)',
|
||||||
|> angledLine([
|
afterRectangle2ndClickSnippet: `angledLine([0,11.39],%,$rectangleSegmentA002)
|
||||||
segAng(rectangleSegmentA002) - 90,
|
|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)
|
||||||
105.26
|
|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)
|
||||||
], %, $rectangleSegmentB001)
|
|>line(endAbsolute=[profileStartX(%),profileStartY(%)])
|
||||||
|> angledLine([
|
|>close()`,
|
||||||
segAng(rectangleSegmentA002),
|
|
||||||
-segLen(rectangleSegmentA002)
|
|
||||||
], %, $rectangleSegmentC001)
|
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|
||||||
|> close()`,
|
|
||||||
})
|
})
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
@ -484,17 +459,17 @@ chamf = chamfer({
|
|||||||
]
|
]
|
||||||
}, %)
|
}, %)
|
||||||
sketch002 = startSketchOn(extrude001, seg03)
|
sketch002 = startSketchOn(extrude001, seg03)
|
||||||
|> startProfileAt([205.96, 254.59], %)
|
profile001 = startProfileAt([205.96, 254.59], sketch002)
|
||||||
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA002) - 90,
|
segAng(rectangleSegmentA002) - 90,
|
||||||
105.26
|
105.26
|
||||||
], %, $rectangleSegmentB001)
|
], %)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA002),
|
segAng(rectangleSegmentA002),
|
||||||
-segLen(rectangleSegmentA002)
|
-segLen(rectangleSegmentA002)
|
||||||
], %, $rectangleSegmentC001)
|
], %)
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute=[profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
`,
|
`,
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
@ -561,10 +536,10 @@ sketch002 = startSketchOn(extrude001, seg03)
|
|||||||
|
|
||||||
const expectedCodeSnippets = {
|
const expectedCodeSnippets = {
|
||||||
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
|
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
|
||||||
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`,
|
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], sketch001)`,
|
||||||
segmentOnXAxis: `xLine(${xAxisSloppy.kcl[0]}, %)`,
|
segmentOnXAxis: `xLine(${xAxisSloppy.kcl[0]}, %)`,
|
||||||
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`,
|
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], sketch001)`,
|
||||||
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
|
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], sketch001)`,
|
||||||
}
|
}
|
||||||
|
|
||||||
await test.step(`Start a sketch on the XZ plane`, async () => {
|
await test.step(`Start a sketch on the XZ plane`, async () => {
|
||||||
@ -605,6 +580,7 @@ sketch002 = startSketchOn(extrude001, seg03)
|
|||||||
expectedCodeSnippets.afterSegmentDraggedOnYAxis
|
expectedCodeSnippets.afterSegmentDraggedOnYAxis
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
await editor.page.waitForTimeout(1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
test(`Verify user can double-click to edit a sketch`, async ({
|
test(`Verify user can double-click to edit a sketch`, async ({
|
||||||
@ -1397,12 +1373,12 @@ sketch002 = startSketchOn('XZ')
|
|||||||
await clickOnSketch2()
|
await clickOnSketch2()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await toolbar.openPane('code')
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
await scene.expectPixelColor([135, 64, 73], testPoint, 15)
|
await scene.expectPixelColor([135, 64, 73], testPoint, 15)
|
||||||
await toolbar.openPane('code')
|
|
||||||
await editor.expectEditor.toContain(sweepDeclaration)
|
await editor.expectEditor.toContain(sweepDeclaration)
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
|
@ -444,8 +444,7 @@ test(
|
|||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
code += `
|
code += `profile001 = startProfileAt([7.19, -9.7], sketch001)`
|
||||||
|> startProfileAt([7.19, -9.7], %)`
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
@ -456,7 +455,9 @@ test(
|
|||||||
mask: [page.getByTestId('model-state-indicator')],
|
mask: [page.getByTestId('model-state-indicator')],
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
const lineEndClick = () =>
|
||||||
|
page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
await lineEndClick()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
code += `
|
code += `
|
||||||
@ -467,6 +468,15 @@ test(
|
|||||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
|
// click on the end of the profile to continue it
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
await lineEndClick()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// 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.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
|
||||||
|
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
@ -589,8 +599,7 @@ test(
|
|||||||
mask: [page.getByTestId('model-state-indicator')],
|
mask: [page.getByTestId('model-state-indicator')],
|
||||||
})
|
})
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')profile001 = circle({ center = [14.44, -2.44], radius = 1 }, sketch001)`
|
||||||
|> circle({ center = [14.44, -2.44], radius = 1 }, %)`
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -634,8 +643,7 @@ test.describe(
|
|||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
code += `
|
code += `profile001 = startProfileAt([7.19, -9.7], sketch001)`
|
||||||
|> startProfileAt([7.19, -9.7], %)`
|
|
||||||
await expect(u.codeLocator).toHaveText(code)
|
await expect(u.codeLocator).toHaveText(code)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
@ -653,6 +661,10 @@ test.describe(
|
|||||||
.click()
|
.click()
|
||||||
await page.waitForTimeout(100)
|
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)
|
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||||
|
|
||||||
code += `
|
code += `
|
||||||
@ -739,8 +751,7 @@ test.describe(
|
|||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
code += `
|
code += `profile001 = startProfileAt([182.59, -246.32], sketch001)`
|
||||||
|> startProfileAt([182.59, -246.32], %)`
|
|
||||||
await expect(u.codeLocator).toHaveText(code)
|
await expect(u.codeLocator).toHaveText(code)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
@ -758,6 +769,10 @@ test.describe(
|
|||||||
.click()
|
.click()
|
||||||
await page.waitForTimeout(100)
|
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)
|
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
||||||
|
|
||||||
code += `
|
code += `
|
||||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 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: 46 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
@ -1,6 +1,7 @@
|
|||||||
import { test, expect } from './zoo-test'
|
import { test, expect } from './zoo-test'
|
||||||
|
|
||||||
import { commonPoints, getUtils } from './test-utils'
|
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.describe('Test network and connection issues', () => {
|
||||||
test(
|
test(
|
||||||
@ -111,18 +112,17 @@ test.describe('Test network and connection issues', () => {
|
|||||||
|
|
||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> xLine(${commonPoints.num1}, %)`)
|
||||||
|> xLine(${commonPoints.num1}, %)`)
|
|
||||||
|
|
||||||
// Expect the network to be up
|
// Expect the network to be up
|
||||||
await expect(networkToggle).toContainText('Connected')
|
await expect(networkToggle).toContainText('Connected')
|
||||||
@ -169,7 +169,9 @@ test.describe('Test network and connection issues', () => {
|
|||||||
await page.mouse.click(100, 100)
|
await page.mouse.click(100, 100)
|
||||||
|
|
||||||
// select a line
|
// select a line
|
||||||
await page.getByText(`startProfileAt(${commonPoints.startAt}, %)`).click()
|
await page
|
||||||
|
.getByText(`startProfileAt(${commonPoints.startAt}, sketch001)`)
|
||||||
|
.click()
|
||||||
|
|
||||||
// enter sketch again
|
// enter sketch again
|
||||||
await u.doAndWaitForCmd(
|
await u.doAndWaitForCmd(
|
||||||
@ -183,11 +185,36 @@ test.describe('Test network and connection issues', () => {
|
|||||||
|
|
||||||
await page.waitForTimeout(150)
|
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
|
// Ensure we can continue sketching
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
await expect.poll(u.normalisedEditorCode)
|
await expect.poll(u.normalisedEditorCode)
|
||||||
.toBe(`sketch001 = startSketchOn('XZ')
|
.toBe(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([12.34, -12.34], %)
|
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
||||||
|> xLine(12.34, %)
|
|> xLine(12.34, %)
|
||||||
|> line(end = [-12.34, 12.34])
|
|> line(end = [-12.34, 12.34])
|
||||||
|
|
||||||
@ -197,7 +224,7 @@ test.describe('Test network and connection issues', () => {
|
|||||||
|
|
||||||
await expect.poll(u.normalisedEditorCode)
|
await expect.poll(u.normalisedEditorCode)
|
||||||
.toBe(`sketch001 = startSketchOn('XZ')
|
.toBe(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([12.34, -12.34], %)
|
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
||||||
|> xLine(12.34, %)
|
|> xLine(12.34, %)
|
||||||
|> line(end = [-12.34, 12.34])
|
|> line(end = [-12.34, 12.34])
|
||||||
|> xLine(-12.34, %)
|
|> xLine(-12.34, %)
|
||||||
|
@ -19,7 +19,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
|> line(end = [20, 0])
|
|> line(end = [20, 0])
|
||||||
|> line(end = [0, 20])
|
|> line(end = [0, 20])
|
||||||
|> xLine(-20, %)
|
|> xLine(-20, %)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -673,7 +673,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
|||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
for (const { testName, addVariable, value, constraint } of cases) {
|
for (const { testName, addVariable, value, constraint } of cases) {
|
||||||
test(`${testName}`, async ({ context, homePage, page }) => {
|
test(`${testName}`, async ({ context, homePage, page, editor }) => {
|
||||||
// constants and locators
|
// constants and locators
|
||||||
const cmdBarKclInput = page
|
const cmdBarKclInput = page
|
||||||
.getByTestId('cmd-bar-arg-value')
|
.getByTestId('cmd-bar-arg-value')
|
||||||
@ -706,7 +706,9 @@ part002 = startSketchOn('XZ')
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
|
await editor.scrollToText('line(end = [74.36, 130.4])', true)
|
||||||
await page.getByText('line(end = [74.36, 130.4])').click()
|
await page.getByText('line(end = [74.36, 130.4])').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
@ -66,33 +66,34 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
const startXPx = 600
|
const startXPx = 600
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
)
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> xLine(${commonPoints.num1}, %)`)
|
||||||
|> xLine(${commonPoints.num1}, %)`)
|
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
commonPoints.startAt
|
||||||
|> xLine(${commonPoints.num1}, %)
|
}, sketch001)
|
||||||
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
|> xLine(${commonPoints.num1}, %)
|
||||||
|
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
commonPoints.startAt
|
||||||
|> xLine(${commonPoints.num1}, %)
|
}, sketch001)
|
||||||
|> yLine(${commonPoints.num1 + 0.01}, %)
|
|> xLine(${commonPoints.num1}, %)
|
||||||
|> xLine(${commonPoints.num2 * -1}, %)`)
|
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||||
|
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||||
@ -259,66 +260,88 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-79.26, 95.04], %)
|
|> startProfileAt([-79.26, 95.04], %)
|
||||||
|> line(end = [112.54, 127.64], tag = $seg02)
|
|> line(end = [112.54, 127.64], tag = $seg02)
|
||||||
|> line(end = [170.36, -121.61], tag = $seg01)
|
|> line(end = [170.36, -121.61], tag = $seg01)
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
extrude001 = extrude(sketch001, length = 50)
|
extrude001 = extrude(sketch001, length = 50)
|
||||||
sketch005 = startSketchOn(extrude001, 'END')
|
sketch005 = startSketchOn(extrude001, 'END')
|
||||||
|> startProfileAt([23.24, 136.52], %)
|
|> startProfileAt([23.24, 136.52], %)
|
||||||
|> line(end = [-8.44, 36.61])
|
|> line(end = [-8.44, 36.61])
|
||||||
|> line(end = [49.4, 2.05])
|
|> line(end = [49.4, 2.05])
|
||||||
|> line(end = [29.69, -46.95])
|
|> line(end = [29.69, -46.95])
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
sketch003 = startSketchOn(extrude001, seg01)
|
sketch003 = startSketchOn(extrude001, seg01)
|
||||||
|> startProfileAt([21.23, 17.81], %)
|
|> startProfileAt([21.23, 17.81], %)
|
||||||
|> line(end = [51.97, 21.32])
|
|> line(end = [51.97, 21.32])
|
||||||
|> line(end = [4.07, -22.75])
|
|> line(end = [4.07, -22.75])
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
sketch002 = startSketchOn(extrude001, seg02)
|
sketch002 = startSketchOn(extrude001, seg02)
|
||||||
|> startProfileAt([-100.54, 16.99], %)
|
|> startProfileAt([-100.54, 16.99], %)
|
||||||
|> line(end = [0, 20.03])
|
|> line(end = [0, 20.03])
|
||||||
|> line(end = [62.61, 0], tag = $seg03)
|
|> line(end = [62.61, 0], tag = $seg03)
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
extrude002 = extrude(sketch002, length = 50)
|
extrude002 = extrude(sketch002, length = 50)
|
||||||
sketch004 = startSketchOn(extrude002, seg03)
|
sketch004 = startSketchOn(extrude002, seg03)
|
||||||
|> startProfileAt([57.07, 134.77], %)
|
|> startProfileAt([57.07, 134.77], %)
|
||||||
|> line(end = [-4.72, 22.84])
|
|> line(end = [-4.72, 22.84])
|
||||||
|> line(end = [28.8, 6.71])
|
|> line(end = [28.8, 6.71])
|
||||||
|> line(end = [9.19, -25.33])
|
|> line(end = [9.19, -25.33])
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
extrude003 = extrude(sketch004, length = 20)
|
extrude003 = extrude(sketch004, length = 20)
|
||||||
pipeLength = 40
|
pipeLength = 40
|
||||||
pipeSmallDia = 10
|
pipeSmallDia = 10
|
||||||
pipeLargeDia = 20
|
pipeLargeDia = 20
|
||||||
thickness = 0.5
|
thickness = 0.5
|
||||||
part009 = startSketchOn('XY')
|
part009 = startSketchOn('XY')
|
||||||
|> startProfileAt([pipeLargeDia - (thickness / 2), 38], %)
|
|> startProfileAt([pipeLargeDia - (thickness / 2), 38], %)
|
||||||
|> line(end = [thickness, 0])
|
|> line(end = [thickness, 0])
|
||||||
|> line(end = [0, -1])
|
|> line(end = [0, -1])
|
||||||
|> angledLineToX({
|
|> angledLineToX({
|
||||||
angle = 60,
|
angle = 60,
|
||||||
to = pipeSmallDia + thickness
|
to = pipeSmallDia + thickness
|
||||||
}, %)
|
}, %)
|
||||||
|> line(end = [0, -pipeLength])
|
|> line(end = [0, -pipeLength])
|
||||||
|> angledLineToX({
|
|> angledLineToX({
|
||||||
angle = -60,
|
angle = -60,
|
||||||
to = pipeLargeDia + thickness
|
to = pipeLargeDia + thickness
|
||||||
}, %)
|
}, %)
|
||||||
|> line(end = [0, -1])
|
|> line(end = [0, -1])
|
||||||
|> line(end = [-thickness, 0])
|
|> line(end = [-thickness, 0])
|
||||||
|> line(end = [0, 1])
|
|> line(end = [0, 1])
|
||||||
|> angledLineToX({ angle = 120, to = pipeSmallDia }, %)
|
|> angledLineToX({ angle = 120, to = pipeSmallDia }, %)
|
||||||
|> line(end = [0, pipeLength])
|
|> line(end = [0, pipeLength])
|
||||||
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|
||||||
|> close()
|
|> close()
|
||||||
rev = revolve({ axis: 'y' }, part009)
|
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)
|
}, KCL_DEFAULT_LENGTH)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
@ -347,9 +370,10 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
})
|
})
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
const revolve = { x: 646, y: 248 }
|
const revolve = { x: 635, y: 253 }
|
||||||
const parentExtrude = { x: 915, y: 133 }
|
const parentExtrude = { x: 915, y: 133 }
|
||||||
const solid2d = { x: 770, y: 167 }
|
const solid2d = { x: 770, y: 167 }
|
||||||
|
const individualProfile = { x: 694, y: 432 }
|
||||||
|
|
||||||
// DELETE REVOLVE
|
// DELETE REVOLVE
|
||||||
await page.mouse.click(revolve.x, revolve.y)
|
await page.mouse.click(revolve.x, revolve.y)
|
||||||
@ -415,6 +439,20 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
await expect(u.codeLocator).not.toContainText(`sketch005 = startSketchOn({`)
|
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(end = [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 ({
|
test("Deleting solid that the AST mod can't handle results in a toast message", async ({
|
||||||
page,
|
page,
|
||||||
@ -1216,12 +1254,15 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
|
|
||||||
await page.waitForTimeout(600)
|
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
|
// 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)
|
await page.waitForTimeout(600)
|
||||||
|
|
||||||
// Code before exiting the tool
|
// 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
|
// deselect the line tool by clicking it
|
||||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||||
@ -1233,14 +1274,23 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.mouse.click(750, 200)
|
await page.mouse.click(750, 200)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// expect no change
|
await expect
|
||||||
await expect(page.locator('.cm-content')).toHaveText(previousCodeContent)
|
.poll(async () => {
|
||||||
|
let str = await page.locator('.cm-content').innerText()
|
||||||
|
str = str.replace(/\s+/g, '')
|
||||||
|
return str
|
||||||
|
})
|
||||||
|
.toBe(previousCodeContent)
|
||||||
|
|
||||||
// select line tool again
|
// select line tool again
|
||||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
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
|
// line tool should work as expected again
|
||||||
await page.mouse.click(700, 200)
|
await page.mouse.click(700, 200)
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(
|
await expect(page.locator('.cm-content')).not.toHaveText(
|
||||||
|
@ -209,8 +209,13 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
|||||||
// Draw a line
|
// Draw a line
|
||||||
await page.mouse.move(700, 200, { steps: 5 })
|
await page.mouse.move(700, 200, { steps: 5 })
|
||||||
await page.mouse.click(700, 200)
|
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
|
// Unequip line tool
|
||||||
await page.keyboard.press('Escape')
|
await page.keyboard.press('Escape')
|
||||||
// Make sure we didn't pop out of sketch mode.
|
// Make sure we didn't pop out of sketch mode.
|
||||||
@ -219,11 +224,23 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
|||||||
// Equip arc tool
|
// Equip arc tool
|
||||||
await page.keyboard.press('a')
|
await page.keyboard.press('a')
|
||||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'true')
|
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.move(1000, 100, { steps: 5 })
|
||||||
await page.mouse.click(1000, 100)
|
await page.mouse.click(1000, 100)
|
||||||
await page.keyboard.press('Escape')
|
await page.keyboard.press('Escape')
|
||||||
await page.keyboard.press('l')
|
await expect(arcButton).toHaveAttribute('aria-pressed', 'false')
|
||||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
await expect
|
||||||
|
.poll(async () => {
|
||||||
|
await page.keyboard.press('l')
|
||||||
|
return lineButton.getAttribute('aria-pressed')
|
||||||
|
})
|
||||||
|
.toBe('true')
|
||||||
|
|
||||||
// Do not close the sketch.
|
// Do not close the sketch.
|
||||||
// On close it will exit sketch mode.
|
// On close it will exit sketch mode.
|
||||||
@ -519,9 +536,9 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
|||||||
|
|
||||||
await expect.poll(u.normalisedEditorCode).toContain(
|
await expect.poll(u.normalisedEditorCode).toContain(
|
||||||
u.normalisedCode(`sketch002 = startSketchOn(extrude001, seg01)
|
u.normalisedCode(`sketch002 = startSketchOn(extrude001, seg01)
|
||||||
|> startProfileAt([-12.94, 6.6], %)
|
profile001 = startProfileAt([-12.34, 12.34], sketch002)
|
||||||
|> line(end = [2.45, -0.2])
|
|> line(end = [12.34, -12.34])
|
||||||
|> line(end = [-2.6, -1.25])
|
|> line(end = [-12.34, -12.34])
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
`)
|
`)
|
||||||
@ -537,9 +554,8 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
|||||||
await page.getByText('startProfileAt([-12').click()
|
await page.getByText('startProfileAt([-12').click()
|
||||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
await page.waitForTimeout(400)
|
await page.waitForTimeout(500)
|
||||||
await page.waitForTimeout(150)
|
await page.setViewportSize({ width: 1200, height: 1200 })
|
||||||
await page.setBodyDimensions({ width: 1200, height: 1200 })
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.updateCamPosition([452, -152, 1166])
|
await u.updateCamPosition([452, -152, 1166])
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
@ -5,7 +5,6 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
|||||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||||
import { ActionButton } from 'components/ActionButton'
|
import { ActionButton } from 'components/ActionButton'
|
||||||
import { isSingleCursorInPipe } from 'lang/queryAst'
|
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
|
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
@ -21,6 +20,7 @@ import {
|
|||||||
} from 'lib/toolbar'
|
} from 'lib/toolbar'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||||
|
import { isCursorInFunctionDefinition } from 'lang/queryAst'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
import { isArray } from 'lib/utils'
|
import { isArray } from 'lib/utils'
|
||||||
|
|
||||||
@ -37,7 +37,12 @@ export function Toolbar({
|
|||||||
const buttonBorderClassName = '!border-transparent'
|
const buttonBorderClassName = '!border-transparent'
|
||||||
|
|
||||||
const sketchPathId = useMemo(() => {
|
const sketchPathId = useMemo(() => {
|
||||||
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast))
|
if (
|
||||||
|
isCursorInFunctionDefinition(
|
||||||
|
kclManager.ast,
|
||||||
|
context.selectionRanges.graphSelections[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
return isCursorInSketchCommandRange(
|
return isCursorInSketchCommandRange(
|
||||||
engineCommandManager.artifactGraph,
|
engineCommandManager.artifactGraph,
|
||||||
|
@ -124,14 +124,7 @@ export const ClientSideScene = ({
|
|||||||
'mouseup',
|
'mouseup',
|
||||||
toSync(sceneInfra.onMouseUp, reportRejection)
|
toSync(sceneInfra.onMouseUp, reportRejection)
|
||||||
)
|
)
|
||||||
sceneEntitiesManager
|
sceneEntitiesManager.tearDownSketch({ removeAxis: true })
|
||||||
.tearDownSketch()
|
|
||||||
.then(() => {
|
|
||||||
// no op
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -152,7 +145,8 @@ export const ClientSideScene = ({
|
|||||||
state.matches({ Sketch: 'Line tool' }) ||
|
state.matches({ Sketch: 'Line tool' }) ||
|
||||||
state.matches({ Sketch: 'Tangential arc to' }) ||
|
state.matches({ Sketch: 'Tangential arc to' }) ||
|
||||||
state.matches({ Sketch: 'Rectangle tool' }) ||
|
state.matches({ Sketch: 'Rectangle tool' }) ||
|
||||||
state.matches({ Sketch: 'Circle tool' })
|
state.matches({ Sketch: 'Circle tool' }) ||
|
||||||
|
state.matches({ Sketch: 'Circle three point tool' })
|
||||||
) {
|
) {
|
||||||
cursor = 'crosshair'
|
cursor = 'crosshair'
|
||||||
} else {
|
} else {
|
||||||
@ -190,12 +184,15 @@ const Overlays = () => {
|
|||||||
style={{ zIndex: '99999999' }}
|
style={{ zIndex: '99999999' }}
|
||||||
>
|
>
|
||||||
{Object.entries(context.segmentOverlays)
|
{Object.entries(context.segmentOverlays)
|
||||||
.filter((a) => a[1].visible)
|
.flatMap((a) =>
|
||||||
.map(([pathToNodeString, overlay], index) => {
|
a[1].map((b) => ({ pathToNodeString: a[0], overlay: b }))
|
||||||
|
)
|
||||||
|
.filter((a) => a.overlay.visible)
|
||||||
|
.map(({ pathToNodeString, overlay }, index) => {
|
||||||
return (
|
return (
|
||||||
<Overlay
|
<Overlay
|
||||||
overlay={overlay}
|
overlay={overlay}
|
||||||
key={pathToNodeString}
|
key={pathToNodeString + String(index)}
|
||||||
pathToNodeString={pathToNodeString}
|
pathToNodeString={pathToNodeString}
|
||||||
overlayIndex={index}
|
overlayIndex={index}
|
||||||
/>
|
/>
|
||||||
@ -236,11 +233,17 @@ const Overlay = ({
|
|||||||
|
|
||||||
const constraints =
|
const constraints =
|
||||||
callExpression.type === 'CallExpression'
|
callExpression.type === 'CallExpression'
|
||||||
? getConstraintInfo(callExpression, codeManager.code, overlay.pathToNode)
|
? getConstraintInfo(
|
||||||
|
callExpression,
|
||||||
|
codeManager.code,
|
||||||
|
overlay.pathToNode,
|
||||||
|
overlay.filterValue
|
||||||
|
)
|
||||||
: getConstraintInfoKw(
|
: getConstraintInfoKw(
|
||||||
callExpression,
|
callExpression,
|
||||||
codeManager.code,
|
codeManager.code,
|
||||||
overlay.pathToNode
|
overlay.pathToNode,
|
||||||
|
overlay.filterValue
|
||||||
)
|
)
|
||||||
|
|
||||||
const offset = 20 // px
|
const offset = 20 // px
|
||||||
@ -260,7 +263,6 @@ const Overlay = ({
|
|||||||
state.matches({ Sketch: 'Tangential arc to' }) ||
|
state.matches({ Sketch: 'Tangential arc to' }) ||
|
||||||
state.matches({ Sketch: 'Rectangle tool' })
|
state.matches({ Sketch: 'Rectangle tool' })
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`absolute w-0 h-0`}>
|
<div className={`absolute w-0 h-0`}>
|
||||||
<div
|
<div
|
||||||
@ -318,17 +320,18 @@ const Overlay = ({
|
|||||||
this will likely change soon when we implement multi-profile so we'll leave it for now
|
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
|
issue: https://github.com/KittyCAD/modeling-app/issues/3910
|
||||||
*/}
|
*/}
|
||||||
{callExpression?.callee?.name !== 'circle' && (
|
{callExpression?.callee?.name !== 'circle' &&
|
||||||
<SegmentMenu
|
callExpression?.callee?.name !== 'circleThreePoint' && (
|
||||||
verticalPosition={
|
<SegmentMenu
|
||||||
overlay.windowCoords[1] > window.innerHeight / 2
|
verticalPosition={
|
||||||
? 'top'
|
overlay.windowCoords[1] > window.innerHeight / 2
|
||||||
: 'bottom'
|
? 'top'
|
||||||
}
|
: 'bottom'
|
||||||
pathToNode={overlay.pathToNode}
|
}
|
||||||
stdLibFnName={constraints[0]?.stdLibFnName}
|
pathToNode={overlay.pathToNode}
|
||||||
/>
|
stdLibFnName={constraints[0]?.stdLibFnName}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -449,6 +452,8 @@ export async function deleteSegment({
|
|||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
pathToNode,
|
pathToNode,
|
||||||
|
sketchDetails.sketchNodePaths,
|
||||||
|
sketchDetails.planeNodePath,
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
|
@ -182,13 +182,15 @@ export class SceneInfra {
|
|||||||
callbacks: (() => SegmentOverlayPayload | null)[] = []
|
callbacks: (() => SegmentOverlayPayload | null)[] = []
|
||||||
_overlayCallbacks(callbacks: (() => SegmentOverlayPayload | null)[]) {
|
_overlayCallbacks(callbacks: (() => SegmentOverlayPayload | null)[]) {
|
||||||
const segmentOverlayPayload: SegmentOverlayPayload = {
|
const segmentOverlayPayload: SegmentOverlayPayload = {
|
||||||
type: 'set-many',
|
type: 'add-many',
|
||||||
overlays: {},
|
overlays: {},
|
||||||
}
|
}
|
||||||
callbacks.forEach((cb) => {
|
callbacks.forEach((cb) => {
|
||||||
const overlay = cb()
|
const overlay = cb()
|
||||||
if (overlay?.type === 'set-one') {
|
if (overlay?.type === 'set-one') {
|
||||||
segmentOverlayPayload.overlays[overlay.pathToNodeString] = overlay.seg
|
segmentOverlayPayload.overlays[overlay.pathToNodeString] = overlay.seg
|
||||||
|
} else if (overlay?.type === 'add-many') {
|
||||||
|
Object.assign(segmentOverlayPayload.overlays, overlay.overlays)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.modelingSend({
|
this.modelingSend({
|
||||||
@ -213,25 +215,27 @@ export class SceneInfra {
|
|||||||
|
|
||||||
overlayThrottleMap: { [pathToNodeString: string]: number } = {}
|
overlayThrottleMap: { [pathToNodeString: string]: number } = {}
|
||||||
updateOverlayDetails({
|
updateOverlayDetails({
|
||||||
arrowGroup,
|
handle,
|
||||||
group,
|
group,
|
||||||
isHandlesVisible,
|
isHandlesVisible,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
angle,
|
angle,
|
||||||
|
hasThreeDotMenu,
|
||||||
}: {
|
}: {
|
||||||
arrowGroup: Group
|
handle: Group
|
||||||
group: Group
|
group: Group
|
||||||
isHandlesVisible: boolean
|
isHandlesVisible: boolean
|
||||||
from: Coords2d
|
from: Coords2d
|
||||||
to: Coords2d
|
to: Coords2d
|
||||||
|
hasThreeDotMenu: boolean
|
||||||
angle?: number
|
angle?: number
|
||||||
}): SegmentOverlayPayload | null {
|
}): 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)
|
const vector = new Vector3(0, 0, 0)
|
||||||
|
|
||||||
// Get the position of the object3D in world space
|
// Get the position of the object3D in world space
|
||||||
arrowGroup.getWorldPosition(vector)
|
handle.getWorldPosition(vector)
|
||||||
|
|
||||||
// Project that position to screen space
|
// Project that position to screen space
|
||||||
vector.project(this.camControls.camera)
|
vector.project(this.camControls.camera)
|
||||||
@ -244,13 +248,16 @@ export class SceneInfra {
|
|||||||
return {
|
return {
|
||||||
type: 'set-one',
|
type: 'set-one',
|
||||||
pathToNodeString,
|
pathToNodeString,
|
||||||
seg: {
|
seg: [
|
||||||
windowCoords: [x, y],
|
{
|
||||||
angle: _angle,
|
windowCoords: [x, y],
|
||||||
group,
|
angle: _angle,
|
||||||
pathToNode: group.userData.pathToNode,
|
group,
|
||||||
visible: isHandlesVisible,
|
pathToNode: group.userData.pathToNode,
|
||||||
},
|
visible: isHandlesVisible,
|
||||||
|
hasThreeDotMenu,
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
@ -31,6 +31,12 @@ import {
|
|||||||
CIRCLE_SEGMENT,
|
CIRCLE_SEGMENT,
|
||||||
CIRCLE_SEGMENT_BODY,
|
CIRCLE_SEGMENT_BODY,
|
||||||
CIRCLE_SEGMENT_DASH,
|
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_HANDLE,
|
||||||
EXTRA_SEGMENT_OFFSET_PX,
|
EXTRA_SEGMENT_OFFSET_PX,
|
||||||
HIDE_HOVER_SEGMENT_LENGTH,
|
HIDE_HOVER_SEGMENT_LENGTH,
|
||||||
@ -56,11 +62,16 @@ import {
|
|||||||
} from './sceneInfra'
|
} from './sceneInfra'
|
||||||
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
|
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
|
||||||
import { normaliseAngle, roundOff } from 'lib/utils'
|
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 { SegmentInputs } from 'lang/std/stdTypes'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { editorManager, sceneInfra } from 'lib/singletons'
|
import { sceneInfra } from 'lib/singletons'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
|
import { calculate_circle_from_3_points } from 'wasm-lib/pkg/wasm_lib'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
interface CreateSegmentArgs {
|
interface CreateSegmentArgs {
|
||||||
@ -307,11 +318,12 @@ class StraightSegment implements SegmentUtils {
|
|||||||
}
|
}
|
||||||
return () =>
|
return () =>
|
||||||
sceneInfra.updateOverlayDetails({
|
sceneInfra.updateOverlayDetails({
|
||||||
arrowGroup,
|
handle: arrowGroup,
|
||||||
group,
|
group,
|
||||||
isHandlesVisible,
|
isHandlesVisible,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
|
hasThreeDotMenu: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -483,12 +495,13 @@ class TangentialArcToSegment implements SegmentUtils {
|
|||||||
)
|
)
|
||||||
return () =>
|
return () =>
|
||||||
sceneInfra.updateOverlayDetails({
|
sceneInfra.updateOverlayDetails({
|
||||||
arrowGroup,
|
handle: arrowGroup,
|
||||||
group,
|
group,
|
||||||
isHandlesVisible,
|
isHandlesVisible,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
angle,
|
angle,
|
||||||
|
hasThreeDotMenu: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -684,35 +697,255 @@ class CircleSegment implements SegmentUtils {
|
|||||||
}
|
}
|
||||||
return () =>
|
return () =>
|
||||||
sceneInfra.updateOverlayDetails({
|
sceneInfra.updateOverlayDetails({
|
||||||
arrowGroup,
|
handle: arrowGroup,
|
||||||
group,
|
group,
|
||||||
isHandlesVisible,
|
isHandlesVisible,
|
||||||
from: from,
|
from: from,
|
||||||
to: [center[0], center[1]],
|
to: [center[0], center[1]],
|
||||||
angle: Math.PI / 4,
|
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({
|
export function createProfileStartHandle({
|
||||||
from,
|
from,
|
||||||
isDraft = false,
|
isDraft = false,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
theme,
|
theme,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
size = 12,
|
||||||
...rest
|
...rest
|
||||||
}: {
|
}: {
|
||||||
from: Coords2d
|
from: Coords2d
|
||||||
scale?: number
|
scale?: number
|
||||||
theme: Themes
|
theme: Themes
|
||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
|
size?: number
|
||||||
} & (
|
} & (
|
||||||
| { isDraft: true }
|
| { isDraft: true }
|
||||||
| { isDraft: false; id: string; pathToNode: PathToNode }
|
| { isDraft: false; id: string; pathToNode: PathToNode }
|
||||||
)) {
|
)) {
|
||||||
const group = new Group()
|
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 baseColor = getThemeColorForThreeJs(theme)
|
||||||
const color = isSelected ? 0x0000ff : baseColor
|
const color = isSelected ? 0x0000ff : baseColor
|
||||||
const body = new MeshBasicMaterial({ color })
|
const body = new MeshBasicMaterial({ color })
|
||||||
@ -774,6 +1007,29 @@ function createCircleCenterHandle(
|
|||||||
circleCenterGroup.scale.set(scale, scale, scale)
|
circleCenterGroup.scale.set(scale, scale, scale)
|
||||||
return circleCenterGroup
|
return circleCenterGroup
|
||||||
}
|
}
|
||||||
|
function createCircleThreePointHandle(
|
||||||
|
scale = 1,
|
||||||
|
theme: Themes,
|
||||||
|
name: `circle-three-point-handle${'1' | '2' | '3'}`,
|
||||||
|
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(
|
function createExtraSegmentHandle(
|
||||||
scale: number,
|
scale: number,
|
||||||
@ -1100,4 +1356,5 @@ export const segmentUtils = {
|
|||||||
straight: new StraightSegment(),
|
straight: new StraightSegment(),
|
||||||
tangentialArcTo: new TangentialArcToSegment(),
|
tangentialArcTo: new TangentialArcToSegment(),
|
||||||
circle: new CircleSegment(),
|
circle: new CircleSegment(),
|
||||||
|
circleThreePoint: new CircleThreePointSegment(),
|
||||||
} as const
|
} as const
|
||||||
|
@ -25,7 +25,7 @@ import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
|||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import {
|
import {
|
||||||
isCursorInSketchCommandRange,
|
isCursorInSketchCommandRange,
|
||||||
updatePathToNodeFromMap,
|
updateSketchDetailsNodePaths,
|
||||||
} from 'lang/util'
|
} from 'lang/util'
|
||||||
import {
|
import {
|
||||||
kclManager,
|
kclManager,
|
||||||
@ -65,17 +65,30 @@ import {
|
|||||||
replaceValueAtNodePath,
|
replaceValueAtNodePath,
|
||||||
sketchOnExtrudedFace,
|
sketchOnExtrudedFace,
|
||||||
sketchOnOffsetPlane,
|
sketchOnOffsetPlane,
|
||||||
|
splitPipedProfile,
|
||||||
startSketchOnDefault,
|
startSketchOnDefault,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
|
import {
|
||||||
import { artifactIsPlaneWithPaths, isSingleCursorInPipe } from 'lang/queryAst'
|
PathToNode,
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
Program,
|
||||||
|
VariableDeclaration,
|
||||||
|
parse,
|
||||||
|
recast,
|
||||||
|
resultIsOk,
|
||||||
|
} from 'lang/wasm'
|
||||||
|
import {
|
||||||
|
artifactIsPlaneWithPaths,
|
||||||
|
doesSketchPipeNeedSplitting,
|
||||||
|
getNodeFromPath,
|
||||||
|
isCursorInFunctionDefinition,
|
||||||
|
traverse,
|
||||||
|
} from 'lang/queryAst'
|
||||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
import { Models } from '@kittycad/lib/dist/types/src'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||||
import { err, reportRejection, trap } from 'lib/trap'
|
import { err, reportRejection, trap, reject } from 'lib/trap'
|
||||||
import {
|
import {
|
||||||
ExportIntent,
|
ExportIntent,
|
||||||
EngineConnectionStateType,
|
EngineConnectionStateType,
|
||||||
@ -86,10 +99,16 @@ import { useFileContext } from 'hooks/useFileContext'
|
|||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { IndexLoaderData } from 'lib/types'
|
import { IndexLoaderData } from 'lib/types'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
import {
|
||||||
|
getFaceCodeRef,
|
||||||
|
getPathsFromArtifact,
|
||||||
|
getPlaneFromArtifact,
|
||||||
|
} from 'lang/std/artifactGraph'
|
||||||
import { promptToEditFlow } from 'lib/promptToEdit'
|
import { promptToEditFlow } from 'lib/promptToEdit'
|
||||||
import { kclEditorActor } from 'machines/kclEditorMachine'
|
import { kclEditorActor } from 'machines/kclEditorMachine'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
import { useToken } from 'machines/appMachine'
|
import { useToken } from 'machines/appMachine'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -254,7 +273,11 @@ export const ModelingMachineProvider = ({
|
|||||||
'Set Segment Overlays': assign({
|
'Set Segment Overlays': assign({
|
||||||
segmentOverlays: ({ context: { segmentOverlays }, event }) => {
|
segmentOverlays: ({ context: { segmentOverlays }, event }) => {
|
||||||
if (event.type !== 'Set Segment Overlays') return {}
|
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')
|
if (event.data.type === 'set-one')
|
||||||
return {
|
return {
|
||||||
...segmentOverlays,
|
...segmentOverlays,
|
||||||
@ -287,7 +310,7 @@ export const ModelingMachineProvider = ({
|
|||||||
return {
|
return {
|
||||||
sketchDetails: {
|
sketchDetails: {
|
||||||
...sketchDetails,
|
...sketchDetails,
|
||||||
sketchPathToNode: event.data,
|
sketchEntryNodePath: event.data,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -483,9 +506,17 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges: setSelections.selection,
|
selectionRanges: setSelections.selection,
|
||||||
sketchDetails: {
|
sketchDetails: {
|
||||||
...sketchDetails,
|
...sketchDetails,
|
||||||
sketchPathToNode:
|
sketchEntryNodePath:
|
||||||
setSelections.updatedPathToNode ||
|
setSelections.updatedSketchEntryNodePath ||
|
||||||
sketchDetails?.sketchPathToNode ||
|
sketchDetails?.sketchEntryNodePath ||
|
||||||
|
[],
|
||||||
|
sketchNodePaths:
|
||||||
|
setSelections.updatedSketchNodePaths ||
|
||||||
|
sketchDetails?.sketchNodePaths ||
|
||||||
|
[],
|
||||||
|
planeNodePath:
|
||||||
|
setSelections.updatedPlaneNodePath ||
|
||||||
|
sketchDetails?.planeNodePath ||
|
||||||
[],
|
[],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -638,7 +669,12 @@ export const ModelingMachineProvider = ({
|
|||||||
if (artifactIsPlaneWithPaths(selectionRanges)) {
|
if (artifactIsPlaneWithPaths(selectionRanges)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
if (
|
||||||
|
isCursorInFunctionDefinition(
|
||||||
|
kclManager.ast,
|
||||||
|
selectionRanges.graphSelections[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
return !!isCursorInSketchCommandRange(
|
return !!isCursorInSketchCommandRange(
|
||||||
engineCommandManager.artifactGraph,
|
engineCommandManager.artifactGraph,
|
||||||
@ -666,13 +702,33 @@ export const ModelingMachineProvider = ({
|
|||||||
async ({ input: { sketchDetails } }) => {
|
async ({ input: { sketchDetails } }) => {
|
||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
if (kclManager.ast.body.length) {
|
if (kclManager.ast.body.length) {
|
||||||
// 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 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
|
// remove body item at varDecIndex
|
||||||
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
||||||
await kclManager.executeAstMock(newAst)
|
await kclManager.executeAstMock(newAst)
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(newAst)
|
||||||
}
|
}
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
@ -682,7 +738,7 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
'animate-to-face': fromPromise(async ({ input }) => {
|
'animate-to-face': fromPromise(async ({ input }) => {
|
||||||
if (!input) return undefined
|
if (!input) return null
|
||||||
if (input.type === 'extrudeFace' || input.type === 'offsetPlane') {
|
if (input.type === 'extrudeFace' || input.type === 'offsetPlane') {
|
||||||
const sketched =
|
const sketched =
|
||||||
input.type === 'extrudeFace'
|
input.type === 'extrudeFace'
|
||||||
@ -709,7 +765,9 @@ export const ModelingMachineProvider = ({
|
|||||||
await letEngineAnimateAndSyncCamAfter(engineCommandManager, id)
|
await letEngineAnimateAndSyncCamAfter(engineCommandManager, id)
|
||||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||||
return {
|
return {
|
||||||
sketchPathToNode: pathToNewSketchNode,
|
sketchEntryNodePath: [],
|
||||||
|
planeNodePath: pathToNewSketchNode,
|
||||||
|
sketchNodePaths: [],
|
||||||
zAxis: input.zAxis,
|
zAxis: input.zAxis,
|
||||||
yAxis: input.yAxis,
|
yAxis: input.yAxis,
|
||||||
origin: input.position,
|
origin: input.position,
|
||||||
@ -730,7 +788,9 @@ export const ModelingMachineProvider = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sketchPathToNode: pathToNode,
|
sketchEntryNodePath: [],
|
||||||
|
planeNodePath: pathToNode,
|
||||||
|
sketchNodePaths: [],
|
||||||
zAxis: input.zAxis,
|
zAxis: input.zAxis,
|
||||||
yAxis: input.yAxis,
|
yAxis: input.yAxis,
|
||||||
origin: [0, 0, 0],
|
origin: [0, 0, 0],
|
||||||
@ -739,21 +799,49 @@ export const ModelingMachineProvider = ({
|
|||||||
}),
|
}),
|
||||||
'animate-to-sketch': fromPromise(
|
'animate-to-sketch': fromPromise(
|
||||||
async ({ input: { selectionRanges } }) => {
|
async ({ input: { selectionRanges } }) => {
|
||||||
const sourceRange =
|
const plane = getPlaneFromArtifact(
|
||||||
selectionRanges.graphSelections[0]?.codeRef?.range
|
selectionRanges.graphSelections[0].artifact,
|
||||||
const sketchPathToNode = getNodePathFromSourceRange(
|
engineCommandManager.artifactGraph
|
||||||
kclManager.ast,
|
|
||||||
sourceRange
|
|
||||||
)
|
)
|
||||||
const info = await getSketchOrientationDetails(
|
if (err(plane)) return Promise.reject(plane)
|
||||||
sketchPathToNode || []
|
|
||||||
|
const sketch = Object.values(kclManager.execState.variables).find(
|
||||||
|
(variable) =>
|
||||||
|
variable?.type === 'Sketch' &&
|
||||||
|
variable.value.artifactId === plane.pathIds[0]
|
||||||
)
|
)
|
||||||
|
if (!sketch || sketch.type !== 'Sketch')
|
||||||
|
return Promise.reject(new Error('No sketch'))
|
||||||
|
const info = await getSketchOrientationDetails(sketch.value)
|
||||||
|
|
||||||
await letEngineAnimateAndSyncCamAfter(
|
await letEngineAnimateAndSyncCamAfter(
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
info?.sketchDetails?.faceId || ''
|
info?.sketchDetails?.faceId || ''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const sketchArtifact = engineCommandManager.artifactGraph.get(
|
||||||
|
plane.pathIds[0]
|
||||||
|
)
|
||||||
|
if (sketchArtifact?.type !== 'path')
|
||||||
|
return Promise.reject(new Error('No sketch artifact'))
|
||||||
|
const sketchPaths = getPathsFromArtifact({
|
||||||
|
artifact: engineCommandManager.artifactGraph.get(plane.id),
|
||||||
|
sketchPathToNode: sketchArtifact?.codeRef?.pathToNode,
|
||||||
|
artifactGraph: engineCommandManager.artifactGraph,
|
||||||
|
ast: kclManager.ast,
|
||||||
|
})
|
||||||
|
if (err(sketchPaths)) return Promise.reject(sketchPaths)
|
||||||
|
let codeRef = getFaceCodeRef(plane)
|
||||||
|
if (!codeRef) return Promise.reject(new Error('No plane codeRef'))
|
||||||
|
// codeRef.pathToNode is not always populated correctly
|
||||||
|
const planeNodePath = getNodePathFromSourceRange(
|
||||||
|
kclManager.ast,
|
||||||
|
codeRef.range
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
sketchPathToNode: sketchPathToNode || [],
|
sketchEntryNodePath: sketchArtifact.codeRef.pathToNode || [],
|
||||||
|
sketchNodePaths: sketchPaths,
|
||||||
|
planeNodePath,
|
||||||
zAxis: info.sketchDetails.zAxis || null,
|
zAxis: info.sketchDetails.zAxis || null,
|
||||||
yAxis: info.sketchDetails.yAxis || null,
|
yAxis: info.sketchDetails.yAxis || null,
|
||||||
origin: info.sketchDetails.origin.map(
|
origin: info.sketchDetails.origin.map(
|
||||||
@ -766,7 +854,7 @@ export const ModelingMachineProvider = ({
|
|||||||
|
|
||||||
'Get horizontal info': fromPromise(
|
'Get horizontal info': fromPromise(
|
||||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||||
await applyConstraintHorzVertDistance({
|
await applyConstraintHorzVertDistance({
|
||||||
constraint: 'setHorzDistance',
|
constraint: 'setHorzDistance',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -778,13 +866,23 @@ export const ModelingMachineProvider = ({
|
|||||||
|
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
|
||||||
sketchDetails.sketchPathToNode,
|
const {
|
||||||
pathToNodeMap
|
updatedSketchEntryNodePath,
|
||||||
)
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
|
} = updateSketchDetailsNodePaths({
|
||||||
|
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||||
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||||
|
planeNodePath: sketchDetails.planeNodePath,
|
||||||
|
exprInsertIndex,
|
||||||
|
})
|
||||||
|
|
||||||
const updatedAst =
|
const updatedAst =
|
||||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
_modifiedAst,
|
_modifiedAst,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
@ -805,13 +903,15 @@ export const ModelingMachineProvider = ({
|
|||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection,
|
selection,
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
'Get vertical info': fromPromise(
|
'Get vertical info': fromPromise(
|
||||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||||
await applyConstraintHorzVertDistance({
|
await applyConstraintHorzVertDistance({
|
||||||
constraint: 'setVertDistance',
|
constraint: 'setVertDistance',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -822,13 +922,23 @@ export const ModelingMachineProvider = ({
|
|||||||
const _modifiedAst = pResult.program
|
const _modifiedAst = pResult.program
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
|
||||||
sketchDetails.sketchPathToNode,
|
const {
|
||||||
pathToNodeMap
|
updatedSketchEntryNodePath,
|
||||||
)
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
|
} = updateSketchDetailsNodePaths({
|
||||||
|
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||||
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||||
|
planeNodePath: sketchDetails.planeNodePath,
|
||||||
|
exprInsertIndex,
|
||||||
|
})
|
||||||
|
|
||||||
const updatedAst =
|
const updatedAst =
|
||||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
_modifiedAst,
|
_modifiedAst,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
@ -849,7 +959,9 @@ export const ModelingMachineProvider = ({
|
|||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection,
|
selection,
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@ -859,14 +971,15 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
if (err(info)) return Promise.reject(info)
|
if (err(info)) return Promise.reject(info)
|
||||||
const { modifiedAst, pathToNodeMap } = await (info.enabled
|
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||||
? applyConstraintAngleBetween({
|
await (info.enabled
|
||||||
selectionRanges,
|
? applyConstraintAngleBetween({
|
||||||
})
|
selectionRanges,
|
||||||
: applyConstraintAngleLength({
|
})
|
||||||
selectionRanges,
|
: applyConstraintAngleLength({
|
||||||
angleOrLength: 'setAngle',
|
selectionRanges,
|
||||||
}))
|
angleOrLength: 'setAngle',
|
||||||
|
}))
|
||||||
const pResult = parse(recast(modifiedAst))
|
const pResult = parse(recast(modifiedAst))
|
||||||
if (trap(pResult) || !resultIsOk(pResult))
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
return Promise.reject(new Error('Unexpected compilation error'))
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
@ -875,13 +988,23 @@ export const ModelingMachineProvider = ({
|
|||||||
|
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
|
||||||
sketchDetails.sketchPathToNode,
|
const {
|
||||||
pathToNodeMap
|
updatedSketchEntryNodePath,
|
||||||
)
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
|
} = updateSketchDetailsNodePaths({
|
||||||
|
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||||
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||||
|
planeNodePath: sketchDetails.planeNodePath,
|
||||||
|
exprInsertIndex,
|
||||||
|
})
|
||||||
|
|
||||||
const updatedAst =
|
const updatedAst =
|
||||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
_modifiedAst,
|
_modifiedAst,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
@ -902,7 +1025,9 @@ export const ModelingMachineProvider = ({
|
|||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection,
|
selection,
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@ -917,20 +1042,30 @@ export const ModelingMachineProvider = ({
|
|||||||
length: lengthValue,
|
length: lengthValue,
|
||||||
})
|
})
|
||||||
if (err(constraintResult)) return Promise.reject(constraintResult)
|
if (err(constraintResult)) return Promise.reject(constraintResult)
|
||||||
const { modifiedAst, pathToNodeMap } = constraintResult
|
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||||
|
constraintResult
|
||||||
const pResult = parse(recast(modifiedAst))
|
const pResult = parse(recast(modifiedAst))
|
||||||
if (trap(pResult) || !resultIsOk(pResult))
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
return Promise.reject(new Error('Unexpected compilation error'))
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
const _modifiedAst = pResult.program
|
const _modifiedAst = pResult.program
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
|
||||||
sketchDetails.sketchPathToNode,
|
const {
|
||||||
pathToNodeMap
|
updatedSketchEntryNodePath,
|
||||||
)
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
|
} = updateSketchDetailsNodePaths({
|
||||||
|
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||||
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||||
|
planeNodePath: sketchDetails.planeNodePath,
|
||||||
|
exprInsertIndex,
|
||||||
|
})
|
||||||
const updatedAst =
|
const updatedAst =
|
||||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
_modifiedAst,
|
_modifiedAst,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
@ -951,13 +1086,15 @@ export const ModelingMachineProvider = ({
|
|||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection,
|
selection,
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
'Get perpendicular distance info': fromPromise(
|
'Get perpendicular distance info': fromPromise(
|
||||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||||
await applyConstraintIntersect({
|
await applyConstraintIntersect({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
@ -967,13 +1104,22 @@ export const ModelingMachineProvider = ({
|
|||||||
const _modifiedAst = pResult.program
|
const _modifiedAst = pResult.program
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
|
||||||
sketchDetails.sketchPathToNode,
|
const {
|
||||||
pathToNodeMap
|
updatedSketchEntryNodePath,
|
||||||
)
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
|
} = updateSketchDetailsNodePaths({
|
||||||
|
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||||
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||||
|
planeNodePath: sketchDetails.planeNodePath,
|
||||||
|
exprInsertIndex,
|
||||||
|
})
|
||||||
const updatedAst =
|
const updatedAst =
|
||||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
_modifiedAst,
|
_modifiedAst,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
@ -994,13 +1140,15 @@ export const ModelingMachineProvider = ({
|
|||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection,
|
selection,
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
'Get ABS X info': fromPromise(
|
'Get ABS X info': fromPromise(
|
||||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||||
await applyConstraintAbsDistance({
|
await applyConstraintAbsDistance({
|
||||||
constraint: 'xAbs',
|
constraint: 'xAbs',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -1011,13 +1159,22 @@ export const ModelingMachineProvider = ({
|
|||||||
const _modifiedAst = pResult.program
|
const _modifiedAst = pResult.program
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
|
||||||
sketchDetails.sketchPathToNode,
|
const {
|
||||||
pathToNodeMap
|
updatedSketchEntryNodePath,
|
||||||
)
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
|
} = updateSketchDetailsNodePaths({
|
||||||
|
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||||
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||||
|
planeNodePath: sketchDetails.planeNodePath,
|
||||||
|
exprInsertIndex,
|
||||||
|
})
|
||||||
const updatedAst =
|
const updatedAst =
|
||||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
_modifiedAst,
|
_modifiedAst,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
@ -1038,13 +1195,15 @@ export const ModelingMachineProvider = ({
|
|||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection,
|
selection,
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
'Get ABS Y info': fromPromise(
|
'Get ABS Y info': fromPromise(
|
||||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||||
await applyConstraintAbsDistance({
|
await applyConstraintAbsDistance({
|
||||||
constraint: 'yAbs',
|
constraint: 'yAbs',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -1055,13 +1214,22 @@ export const ModelingMachineProvider = ({
|
|||||||
const _modifiedAst = pResult.program
|
const _modifiedAst = pResult.program
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
|
||||||
sketchDetails.sketchPathToNode,
|
const {
|
||||||
pathToNodeMap
|
updatedSketchEntryNodePath,
|
||||||
)
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
|
} = updateSketchDetailsNodePaths({
|
||||||
|
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||||
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||||
|
planeNodePath: sketchDetails.planeNodePath,
|
||||||
|
exprInsertIndex,
|
||||||
|
})
|
||||||
const updatedAst =
|
const updatedAst =
|
||||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
_modifiedAst,
|
_modifiedAst,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
@ -1082,7 +1250,9 @@ export const ModelingMachineProvider = ({
|
|||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection,
|
selection,
|
||||||
updatedPathToNode,
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@ -1102,9 +1272,11 @@ export const ModelingMachineProvider = ({
|
|||||||
let result: {
|
let result: {
|
||||||
modifiedAst: Node<Program>
|
modifiedAst: Node<Program>
|
||||||
pathToReplaced: PathToNode | null
|
pathToReplaced: PathToNode | null
|
||||||
|
exprInsertIndex: number
|
||||||
} = {
|
} = {
|
||||||
modifiedAst: parsed,
|
modifiedAst: parsed,
|
||||||
pathToReplaced: null,
|
pathToReplaced: null,
|
||||||
|
exprInsertIndex: -1,
|
||||||
}
|
}
|
||||||
// If the user provided a constant name,
|
// If the user provided a constant name,
|
||||||
// we need to insert the named constant
|
// we need to insert the named constant
|
||||||
@ -1134,6 +1306,7 @@ export const ModelingMachineProvider = ({
|
|||||||
result = {
|
result = {
|
||||||
modifiedAst: parseResultAfterInsertion.program,
|
modifiedAst: parseResultAfterInsertion.program,
|
||||||
pathToReplaced: astAfterReplacement.pathToReplaced,
|
pathToReplaced: astAfterReplacement.pathToReplaced,
|
||||||
|
exprInsertIndex: astAfterReplacement.exprInsertIndex,
|
||||||
}
|
}
|
||||||
} else if ('valueText' in data.namedValue) {
|
} else if ('valueText' in data.namedValue) {
|
||||||
// If they didn't provide a constant name,
|
// If they didn't provide a constant name,
|
||||||
@ -1164,10 +1337,22 @@ export const ModelingMachineProvider = ({
|
|||||||
parsed = parsed as Node<Program>
|
parsed = parsed as Node<Program>
|
||||||
if (!result.pathToReplaced)
|
if (!result.pathToReplaced)
|
||||||
return Promise.reject(new Error('No path to replaced node'))
|
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 =
|
const updatedAst =
|
||||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
result.pathToReplaced || [],
|
updatedSketchEntryNodePath,
|
||||||
|
updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath,
|
||||||
parsed,
|
parsed,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
@ -1188,7 +1373,191 @@ export const ModelingMachineProvider = ({
|
|||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection,
|
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,
|
||||||
|
expressionIndexToDelete: -1,
|
||||||
|
} 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)
|
||||||
|
let moddedAst: Program = structuredClone(kclManager.ast)
|
||||||
|
let pathToProfile = sketchDetails.sketchEntryNodePath
|
||||||
|
let updatedSketchNodePaths = sketchDetails.sketchNodePaths
|
||||||
|
if (doesNeedSplitting) {
|
||||||
|
const splitResult = splitPipedProfile(
|
||||||
|
moddedAst,
|
||||||
|
sketchDetails.sketchEntryNodePath
|
||||||
|
)
|
||||||
|
if (err(splitResult)) return reject(splitResult)
|
||||||
|
moddedAst = splitResult.modifiedAst
|
||||||
|
pathToProfile = splitResult.pathToProfile
|
||||||
|
updatedSketchNodePaths = [pathToProfile]
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexToDelete = sketchDetails?.expressionIndexToDelete || -1
|
||||||
|
if (indexToDelete >= 0) {
|
||||||
|
// this is the expression that was added when as sketch tool was used but not completed
|
||||||
|
// i.e first click for the center of the circle, but not the second click for the radius
|
||||||
|
// we added a circle to editor, but they bailed out early so we should remove it
|
||||||
|
moddedAst.body.splice(indexToDelete, 1)
|
||||||
|
// make sure the deleted expression is removed from the sketchNodePaths
|
||||||
|
updatedSketchNodePaths = updatedSketchNodePaths.filter(
|
||||||
|
(path) => path[1][0] !== indexToDelete
|
||||||
|
)
|
||||||
|
// if the deleted expression was the entryNodePath, we should just make it the first sketchNodePath
|
||||||
|
// as a safe default
|
||||||
|
pathToProfile =
|
||||||
|
pathToProfile[1][0] !== indexToDelete
|
||||||
|
? pathToProfile
|
||||||
|
: updatedSketchNodePaths[0]
|
||||||
|
}
|
||||||
|
await kclManager.executeAstMock(moddedAst)
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(moddedAst)
|
||||||
|
return {
|
||||||
|
updatedEntryNodePath: pathToProfile,
|
||||||
|
updatedSketchNodePaths: updatedSketchNodePaths,
|
||||||
|
updatedPlaneNodePath: sketchDetails.planeNodePath,
|
||||||
|
expressionIndexToDelete: -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -13,12 +13,7 @@ import {
|
|||||||
getOperationLabel,
|
getOperationLabel,
|
||||||
stdLibMap,
|
stdLibMap,
|
||||||
} from 'lib/operations'
|
} from 'lib/operations'
|
||||||
import {
|
import { editorManager, engineCommandManager, kclManager } from 'lib/singletons'
|
||||||
codeManager,
|
|
||||||
editorManager,
|
|
||||||
engineCommandManager,
|
|
||||||
kclManager,
|
|
||||||
} from 'lib/singletons'
|
|
||||||
import { ComponentProps, useEffect, useMemo, useRef, useState } from 'react'
|
import { ComponentProps, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
||||||
import { Actor, Prop } from 'xstate'
|
import { Actor, Prop } from 'xstate'
|
||||||
@ -67,7 +62,7 @@ export const FeatureTreePane = () => {
|
|||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
if (!artifact || !('codeRef' in artifact)) {
|
if (!artifact) {
|
||||||
modelingSend({
|
modelingSend({
|
||||||
type: 'Set selection',
|
type: 'Set selection',
|
||||||
data: {
|
data: {
|
||||||
|
@ -2,7 +2,12 @@ import { SVGProps } from 'react'
|
|||||||
|
|
||||||
export const Spinner = (props: SVGProps<SVGSVGElement>) => {
|
export const Spinner = (props: SVGProps<SVGSVGElement>) => {
|
||||||
return (
|
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
|
<circle
|
||||||
cx="5"
|
cx="5"
|
||||||
cy="5"
|
cy="5"
|
||||||
|
@ -136,6 +136,7 @@ export async function applyConstraintIntersect({
|
|||||||
}): Promise<{
|
}): Promise<{
|
||||||
modifiedAst: Node<Program>
|
modifiedAst: Node<Program>
|
||||||
pathToNodeMap: PathToNodeMap
|
pathToNodeMap: PathToNodeMap
|
||||||
|
exprInsertIndex: number
|
||||||
}> {
|
}> {
|
||||||
const info = intersectInfo({
|
const info = intersectInfo({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -174,6 +175,7 @@ export async function applyConstraintIntersect({
|
|||||||
return {
|
return {
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
|
exprInsertIndex: -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// transform again but forcing certain values
|
// transform again but forcing certain values
|
||||||
@ -192,6 +194,7 @@ export async function applyConstraintIntersect({
|
|||||||
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
||||||
transform2
|
transform2
|
||||||
|
|
||||||
|
let exprInsertIndex = -1
|
||||||
if (variableName) {
|
if (variableName) {
|
||||||
const newBody = [..._modifiedAst.body]
|
const newBody = [..._modifiedAst.body]
|
||||||
newBody.splice(
|
newBody.splice(
|
||||||
@ -204,9 +207,11 @@ export async function applyConstraintIntersect({
|
|||||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||||
})
|
})
|
||||||
|
exprInsertIndex = newVariableInsertIndex
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _modifiedAst,
|
modifiedAst: _modifiedAst,
|
||||||
pathToNodeMap: _pathToNodeMap,
|
pathToNodeMap: _pathToNodeMap,
|
||||||
|
exprInsertIndex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ export function removeConstrainingValuesInfo({
|
|||||||
| Error {
|
| Error {
|
||||||
const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
|
const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
|
||||||
const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
|
const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
|
||||||
if (err(tmp)) return tmp
|
if (tmp instanceof Error) return tmp
|
||||||
return tmp.node
|
return tmp.node
|
||||||
})
|
})
|
||||||
const _err1 = _nodes.find(err)
|
const _err1 = _nodes.find(err)
|
||||||
|
@ -92,6 +92,7 @@ export async function applyConstraintAbsDistance({
|
|||||||
}): Promise<{
|
}): Promise<{
|
||||||
modifiedAst: Program
|
modifiedAst: Program
|
||||||
pathToNodeMap: PathToNodeMap
|
pathToNodeMap: PathToNodeMap
|
||||||
|
exprInsertIndex: number
|
||||||
}> {
|
}> {
|
||||||
const info = absDistanceInfo({
|
const info = absDistanceInfo({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -131,6 +132,7 @@ export async function applyConstraintAbsDistance({
|
|||||||
if (err(transform2)) return Promise.reject(transform2)
|
if (err(transform2)) return Promise.reject(transform2)
|
||||||
const { modifiedAst: _modifiedAst, pathToNodeMap } = transform2
|
const { modifiedAst: _modifiedAst, pathToNodeMap } = transform2
|
||||||
|
|
||||||
|
let exprInsertIndex = -1
|
||||||
if (variableName) {
|
if (variableName) {
|
||||||
const newBody = [..._modifiedAst.body]
|
const newBody = [..._modifiedAst.body]
|
||||||
newBody.splice(
|
newBody.splice(
|
||||||
@ -143,8 +145,9 @@ export async function applyConstraintAbsDistance({
|
|||||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||||
})
|
})
|
||||||
|
exprInsertIndex = newVariableInsertIndex
|
||||||
}
|
}
|
||||||
return { modifiedAst: _modifiedAst, pathToNodeMap }
|
return { modifiedAst: _modifiedAst, pathToNodeMap, exprInsertIndex }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyConstraintAxisAlign({
|
export function applyConstraintAxisAlign({
|
||||||
|
@ -86,6 +86,7 @@ export async function applyConstraintAngleBetween({
|
|||||||
}): Promise<{
|
}): Promise<{
|
||||||
modifiedAst: Program
|
modifiedAst: Program
|
||||||
pathToNodeMap: PathToNodeMap
|
pathToNodeMap: PathToNodeMap
|
||||||
|
exprInsertIndex: number
|
||||||
}> {
|
}> {
|
||||||
const info = angleBetweenInfo({ selectionRanges })
|
const info = angleBetweenInfo({ selectionRanges })
|
||||||
if (err(info)) return Promise.reject(info)
|
if (err(info)) return Promise.reject(info)
|
||||||
@ -122,6 +123,7 @@ export async function applyConstraintAngleBetween({
|
|||||||
return {
|
return {
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
|
exprInsertIndex: -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +143,7 @@ export async function applyConstraintAngleBetween({
|
|||||||
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
||||||
transformed2
|
transformed2
|
||||||
|
|
||||||
|
let exprInsertIndex = -1
|
||||||
if (variableName) {
|
if (variableName) {
|
||||||
const newBody = [..._modifiedAst.body]
|
const newBody = [..._modifiedAst.body]
|
||||||
newBody.splice(
|
newBody.splice(
|
||||||
@ -153,9 +156,11 @@ export async function applyConstraintAngleBetween({
|
|||||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||||
})
|
})
|
||||||
|
exprInsertIndex = newVariableInsertIndex
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _modifiedAst,
|
modifiedAst: _modifiedAst,
|
||||||
pathToNodeMap: _pathToNodeMap,
|
pathToNodeMap: _pathToNodeMap,
|
||||||
|
exprInsertIndex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,15 +87,13 @@ export function horzVertDistanceInfo({
|
|||||||
export async function applyConstraintHorzVertDistance({
|
export async function applyConstraintHorzVertDistance({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
constraint,
|
constraint,
|
||||||
// TODO align will always be false (covered by synconous applyConstraintHorzVertAlign), remove it
|
|
||||||
isAlign = false,
|
|
||||||
}: {
|
}: {
|
||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
constraint: 'setHorzDistance' | 'setVertDistance'
|
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||||
isAlign?: false
|
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
modifiedAst: Program
|
modifiedAst: Program
|
||||||
pathToNodeMap: PathToNodeMap
|
pathToNodeMap: PathToNodeMap
|
||||||
|
exprInsertIndex: number
|
||||||
}> {
|
}> {
|
||||||
const info = horzVertDistanceInfo({
|
const info = horzVertDistanceInfo({
|
||||||
selectionRanges: selectionRanges,
|
selectionRanges: selectionRanges,
|
||||||
@ -133,13 +131,12 @@ export async function applyConstraintHorzVertDistance({
|
|||||||
return {
|
return {
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
|
exprInsertIndex: -1,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!isExprBinaryPart(valueNode))
|
if (!isExprBinaryPart(valueNode))
|
||||||
return Promise.reject('Invalid valueNode, is not a BinaryPart')
|
return Promise.reject('Invalid valueNode, is not a BinaryPart')
|
||||||
let finalValue = isAlign
|
let finalValue = removeDoubleNegatives(valueNode, sign, variableName)
|
||||||
? createLiteral(0)
|
|
||||||
: removeDoubleNegatives(valueNode, sign, variableName)
|
|
||||||
// transform again but forcing certain values
|
// transform again but forcing certain values
|
||||||
const transformed = transformSecondarySketchLinesTagFirst({
|
const transformed = transformSecondarySketchLinesTagFirst({
|
||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
@ -152,6 +149,7 @@ export async function applyConstraintHorzVertDistance({
|
|||||||
|
|
||||||
if (err(transformed)) return Promise.reject(transformed)
|
if (err(transformed)) return Promise.reject(transformed)
|
||||||
const { modifiedAst: _modifiedAst, pathToNodeMap } = transformed
|
const { modifiedAst: _modifiedAst, pathToNodeMap } = transformed
|
||||||
|
let exprInsertIndex = -1
|
||||||
if (variableName) {
|
if (variableName) {
|
||||||
const newBody = [..._modifiedAst.body]
|
const newBody = [..._modifiedAst.body]
|
||||||
newBody.splice(
|
newBody.splice(
|
||||||
@ -164,10 +162,12 @@ export async function applyConstraintHorzVertDistance({
|
|||||||
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||||
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||||
})
|
})
|
||||||
|
exprInsertIndex = newVariableInsertIndex
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _modifiedAst,
|
modifiedAst: _modifiedAst,
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
|
exprInsertIndex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,10 +74,14 @@ export async function applyConstraintLength({
|
|||||||
}: {
|
}: {
|
||||||
length: KclCommandValue
|
length: KclCommandValue
|
||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
}) {
|
}): Promise<{
|
||||||
|
modifiedAst: Program
|
||||||
|
pathToNodeMap: PathToNodeMap
|
||||||
|
exprInsertIndex: number
|
||||||
|
}> {
|
||||||
const ast = kclManager.ast
|
const ast = kclManager.ast
|
||||||
const angleLength = angleLengthInfo({ selectionRanges })
|
const angleLength = angleLengthInfo({ selectionRanges })
|
||||||
if (err(angleLength)) return angleLength
|
if (err(angleLength)) return Promise.reject(angleLength)
|
||||||
const { transforms } = angleLength
|
const { transforms } = angleLength
|
||||||
|
|
||||||
let distanceExpression: Expr = length.valueAst
|
let distanceExpression: Expr = length.valueAst
|
||||||
@ -98,7 +102,7 @@ export async function applyConstraintLength({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isExprBinaryPart(distanceExpression)) {
|
if (!isExprBinaryPart(distanceExpression)) {
|
||||||
return new Error('Invalid valueNode, is not a BinaryPart')
|
return Promise.reject('Invalid valueNode, is not a BinaryPart')
|
||||||
}
|
}
|
||||||
|
|
||||||
const retval = transformAstSketchLines({
|
const retval = transformAstSketchLines({
|
||||||
@ -116,6 +120,12 @@ export async function applyConstraintLength({
|
|||||||
return {
|
return {
|
||||||
modifiedAst: _modifiedAst,
|
modifiedAst: _modifiedAst,
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
|
exprInsertIndex:
|
||||||
|
'variableName' in length &&
|
||||||
|
length.variableName &&
|
||||||
|
length.insertIndex !== undefined
|
||||||
|
? length.insertIndex
|
||||||
|
: -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +138,7 @@ export async function applyConstraintAngleLength({
|
|||||||
}): Promise<{
|
}): Promise<{
|
||||||
modifiedAst: Program
|
modifiedAst: Program
|
||||||
pathToNodeMap: PathToNodeMap
|
pathToNodeMap: PathToNodeMap
|
||||||
|
exprInsertIndex: number
|
||||||
}> {
|
}> {
|
||||||
const angleLength = angleLengthInfo({ selectionRanges, angleOrLength })
|
const angleLength = angleLengthInfo({ selectionRanges, angleOrLength })
|
||||||
if (err(angleLength)) return Promise.reject(angleLength)
|
if (err(angleLength)) return Promise.reject(angleLength)
|
||||||
@ -212,5 +223,6 @@ export async function applyConstraintAngleLength({
|
|||||||
return {
|
return {
|
||||||
modifiedAst: _modifiedAst,
|
modifiedAst: _modifiedAst,
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
|
exprInsertIndex: variableName ? newVariableInsertIndex : -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -413,7 +413,6 @@ export class KclManager {
|
|||||||
if (!isInterrupted) {
|
if (!isInterrupted) {
|
||||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.engineCommandManager.addCommandLog({
|
this.engineCommandManager.addCommandLog({
|
||||||
type: 'execution-done',
|
type: 'execution-done',
|
||||||
data: null,
|
data: null,
|
||||||
@ -465,6 +464,7 @@ export class KclManager {
|
|||||||
|
|
||||||
this._logs = logs
|
this._logs = logs
|
||||||
this.addDiagnostics(kclErrorsToDiagnostics(errors))
|
this.addDiagnostics(kclErrorsToDiagnostics(errors))
|
||||||
|
|
||||||
this._execState = execState
|
this._execState = execState
|
||||||
this._variables = execState.variables
|
this._variables = execState.variables
|
||||||
if (!errors.length) {
|
if (!errors.length) {
|
||||||
|
@ -27,6 +27,7 @@ export type ToolTip =
|
|||||||
| 'angledLineThatIntersects'
|
| 'angledLineThatIntersects'
|
||||||
| 'tangentialArcTo'
|
| 'tangentialArcTo'
|
||||||
| 'circle'
|
| 'circle'
|
||||||
|
| 'circleThreePoint'
|
||||||
|
|
||||||
export const toolTips: Array<ToolTip> = [
|
export const toolTips: Array<ToolTip> = [
|
||||||
'line',
|
'line',
|
||||||
@ -42,6 +43,7 @@ export const toolTips: Array<ToolTip> = [
|
|||||||
'yLineTo',
|
'yLineTo',
|
||||||
'angledLineThatIntersects',
|
'angledLineThatIntersects',
|
||||||
'tangentialArcTo',
|
'tangentialArcTo',
|
||||||
|
'circleThreePoint',
|
||||||
]
|
]
|
||||||
|
|
||||||
export async function executeAst({
|
export async function executeAst({
|
||||||
@ -71,7 +73,6 @@ export async function executeAst({
|
|||||||
: executeWithEngine(ast, engineCommandManager, path))
|
: executeWithEngine(ast, engineCommandManager, path))
|
||||||
|
|
||||||
await engineCommandManager.waitForAllCommands()
|
await engineCommandManager.waitForAllCommands()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
logs: [],
|
logs: [],
|
||||||
errors: [],
|
errors: [],
|
||||||
|
@ -3,7 +3,6 @@ import {
|
|||||||
recast,
|
recast,
|
||||||
initPromise,
|
initPromise,
|
||||||
Identifier,
|
Identifier,
|
||||||
SourceRange,
|
|
||||||
topLevelRange,
|
topLevelRange,
|
||||||
LiteralValue,
|
LiteralValue,
|
||||||
Literal,
|
Literal,
|
||||||
@ -25,6 +24,7 @@ import {
|
|||||||
deleteSegmentFromPipeExpression,
|
deleteSegmentFromPipeExpression,
|
||||||
removeSingleConstraintInfo,
|
removeSingleConstraintInfo,
|
||||||
deleteFromSelection,
|
deleteFromSelection,
|
||||||
|
splitPipedProfile,
|
||||||
} from './modifyAst'
|
} from './modifyAst'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import { findUsesOfTagInPipe } from './queryAst'
|
import { findUsesOfTagInPipe } from './queryAst'
|
||||||
@ -996,3 +996,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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -21,8 +21,11 @@ import {
|
|||||||
SourceRange,
|
SourceRange,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
isPathToNodeNumber,
|
isPathToNodeNumber,
|
||||||
|
parse,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
ArtifactGraph,
|
||||||
VariableMap,
|
VariableMap,
|
||||||
|
KclValue,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import {
|
import {
|
||||||
isNodeSafeToReplacePath,
|
isNodeSafeToReplacePath,
|
||||||
@ -31,6 +34,8 @@ import {
|
|||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
traverse,
|
traverse,
|
||||||
|
getBodyIndex,
|
||||||
|
isCallExprWithName,
|
||||||
ARG_INDEX_FIELD,
|
ARG_INDEX_FIELD,
|
||||||
LABELED_ARG_FIELD,
|
LABELED_ARG_FIELD,
|
||||||
} from './queryAst'
|
} from './queryAst'
|
||||||
@ -48,7 +53,7 @@ import {
|
|||||||
transformAstSketchLines,
|
transformAstSketchLines,
|
||||||
} from './std/sketchcombos'
|
} from './std/sketchcombos'
|
||||||
import { DefaultPlaneStr } from 'lib/planes'
|
import { DefaultPlaneStr } from 'lib/planes'
|
||||||
import { isOverlap, roundOff } from 'lib/utils'
|
import { isArray, isOverlap, roundOff } from 'lib/utils'
|
||||||
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
||||||
import { SimplifiedArgDetails } from './std/stdTypes'
|
import { SimplifiedArgDetails } from './std/stdTypes'
|
||||||
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
||||||
@ -56,8 +61,19 @@ import { Models } from '@kittycad/lib'
|
|||||||
import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
||||||
|
import {
|
||||||
|
Artifact,
|
||||||
|
expandCap,
|
||||||
|
expandPlane,
|
||||||
|
expandWall,
|
||||||
|
getArtifactOfTypes,
|
||||||
|
getArtifactsOfTypes,
|
||||||
|
getPathsFromArtifact,
|
||||||
|
} from './std/artifactGraph'
|
||||||
|
import { BodyItem } from 'wasm-lib/kcl/bindings/BodyItem'
|
||||||
import { findKwArg } from './util'
|
import { findKwArg } from './util'
|
||||||
import { deleteEdgeTreatment } from './modifyAst/addEdgeTreatment'
|
import { deleteEdgeTreatment } from './modifyAst/addEdgeTreatment'
|
||||||
|
import { engineCommandManager } from 'lib/singletons'
|
||||||
|
|
||||||
export function startSketchOnDefault(
|
export function startSketchOnDefault(
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
@ -90,41 +106,54 @@ export function startSketchOnDefault(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addStartProfileAt(
|
export function insertNewStartProfileAt(
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
pathToNode: PathToNode,
|
sketchEntryNodePath: PathToNode,
|
||||||
at: [number, number]
|
sketchNodePaths: PathToNode[],
|
||||||
): { modifiedAst: Node<Program>; pathToNode: PathToNode } | Error {
|
planeNodePath: PathToNode,
|
||||||
const _node1 = getNodeFromPath<VariableDeclaration>(
|
at: [number, number],
|
||||||
|
insertType: 'start' | 'end' = 'end'
|
||||||
|
):
|
||||||
|
| {
|
||||||
|
modifiedAst: Node<Program>
|
||||||
|
updatedSketchNodePaths: PathToNode[]
|
||||||
|
updatedEntryNodePath: PathToNode
|
||||||
|
}
|
||||||
|
| Error {
|
||||||
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
node,
|
node,
|
||||||
pathToNode,
|
planeNodePath,
|
||||||
'VariableDeclaration'
|
'VariableDeclarator'
|
||||||
)
|
)
|
||||||
if (err(_node1)) return _node1
|
if (err(varDec)) return varDec
|
||||||
const variableDeclaration = _node1.node
|
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
|
||||||
if (variableDeclaration.type !== 'VariableDeclaration') {
|
|
||||||
return new Error('variableDeclaration.init.type !== PipeExpression')
|
const newExpression = createVariableDeclaration(
|
||||||
}
|
findUniqueName(node, 'profile'),
|
||||||
const _node = { ...node }
|
createCallExpressionStdLib('startProfileAt', [
|
||||||
const init = variableDeclaration.declaration.init
|
createArrayExpression([
|
||||||
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
createLiteral(roundOff(at[0])),
|
||||||
createArrayExpression([
|
createLiteral(roundOff(at[1])),
|
||||||
createLiteral(roundOff(at[0])),
|
]),
|
||||||
createLiteral(roundOff(at[1])),
|
createIdentifier(varDec.node.id.name),
|
||||||
]),
|
|
||||||
createPipeSubstitution(),
|
|
||||||
])
|
|
||||||
if (init.type === 'PipeExpression') {
|
|
||||||
init.body.splice(1, 0, startProfileAt)
|
|
||||||
} else {
|
|
||||||
variableDeclaration.declaration.init = createPipeExpression([
|
|
||||||
init,
|
|
||||||
startProfileAt,
|
|
||||||
])
|
])
|
||||||
}
|
)
|
||||||
|
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 {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
updatedSketchNodePaths,
|
||||||
|
updatedEntryNodePath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,8 +253,21 @@ export function mutateKwArg(
|
|||||||
for (let i = 0; i < node.arguments.length; i++) {
|
for (let i = 0; i < node.arguments.length; i++) {
|
||||||
const arg = node.arguments[i]
|
const arg = node.arguments[i]
|
||||||
if (arg.label.name === label) {
|
if (arg.label.name === label) {
|
||||||
node.arguments[i].arg = val
|
if (isLiteralArrayOrStatic(val) && isLiteralArrayOrStatic(arg.arg)) {
|
||||||
return true
|
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))
|
node.arguments.push(createLabeledArg(label, val))
|
||||||
@ -288,15 +330,17 @@ export function mutateObjExpProp(
|
|||||||
export function extrudeSketch({
|
export function extrudeSketch({
|
||||||
node,
|
node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
shouldPipe = false,
|
|
||||||
distance = createLiteral(4),
|
distance = createLiteral(4),
|
||||||
extrudeName,
|
extrudeName,
|
||||||
|
artifact,
|
||||||
|
artifactGraph,
|
||||||
}: {
|
}: {
|
||||||
node: Node<Program>
|
node: Node<Program>
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
shouldPipe?: boolean
|
|
||||||
distance: Expr
|
distance: Expr
|
||||||
extrudeName?: string
|
extrudeName?: string
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
|
artifact?: Artifact
|
||||||
}):
|
}):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Node<Program>
|
modifiedAst: Node<Program>
|
||||||
@ -304,10 +348,16 @@ export function extrudeSketch({
|
|||||||
pathToExtrudeArg: PathToNode
|
pathToExtrudeArg: PathToNode
|
||||||
}
|
}
|
||||||
| Error {
|
| Error {
|
||||||
|
const orderedSketchNodePaths = getPathsFromArtifact({
|
||||||
|
artifact: artifact,
|
||||||
|
sketchPathToNode: pathToNode,
|
||||||
|
artifactGraph,
|
||||||
|
ast: node,
|
||||||
|
})
|
||||||
|
if (err(orderedSketchNodePaths)) return orderedSketchNodePaths
|
||||||
const _node = structuredClone(node)
|
const _node = structuredClone(node)
|
||||||
const _node1 = getNodeFromPath(_node, pathToNode)
|
const _node1 = getNodeFromPath(_node, pathToNode)
|
||||||
if (err(_node1)) return _node1
|
if (err(_node1)) return _node1
|
||||||
const { node: sketchExpression } = _node1
|
|
||||||
|
|
||||||
// determine if sketchExpression is in a pipeExpression or not
|
// determine if sketchExpression is in a pipeExpression or not
|
||||||
const _node2 = getNodeFromPath<PipeExpression>(
|
const _node2 = getNodeFromPath<PipeExpression>(
|
||||||
@ -316,9 +366,6 @@ export function extrudeSketch({
|
|||||||
'PipeExpression'
|
'PipeExpression'
|
||||||
)
|
)
|
||||||
if (err(_node2)) return _node2
|
if (err(_node2)) return _node2
|
||||||
const { node: pipeExpression } = _node2
|
|
||||||
|
|
||||||
const isInPipeExpression = pipeExpression.type === 'PipeExpression'
|
|
||||||
|
|
||||||
const _node3 = getNodeFromPath<VariableDeclarator>(
|
const _node3 = getNodeFromPath<VariableDeclarator>(
|
||||||
_node,
|
_node,
|
||||||
@ -326,54 +373,27 @@ export function extrudeSketch({
|
|||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
)
|
)
|
||||||
if (err(_node3)) return _node3
|
if (err(_node3)) return _node3
|
||||||
const { node: variableDeclarator, shallowPath: pathToDecleration } = _node3
|
const { node: variableDeclarator } = _node3
|
||||||
|
|
||||||
const sketchToExtrude = shouldPipe
|
const extrudeCall = createCallExpressionStdLibKw(
|
||||||
? createPipeSubstitution()
|
'extrude',
|
||||||
: createIdentifier(variableDeclarator.id.name)
|
createIdentifier(variableDeclarator.id.name),
|
||||||
const extrudeCall = createCallExpressionStdLibKw('extrude', sketchToExtrude, [
|
[createLabeledArg('length', distance)]
|
||||||
createLabeledArg('length', distance),
|
)
|
||||||
])
|
|
||||||
// index of the 'length' arg above. If you reorder the labeled args above,
|
// index of the 'length' arg above. If you reorder the labeled args above,
|
||||||
// make sure to update this too.
|
// make sure to update this too.
|
||||||
const argIndex = 0
|
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,
|
// We're not creating a pipe expression,
|
||||||
// but rather a separate constant for the extrusion
|
// but rather a separate constant for the extrusion
|
||||||
const name =
|
const name =
|
||||||
extrudeName ?? findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.EXTRUDE)
|
extrudeName ?? findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.EXTRUDE)
|
||||||
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
|
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
|
||||||
|
|
||||||
const sketchIndexInPathToNode =
|
const lastSketchNodePath =
|
||||||
pathToDecleration.findIndex((a) => a[0] === 'body') + 1
|
orderedSketchNodePaths[orderedSketchNodePaths.length - 1]
|
||||||
const sketchIndexInBody = pathToDecleration[
|
|
||||||
sketchIndexInPathToNode
|
const sketchIndexInBody = Number(lastSketchNodePath[1][0])
|
||||||
][0] as number
|
|
||||||
_node.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
_node.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
||||||
|
|
||||||
const pathToExtrudeArg: PathToNode = [
|
const pathToExtrudeArg: PathToNode = [
|
||||||
@ -1374,6 +1394,39 @@ export async function deleteFromSelection(
|
|||||||
({} as any)
|
({} as any)
|
||||||
): Promise<Node<Program> | Error> {
|
): Promise<Node<Program> | Error> {
|
||||||
const astClone = structuredClone(ast)
|
const astClone = structuredClone(ast)
|
||||||
|
if (
|
||||||
|
(selection.artifact?.type === 'plane' ||
|
||||||
|
selection.artifact?.type === 'cap' ||
|
||||||
|
selection.artifact?.type === 'wall') &&
|
||||||
|
selection.artifact?.pathIds?.length
|
||||||
|
) {
|
||||||
|
const plane =
|
||||||
|
selection.artifact.type === 'plane'
|
||||||
|
? expandPlane(selection.artifact, engineCommandManager.artifactGraph)
|
||||||
|
: selection.artifact.type === 'wall'
|
||||||
|
? expandWall(selection.artifact, engineCommandManager.artifactGraph)
|
||||||
|
: expandCap(selection.artifact, engineCommandManager.artifactGraph)
|
||||||
|
for (const path of plane.paths.sort(
|
||||||
|
(a, b) => b.codeRef.range[0] - a.codeRef.range[0]
|
||||||
|
)) {
|
||||||
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
|
ast,
|
||||||
|
path.codeRef.pathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
if (err(varDec)) return varDec
|
||||||
|
const bodyIndex = Number(varDec.shallowPath[1][0])
|
||||||
|
astClone.body.splice(bodyIndex, 1)
|
||||||
|
}
|
||||||
|
// If it's a cap, we're not going to continue and try to
|
||||||
|
// delete the extrusion
|
||||||
|
if (
|
||||||
|
selection.artifact.type === 'cap' ||
|
||||||
|
selection.artifact.type === 'wall'
|
||||||
|
) {
|
||||||
|
return astClone
|
||||||
|
}
|
||||||
|
}
|
||||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
ast,
|
ast,
|
||||||
selection?.codeRef?.pathToNode,
|
selection?.codeRef?.pathToNode,
|
||||||
@ -1452,59 +1505,108 @@ export async function deleteFromSelection(
|
|||||||
if (extrudeNameToDelete) {
|
if (extrudeNameToDelete) {
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
let currentVariableName = ''
|
|
||||||
const pathsDependingOnExtrude: Array<{
|
const pathsDependingOnExtrude: Array<{
|
||||||
path: PathToNode
|
path: PathToNode
|
||||||
sketchName: string
|
variable: KclValue
|
||||||
}> = []
|
}> = []
|
||||||
traverse(astClone, {
|
|
||||||
leave: (node) => {
|
|
||||||
if (node.type === 'VariableDeclaration') {
|
|
||||||
currentVariableName = ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enter: (node, path) => {
|
|
||||||
;(async () => {
|
|
||||||
if (node.type === 'VariableDeclaration') {
|
|
||||||
currentVariableName = node.declaration.id.name
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
// match startSketchOn(${extrudeNameToDelete})
|
|
||||||
node.type === 'CallExpression' &&
|
|
||||||
node.callee.name === 'startSketchOn' &&
|
|
||||||
node.arguments[0].type === 'Identifier' &&
|
|
||||||
node.arguments[0].name === extrudeNameToDelete
|
|
||||||
) {
|
|
||||||
pathsDependingOnExtrude.push({
|
|
||||||
path,
|
|
||||||
sketchName: currentVariableName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})().catch(reportRejection)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const roundLiteral = (x: number) => createLiteral(roundOff(x))
|
const roundLiteral = (x: number) => createLiteral(roundOff(x))
|
||||||
const modificationDetails: {
|
const modificationDetails: {
|
||||||
parent: PipeExpression['body']
|
parentPipe: PipeExpression['body']
|
||||||
|
parentInit: VariableDeclarator
|
||||||
faceDetails: Models['FaceIsPlanar_type']
|
faceDetails: Models['FaceIsPlanar_type']
|
||||||
lastKey: number
|
lastKey: number | string
|
||||||
}[] = []
|
}[] = []
|
||||||
for (const { path, sketchName } of pathsDependingOnExtrude) {
|
const wallArtifact =
|
||||||
const parent = getNodeFromPath<PipeExpression['body']>(
|
selection.artifact?.type === 'wall'
|
||||||
|
? selection.artifact
|
||||||
|
: selection.artifact?.type === 'segment' &&
|
||||||
|
selection.artifact.surfaceId
|
||||||
|
? getArtifactOfTypes(
|
||||||
|
{ key: selection.artifact.surfaceId, types: ['wall'] },
|
||||||
|
engineCommandManager.artifactGraph
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
if (err(wallArtifact)) return
|
||||||
|
if (wallArtifact) {
|
||||||
|
const sweep = getArtifactOfTypes(
|
||||||
|
{ key: wallArtifact.sweepId, types: ['sweep'] },
|
||||||
|
engineCommandManager.artifactGraph
|
||||||
|
)
|
||||||
|
if (err(sweep)) return
|
||||||
|
const wallsWithDependencies = Array.from(
|
||||||
|
getArtifactsOfTypes(
|
||||||
|
{ keys: sweep.surfaceIds, types: ['wall', 'cap'] },
|
||||||
|
engineCommandManager.artifactGraph
|
||||||
|
).values()
|
||||||
|
).filter((wall) => wall?.pathIds?.length)
|
||||||
|
const wallIds = wallsWithDependencies.map((wall) => wall.id)
|
||||||
|
Object.entries(variables).forEach(([key, _var]) => {
|
||||||
|
if (
|
||||||
|
_var?.type === 'Face' &&
|
||||||
|
wallIds.includes(_var.value.artifactId)
|
||||||
|
) {
|
||||||
|
const pathToStartSketchOn = getNodePathFromSourceRange(
|
||||||
|
astClone,
|
||||||
|
_var.value.__meta[0].sourceRange
|
||||||
|
)
|
||||||
|
pathsDependingOnExtrude.push({
|
||||||
|
path: pathToStartSketchOn,
|
||||||
|
variable: _var,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
_var?.type === 'Sketch' &&
|
||||||
|
_var.value.on.type === 'face' &&
|
||||||
|
wallIds.includes(_var.value.on.artifactId)
|
||||||
|
) {
|
||||||
|
const pathToStartSketchOn = getNodePathFromSourceRange(
|
||||||
|
astClone,
|
||||||
|
_var.value.on.__meta[0].sourceRange
|
||||||
|
)
|
||||||
|
pathsDependingOnExtrude.push({
|
||||||
|
path: pathToStartSketchOn,
|
||||||
|
variable: {
|
||||||
|
type: 'Face',
|
||||||
|
value: _var.value.on,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for (const { path, variable } of pathsDependingOnExtrude) {
|
||||||
|
// `parentPipe` and `parentInit` are the exact same node, but because it could either be an array or on object node
|
||||||
|
// putting them in two different variables was the only way to get TypeScript to stop complaining
|
||||||
|
// the reason why we're grabbing the parent and the last key is because we want to mutate the ast
|
||||||
|
// so `parent[lastKey]` does the trick, if there's a better way of doing this I'm all years
|
||||||
|
const parentPipe = getNodeFromPath<PipeExpression['body']>(
|
||||||
astClone,
|
astClone,
|
||||||
path.slice(0, -1)
|
path.slice(0, -1)
|
||||||
)
|
)
|
||||||
if (err(parent)) {
|
const parentInit = getNodeFromPath<VariableDeclarator>(
|
||||||
|
astClone,
|
||||||
|
path.slice(0, -1)
|
||||||
|
)
|
||||||
|
if (err(parentPipe) || err(parentInit)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const sketchToPreserve = sketchFromKclValue(
|
if (!variable) return new Error('Could not find sketch')
|
||||||
variables[sketchName],
|
const artifactId =
|
||||||
sketchName
|
variable.type === 'Sketch'
|
||||||
)
|
? variable.value.artifactId
|
||||||
if (err(sketchToPreserve)) return sketchToPreserve
|
: variable.type === 'Face'
|
||||||
|
? variable.value.artifactId
|
||||||
|
: ''
|
||||||
|
if (!artifactId) return new Error('Sketch not on anything')
|
||||||
|
const onId =
|
||||||
|
variable.type === 'Sketch'
|
||||||
|
? variable.value.on.id
|
||||||
|
: variable.type === 'Face'
|
||||||
|
? variable.value.id
|
||||||
|
: ''
|
||||||
|
if (!onId) return new Error('Sketch not on anything')
|
||||||
// Can't kick off multiple requests at once as getFaceDetails
|
// Can't kick off multiple requests at once as getFaceDetails
|
||||||
// is three engine calls in one and they conflict
|
// is three engine calls in one and they conflict
|
||||||
const faceDetails = await getFaceDetails(sketchToPreserve.on.id)
|
const faceDetails = await getFaceDetails(onId)
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
faceDetails.origin &&
|
faceDetails.origin &&
|
||||||
@ -1515,14 +1617,20 @@ export async function deleteFromSelection(
|
|||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const lastKey = Number(path.slice(-1)[0][0])
|
const lastKey = path.slice(-1)[0][0]
|
||||||
modificationDetails.push({
|
modificationDetails.push({
|
||||||
parent: parent.node,
|
parentPipe: parentPipe.node,
|
||||||
|
parentInit: parentInit.node,
|
||||||
faceDetails,
|
faceDetails,
|
||||||
lastKey,
|
lastKey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for (const { parent, faceDetails, lastKey } of modificationDetails) {
|
for (const {
|
||||||
|
parentInit,
|
||||||
|
parentPipe,
|
||||||
|
faceDetails,
|
||||||
|
lastKey,
|
||||||
|
} of modificationDetails) {
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
faceDetails.origin &&
|
faceDetails.origin &&
|
||||||
@ -1533,7 +1641,7 @@ export async function deleteFromSelection(
|
|||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
parent[lastKey] = createCallExpressionStdLib('startSketchOn', [
|
const expression = createCallExpressionStdLib('startSketchOn', [
|
||||||
createObjectExpression({
|
createObjectExpression({
|
||||||
plane: createObjectExpression({
|
plane: createObjectExpression({
|
||||||
origin: createObjectExpression({
|
origin: createObjectExpression({
|
||||||
@ -1559,6 +1667,14 @@ export async function deleteFromSelection(
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
if (
|
||||||
|
parentInit.type === 'VariableDeclarator' &&
|
||||||
|
lastKey === 'init'
|
||||||
|
) {
|
||||||
|
parentInit[lastKey] = expression
|
||||||
|
} else if (isArray(parentPipe) && typeof lastKey === 'number') {
|
||||||
|
parentPipe[lastKey] = expression
|
||||||
|
}
|
||||||
}
|
}
|
||||||
resolve(true)
|
resolve(true)
|
||||||
})().catch(reportRejection)
|
})().catch(reportRejection)
|
||||||
@ -1570,15 +1686,29 @@ export async function deleteFromSelection(
|
|||||||
return deleteEdgeTreatment(astClone, selection)
|
return deleteEdgeTreatment(astClone, selection)
|
||||||
} else if (varDec.node.init.type === 'PipeExpression') {
|
} else if (varDec.node.init.type === 'PipeExpression') {
|
||||||
const pipeBody = varDec.node.init.body
|
const pipeBody = varDec.node.init.body
|
||||||
|
const doNotDeleteProfileIfItHasBeenExtruded = !(
|
||||||
|
selection?.artifact?.type === 'segment' && selection?.artifact?.surfaceId
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
pipeBody[0].type === 'CallExpression' &&
|
pipeBody[0].type === 'CallExpression' &&
|
||||||
pipeBody[0].callee.name === 'startSketchOn'
|
doNotDeleteProfileIfItHasBeenExtruded &&
|
||||||
|
(pipeBody[0].callee.name === 'startSketchOn' ||
|
||||||
|
pipeBody[0].callee.name === 'startProfileAt')
|
||||||
) {
|
) {
|
||||||
// remove varDec
|
// remove varDec
|
||||||
const varDecIndex = varDec.shallowPath[1][0] as number
|
const varDecIndex = varDec.shallowPath[1][0] as number
|
||||||
astClone.body.splice(varDecIndex, 1)
|
astClone.body.splice(varDecIndex, 1)
|
||||||
return astClone
|
return astClone
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
// single expression profiles
|
||||||
|
(varDec.node.init.type === 'CallExpressionKw' ||
|
||||||
|
varDec.node.init.type === 'CallExpression') &&
|
||||||
|
['circleThreePoint', 'circle'].includes(varDec.node.init.callee.name)
|
||||||
|
) {
|
||||||
|
const varDecIndex = varDec.shallowPath[1][0] as number
|
||||||
|
astClone.body.splice(varDecIndex, 1)
|
||||||
|
return astClone
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Error('Selection not recognised, could not delete')
|
return new Error('Selection not recognised, could not delete')
|
||||||
@ -1588,6 +1718,167 @@ const nonCodeMetaEmpty = () => {
|
|||||||
return { nonCodeNodes: {}, startNodes: [], start: 0, end: 0 }
|
return { nonCodeNodes: {}, startNodes: [], start: 0, end: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createLabeledArg = (name: string, arg: Expr): LabeledArg => {
|
export function getInsertIndex(
|
||||||
return { label: createIdentifier(name), arg, type: 'LabeledArg' }
|
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,9 +5,9 @@ import {
|
|||||||
PathToNode,
|
PathToNode,
|
||||||
Expr,
|
Expr,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
PipeExpression,
|
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
CallExpressionKw,
|
CallExpressionKw,
|
||||||
|
ArtifactGraph,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
@ -16,7 +16,6 @@ import {
|
|||||||
createCallExpressionStdLib,
|
createCallExpressionStdLib,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
createIdentifier,
|
createIdentifier,
|
||||||
createPipeExpression,
|
|
||||||
findUniqueName,
|
findUniqueName,
|
||||||
createVariableDeclaration,
|
createVariableDeclaration,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
@ -26,14 +25,18 @@ import {
|
|||||||
mutateAstWithTagForSketchSegment,
|
mutateAstWithTagForSketchSegment,
|
||||||
getEdgeTagCall,
|
getEdgeTagCall,
|
||||||
} from 'lang/modifyAst/addEdgeTreatment'
|
} from 'lang/modifyAst/addEdgeTreatment'
|
||||||
|
import { Artifact, getPathsFromArtifact } from 'lang/std/artifactGraph'
|
||||||
|
import { kclManager } from 'lib/singletons'
|
||||||
|
|
||||||
export function revolveSketch(
|
export function revolveSketch(
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
pathToSketchNode: PathToNode,
|
pathToSketchNode: PathToNode,
|
||||||
shouldPipe = false,
|
|
||||||
angle: Expr = createLiteral(4),
|
angle: Expr = createLiteral(4),
|
||||||
axisOrEdge: string,
|
axisOrEdge: string,
|
||||||
axis: string,
|
axis: string,
|
||||||
edge: Selections
|
edge: Selections,
|
||||||
|
artifactGraph: ArtifactGraph,
|
||||||
|
artifact?: Artifact
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Node<Program>
|
modifiedAst: Node<Program>
|
||||||
@ -41,6 +44,13 @@ export function revolveSketch(
|
|||||||
pathToRevolveArg: PathToNode
|
pathToRevolveArg: PathToNode
|
||||||
}
|
}
|
||||||
| Error {
|
| Error {
|
||||||
|
const orderedSketchNodePaths = getPathsFromArtifact({
|
||||||
|
artifact: artifact,
|
||||||
|
sketchPathToNode: pathToSketchNode,
|
||||||
|
artifactGraph,
|
||||||
|
ast: kclManager.ast,
|
||||||
|
})
|
||||||
|
if (err(orderedSketchNodePaths)) return orderedSketchNodePaths
|
||||||
const clonedAst = structuredClone(ast)
|
const clonedAst = structuredClone(ast)
|
||||||
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
|
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
|
||||||
if (err(sketchNode)) return sketchNode
|
if (err(sketchNode)) return sketchNode
|
||||||
@ -82,29 +92,13 @@ export function revolveSketch(
|
|||||||
generatedAxis = createLiteral(axis)
|
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>(
|
const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>(
|
||||||
clonedAst,
|
clonedAst,
|
||||||
pathToSketchNode,
|
pathToSketchNode,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
)
|
)
|
||||||
if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode
|
if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode
|
||||||
const {
|
const { node: sketchVariableDeclarator } = sketchVariableDeclaratorNode
|
||||||
node: sketchVariableDeclarator,
|
|
||||||
shallowPath: sketchPathToDecleration,
|
|
||||||
} = sketchVariableDeclaratorNode
|
|
||||||
|
|
||||||
if (!generatedAxis) return new Error('Generated axis selection is missing.')
|
if (!generatedAxis) return new Error('Generated axis selection is missing.')
|
||||||
|
|
||||||
@ -116,41 +110,16 @@ export function revolveSketch(
|
|||||||
createIdentifier(sketchVariableDeclarator.id.name),
|
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,
|
// We're not creating a pipe expression,
|
||||||
// but rather a separate constant for the extrusion
|
// but rather a separate constant for the extrusion
|
||||||
const name = findUniqueName(clonedAst, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE)
|
const name = findUniqueName(clonedAst, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE)
|
||||||
const VariableDeclaration = createVariableDeclaration(name, revolveCall)
|
const VariableDeclaration = createVariableDeclaration(name, revolveCall)
|
||||||
const sketchIndexInPathToNode =
|
const lastSketchNodePath =
|
||||||
sketchPathToDecleration.findIndex((a) => a[0] === 'body') + 1
|
orderedSketchNodePaths[orderedSketchNodePaths.length - 1]
|
||||||
const sketchIndexInBody = sketchPathToDecleration[sketchIndexInPathToNode][0]
|
let sketchIndexInBody = Number(lastSketchNodePath[1][0])
|
||||||
let insertIndex = sketchIndexInBody
|
if (typeof sketchIndexInBody !== 'number') {
|
||||||
|
return new Error('expected sketchIndexInBody to be a number')
|
||||||
if (typeof insertIndex !== 'number')
|
}
|
||||||
return new Error('expected insertIndex to be a number')
|
|
||||||
|
|
||||||
// If an axis was selected in KCL, find the max index to insert the revolve command
|
// If an axis was selected in KCL, find the max index to insert the revolve command
|
||||||
if (axisDeclaration) {
|
if (axisDeclaration) {
|
||||||
@ -161,14 +130,14 @@ export function revolveSketch(
|
|||||||
if (typeof axisIndex !== 'number')
|
if (typeof axisIndex !== 'number')
|
||||||
return new Error('expected axisIndex to be a number')
|
return new Error('expected axisIndex to be a number')
|
||||||
|
|
||||||
insertIndex = Math.max(insertIndex, axisIndex)
|
sketchIndexInBody = Math.max(sketchIndexInBody, axisIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
clonedAst.body.splice(insertIndex + 1, 0, VariableDeclaration)
|
clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
||||||
|
|
||||||
const pathToRevolveArg: PathToNode = [
|
const pathToRevolveArg: PathToNode = [
|
||||||
['body', ''],
|
['body', ''],
|
||||||
[insertIndex + 1, 'index'],
|
[sketchIndexInBody + 1, 'index'],
|
||||||
['declaration', 'VariableDeclaration'],
|
['declaration', 'VariableDeclaration'],
|
||||||
['init', 'VariableDeclarator'],
|
['init', 'VariableDeclarator'],
|
||||||
['arguments', 'CallExpression'],
|
['arguments', 'CallExpression'],
|
||||||
|
@ -2,7 +2,6 @@ import { ToolTip } from 'lang/langHelpers'
|
|||||||
import { Selection, Selections } from 'lib/selections'
|
import { Selection, Selections } from 'lib/selections'
|
||||||
import {
|
import {
|
||||||
ArrayExpression,
|
ArrayExpression,
|
||||||
ArtifactGraph,
|
|
||||||
BinaryExpression,
|
BinaryExpression,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
CallExpressionKw,
|
CallExpressionKw,
|
||||||
@ -22,6 +21,7 @@ import {
|
|||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
recast,
|
recast,
|
||||||
|
ArtifactGraph,
|
||||||
kclSettings,
|
kclSettings,
|
||||||
unitLenToUnitLength,
|
unitLenToUnitLength,
|
||||||
unitAngToUnitAngle,
|
unitAngToUnitAngle,
|
||||||
@ -37,10 +37,11 @@ import {
|
|||||||
getConstraintType,
|
getConstraintType,
|
||||||
} from './std/sketchcombos'
|
} from './std/sketchcombos'
|
||||||
import { err, Reason } from 'lib/trap'
|
import { err, Reason } from 'lib/trap'
|
||||||
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { findKwArg } from './util'
|
import { findKwArg } from './util'
|
||||||
import { codeRefFromRange } from './std/artifactGraph'
|
import { codeRefFromRange } from './std/artifactGraph'
|
||||||
|
import { FunctionExpression } from 'wasm-lib/kcl/bindings/FunctionExpression'
|
||||||
|
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||||
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
|
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
|
||||||
|
|
||||||
export const LABELED_ARG_FIELD = 'LabeledArg -> Arg'
|
export const LABELED_ARG_FIELD = 'LabeledArg -> Arg'
|
||||||
@ -357,7 +358,13 @@ export function findAllPreviousVariables(
|
|||||||
type ReplacerFn = (
|
type ReplacerFn = (
|
||||||
_ast: Node<Program>,
|
_ast: Node<Program>,
|
||||||
varName: string
|
varName: string
|
||||||
) => { modifiedAst: Node<Program>; pathToReplaced: PathToNode } | Error
|
) =>
|
||||||
|
| {
|
||||||
|
modifiedAst: Node<Program>
|
||||||
|
pathToReplaced: PathToNode
|
||||||
|
exprInsertIndex: number
|
||||||
|
}
|
||||||
|
| Error
|
||||||
|
|
||||||
export function isNodeSafeToReplacePath(
|
export function isNodeSafeToReplacePath(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
@ -409,7 +416,7 @@ export function isNodeSafeToReplacePath(
|
|||||||
if (err(_nodeToReplace)) return _nodeToReplace
|
if (err(_nodeToReplace)) return _nodeToReplace
|
||||||
const nodeToReplace = _nodeToReplace.node as any
|
const nodeToReplace = _nodeToReplace.node as any
|
||||||
nodeToReplace[last[0]] = identifier
|
nodeToReplace[last[0]] = identifier
|
||||||
return { modifiedAst: _ast, pathToReplaced }
|
return { modifiedAst: _ast, pathToReplaced, exprInsertIndex: index }
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasPipeSub = isTypeInValue(finVal as Expr, 'PipeSubstitution')
|
const hasPipeSub = isTypeInValue(finVal as Expr, 'PipeSubstitution')
|
||||||
@ -518,8 +525,15 @@ export function isLinesParallelAndConstrained(
|
|||||||
if (err(_primarySegment)) return _primarySegment
|
if (err(_primarySegment)) return _primarySegment
|
||||||
const primarySegment = _primarySegment.segment
|
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(memVars[varName2], varName2)
|
||||||
|
if (err(sg2)) return sg2
|
||||||
|
|
||||||
const _segment = getSketchSegmentFromSourceRange(
|
const _segment = getSketchSegmentFromSourceRange(
|
||||||
sg,
|
sg2,
|
||||||
secondaryLine?.codeRef?.range
|
secondaryLine?.codeRef?.range
|
||||||
)
|
)
|
||||||
if (err(_segment)) return _segment
|
if (err(_segment)) return _segment
|
||||||
@ -871,6 +885,59 @@ export function getObjExprProperty(
|
|||||||
return { expr: node.properties[index].value, index }
|
return { expr: node.properties[index].value, index }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCursorInFunctionDefinition(
|
||||||
|
ast: Node<Program>,
|
||||||
|
selectionRanges: Selection
|
||||||
|
): boolean {
|
||||||
|
if (!selectionRanges?.codeRef?.pathToNode) return false
|
||||||
|
const node = getNodeFromPath<FunctionExpression>(
|
||||||
|
ast,
|
||||||
|
selectionRanges.codeRef.pathToNode,
|
||||||
|
'FunctionExpression'
|
||||||
|
)
|
||||||
|
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
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Given KCL, returns the settings annotation object if it exists.
|
* Given KCL, returns the settings annotation object if it exists.
|
||||||
*/
|
*/
|
||||||
|
@ -82,6 +82,7 @@ function moreNodePathFromSourceRange(
|
|||||||
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return path
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
Expr,
|
||||||
Artifact,
|
Artifact,
|
||||||
ArtifactGraph,
|
ArtifactGraph,
|
||||||
ArtifactId,
|
ArtifactId,
|
||||||
@ -18,7 +19,8 @@ import {
|
|||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { codeManager } from 'lib/singletons'
|
import { Cap, Plane, Wall } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
import { CapSubType } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
|
||||||
export type { Artifact, ArtifactId, SegmentArtifact } from 'lang/wasm'
|
export type { Artifact, ArtifactId, SegmentArtifact } from 'lang/wasm'
|
||||||
|
|
||||||
@ -37,10 +39,28 @@ export interface PlaneArtifactRich extends BaseArtifact {
|
|||||||
codeRef: CodeRef
|
codeRef: CodeRef
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CapArtifactRich extends BaseArtifact {
|
||||||
|
type: 'cap'
|
||||||
|
subType: CapSubType
|
||||||
|
faceCodeRef: CodeRef
|
||||||
|
edgeCuts: Array<EdgeCut>
|
||||||
|
paths: Array<PathArtifact>
|
||||||
|
sweep?: SweepArtifact
|
||||||
|
}
|
||||||
|
export interface WallArtifactRich extends BaseArtifact {
|
||||||
|
type: 'wall'
|
||||||
|
id: ArtifactId
|
||||||
|
segment: PathArtifact
|
||||||
|
edgeCuts: Array<EdgeCut>
|
||||||
|
sweep: SweepArtifact
|
||||||
|
paths: Array<PathArtifact>
|
||||||
|
faceCodeRef: CodeRef
|
||||||
|
}
|
||||||
|
|
||||||
export interface PathArtifactRich extends BaseArtifact {
|
export interface PathArtifactRich extends BaseArtifact {
|
||||||
type: 'path'
|
type: 'path'
|
||||||
/** A path must always lie on a plane */
|
/** A path must always lie on a plane */
|
||||||
plane: PlaneArtifact | WallArtifact
|
plane: PlaneArtifact | WallArtifact | CapArtifact
|
||||||
/** A path must always contain 0 or more segments */
|
/** A path must always contain 0 or more segments */
|
||||||
segments: Array<SegmentArtifact>
|
segments: Array<SegmentArtifact>
|
||||||
/** A path may not result in a sweep artifact */
|
/** A path may not result in a sweep artifact */
|
||||||
@ -51,7 +71,7 @@ export interface PathArtifactRich extends BaseArtifact {
|
|||||||
interface SegmentArtifactRich extends BaseArtifact {
|
interface SegmentArtifactRich extends BaseArtifact {
|
||||||
type: 'segment'
|
type: 'segment'
|
||||||
path: PathArtifact
|
path: PathArtifact
|
||||||
surf?: WallArtifact
|
surf: WallArtifact
|
||||||
edges: Array<SweepEdge>
|
edges: Array<SweepEdge>
|
||||||
edgeCut?: EdgeCut
|
edgeCut?: EdgeCut
|
||||||
codeRef: CodeRef
|
codeRef: CodeRef
|
||||||
@ -151,6 +171,73 @@ export function expandPlane(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function expandWall(
|
||||||
|
wall: WallArtifact,
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
|
): WallArtifactRich {
|
||||||
|
const { pathIds, sweepId: _s, edgeCutEdgeIds, ...keptProperties } = wall
|
||||||
|
const paths = pathIds?.length
|
||||||
|
? Array.from(
|
||||||
|
getArtifactsOfTypes(
|
||||||
|
{ keys: wall.pathIds, types: ['path'] },
|
||||||
|
artifactGraph
|
||||||
|
).values()
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
const sweep = artifactGraph.get(wall.sweepId) as SweepArtifact
|
||||||
|
const edgeCuts = edgeCutEdgeIds?.length
|
||||||
|
? Array.from(
|
||||||
|
getArtifactsOfTypes(
|
||||||
|
{ keys: wall.edgeCutEdgeIds, types: ['edgeCut'] },
|
||||||
|
artifactGraph
|
||||||
|
).values()
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
const segment = artifactGraph.get(wall.segId) as PathArtifact
|
||||||
|
return {
|
||||||
|
type: 'wall',
|
||||||
|
...keptProperties,
|
||||||
|
paths,
|
||||||
|
sweep,
|
||||||
|
segment,
|
||||||
|
edgeCuts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function expandCap(
|
||||||
|
cap: CapArtifact,
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
|
): CapArtifactRich {
|
||||||
|
const { pathIds, sweepId: _s, edgeCutEdgeIds, ...keptProperties } = cap
|
||||||
|
const paths = pathIds?.length
|
||||||
|
? Array.from(
|
||||||
|
getArtifactsOfTypes(
|
||||||
|
{ keys: cap.pathIds, types: ['path'] },
|
||||||
|
artifactGraph
|
||||||
|
).values()
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
const maybeSweep = getArtifactOfTypes(
|
||||||
|
{ key: cap.sweepId, types: ['sweep'] },
|
||||||
|
artifactGraph
|
||||||
|
)
|
||||||
|
const sweep = err(maybeSweep) ? undefined : maybeSweep
|
||||||
|
const edgeCuts = edgeCutEdgeIds?.length
|
||||||
|
? Array.from(
|
||||||
|
getArtifactsOfTypes(
|
||||||
|
{ keys: cap.edgeCutEdgeIds, types: ['edgeCut'] },
|
||||||
|
artifactGraph
|
||||||
|
).values()
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
return {
|
||||||
|
type: 'cap',
|
||||||
|
...keptProperties,
|
||||||
|
paths,
|
||||||
|
sweep,
|
||||||
|
edgeCuts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function expandPath(
|
export function expandPath(
|
||||||
path: PathArtifact,
|
path: PathArtifact,
|
||||||
artifactGraph: ArtifactGraph
|
artifactGraph: ArtifactGraph
|
||||||
@ -239,6 +326,7 @@ export function expandSegment(
|
|||||||
if (err(path)) return path
|
if (err(path)) return path
|
||||||
if (err(surf)) return surf
|
if (err(surf)) return surf
|
||||||
if (err(edgeCut)) return edgeCut
|
if (err(edgeCut)) return edgeCut
|
||||||
|
if (!surf) return new Error('Segment does not have a surface')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'segment',
|
type: 'segment',
|
||||||
@ -410,6 +498,186 @@ 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 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 === 'wall' || artifact.type === 'cap') return artifact
|
||||||
|
if (artifact.type === 'sweepEdge')
|
||||||
|
return getPlaneFromSweepEdge(artifact, graph)
|
||||||
|
return new Error(`Artifact type ${artifact.type} does not have a plane`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onlyConsecutivePaths = (
|
||||||
|
orderedNodePaths: PathToNode[],
|
||||||
|
originalPath: PathToNode,
|
||||||
|
ast: Program
|
||||||
|
): PathToNode[] => {
|
||||||
|
const isExprSafe = (index: number, ast: Program): boolean => {
|
||||||
|
// we allow expressions between profiles, but only basic math expressions 5 + 6 etc
|
||||||
|
// because 5 + doSomeMath() might be okay, but we can't know if it's an abstraction on a stdlib
|
||||||
|
// call that involves a engine call, and we can't have that in sketch-mode/mock-execution
|
||||||
|
const expr = 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 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, ast)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = originalIndex - 1; i >= minIndex; i--) {
|
||||||
|
if (pathIndexMap[i]) {
|
||||||
|
safePaths.unshift(pathIndexMap[i])
|
||||||
|
} else if (!isExprSafe(i, ast)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return safePaths
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPathsFromPlaneArtifact(
|
||||||
|
planeArtifact: PlaneArtifact,
|
||||||
|
artifactGraph: ArtifactGraph,
|
||||||
|
ast: Program
|
||||||
|
): PathToNode[] {
|
||||||
|
const nodePaths: PathToNode[] = []
|
||||||
|
for (const pathId of planeArtifact.pathIds) {
|
||||||
|
const path = 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(ast, path.codeRef.range)
|
||||||
|
: path.codeRef.pathToNode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return onlyConsecutivePaths(nodePaths, nodePaths[0], ast)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPathsFromArtifact({
|
||||||
|
sketchPathToNode,
|
||||||
|
artifact,
|
||||||
|
artifactGraph,
|
||||||
|
ast,
|
||||||
|
}: {
|
||||||
|
sketchPathToNode: PathToNode
|
||||||
|
artifact?: Artifact
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
|
ast: Program
|
||||||
|
}): PathToNode[] | Error {
|
||||||
|
const plane = getPlaneFromArtifact(artifact, artifactGraph)
|
||||||
|
if (err(plane)) return plane
|
||||||
|
const paths = getArtifactsOfTypes(
|
||||||
|
{ keys: plane.pathIds, types: ['path'] },
|
||||||
|
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, ast)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
* Get an artifact from a code source range
|
||||||
*/
|
*/
|
||||||
@ -418,12 +686,24 @@ export function getArtifactFromRange(
|
|||||||
artifactGraph: ArtifactGraph
|
artifactGraph: ArtifactGraph
|
||||||
): Artifact | null {
|
): Artifact | null {
|
||||||
for (const artifact of artifactGraph.values()) {
|
for (const artifact of artifactGraph.values()) {
|
||||||
if ('codeRef' in artifact) {
|
const codeRef = getFaceCodeRef(artifact)
|
||||||
|
if (codeRef) {
|
||||||
const match =
|
const match =
|
||||||
artifact.codeRef?.range[0] === range[0] &&
|
codeRef?.range[0] === range[0] && codeRef.range[1] === range[1]
|
||||||
artifact.codeRef.range[1] === range[1]
|
|
||||||
if (match) return artifact
|
if (match) return artifact
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFaceCodeRef(
|
||||||
|
artifact: Artifact | Plane | Wall | Cap
|
||||||
|
): CodeRef | null {
|
||||||
|
if ('faceCodeRef' in artifact) {
|
||||||
|
return artifact.faceCodeRef
|
||||||
|
}
|
||||||
|
if ('codeRef' in artifact) {
|
||||||
|
return artifact.codeRef
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 569 KiB After Width: | Height: | Size: 560 KiB |
@ -66,7 +66,12 @@ import { perpendicularDistance } from 'sketch-helpers'
|
|||||||
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
||||||
import { EdgeCutInfo } from 'machines/modelingMachine'
|
import { EdgeCutInfo } from 'machines/modelingMachine'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
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_TAG = 'tag'
|
||||||
export const ARG_END = 'end'
|
export const ARG_END = 'end'
|
||||||
@ -76,6 +81,9 @@ const STRAIGHT_SEGMENT_ERR = new Error(
|
|||||||
'Invalid input, expected "straight-segment"'
|
'Invalid input, expected "straight-segment"'
|
||||||
)
|
)
|
||||||
const ARC_SEGMENT_ERR = new Error('Invalid input, expected "arc-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]
|
export type Coords2d = [number, number]
|
||||||
|
|
||||||
@ -171,7 +179,8 @@ const commonConstraintInfoHelper = (
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
code: string,
|
code: string,
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode,
|
||||||
|
filterValue?: string
|
||||||
) => {
|
) => {
|
||||||
if (callExp.type !== 'CallExpression' && callExp.type !== 'CallExpressionKw')
|
if (callExp.type !== 'CallExpression' && callExp.type !== 'CallExpressionKw')
|
||||||
return []
|
return []
|
||||||
@ -295,7 +304,8 @@ const horzVertConstraintInfoHelper = (
|
|||||||
stdLibFnName: ConstrainInfo['stdLibFnName'],
|
stdLibFnName: ConstrainInfo['stdLibFnName'],
|
||||||
abbreviatedInput: AbbreviatedInput,
|
abbreviatedInput: AbbreviatedInput,
|
||||||
code: string,
|
code: string,
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode,
|
||||||
|
filterValue?: string
|
||||||
) => {
|
) => {
|
||||||
if (callExp.type !== 'CallExpression') return []
|
if (callExp.type !== 'CallExpression') return []
|
||||||
const firstArg = callExp.arguments?.[0]
|
const firstArg = callExp.arguments?.[0]
|
||||||
@ -502,13 +512,14 @@ export const lineTo: SketchLineHelperKw = {
|
|||||||
}) => {
|
}) => {
|
||||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||||
const to = segmentInput.to
|
const to = segmentInput.to
|
||||||
const _node = { ...node }
|
const _node = structuredClone(node)
|
||||||
const nodeMeta = getNodeFromPath<PipeExpression | CallExpressionKw>(
|
const nodeMeta = getNodeFromPath<PipeExpression | CallExpressionKw>(
|
||||||
_node,
|
_node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
'PipeExpression'
|
'PipeExpression'
|
||||||
)
|
)
|
||||||
if (err(nodeMeta)) return nodeMeta
|
if (err(nodeMeta)) return nodeMeta
|
||||||
|
|
||||||
const { node: pipe } = nodeMeta
|
const { node: pipe } = nodeMeta
|
||||||
const nodeMeta2 = getNodeFromPath<VariableDeclarator>(
|
const nodeMeta2 = getNodeFromPath<VariableDeclarator>(
|
||||||
_node,
|
_node,
|
||||||
@ -783,11 +794,11 @@ export const xLine: SketchLineHelper = {
|
|||||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||||
const { from, to } = segmentInput
|
const { from, to } = segmentInput
|
||||||
const _node = { ...node }
|
const _node = structuredClone(node)
|
||||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||||
const _node1 = getNode<PipeExpression>('PipeExpression')
|
const varDec = getNode<VariableDeclaration>('VariableDeclaration')
|
||||||
if (err(_node1)) return _node1
|
if (err(varDec)) return varDec
|
||||||
const { node: pipe } = _node1
|
const dec = varDec.node.declaration
|
||||||
|
|
||||||
const newVal = createLiteral(roundOff(to[0] - from[0], 2))
|
const newVal = createLiteral(roundOff(to[0] - from[0], 2))
|
||||||
|
|
||||||
@ -802,7 +813,11 @@ export const xLine: SketchLineHelper = {
|
|||||||
])
|
])
|
||||||
if (err(result)) return result
|
if (err(result)) return result
|
||||||
const { callExp, valueUsedInTransform } = result
|
const { callExp, valueUsedInTransform } = result
|
||||||
pipe.body[callIndex] = callExp
|
if (dec.init.type === 'PipeExpression') {
|
||||||
|
dec.init.body[callIndex] = callExp
|
||||||
|
} else {
|
||||||
|
dec.init = callExp
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -814,7 +829,11 @@ export const xLine: SketchLineHelper = {
|
|||||||
newVal,
|
newVal,
|
||||||
createPipeSubstitution(),
|
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 }
|
return { modifiedAst: _node, pathToNode }
|
||||||
},
|
},
|
||||||
updateArgs: ({ node, pathToNode, input }) => {
|
updateArgs: ({ node, pathToNode, input }) => {
|
||||||
@ -851,11 +870,11 @@ export const yLine: SketchLineHelper = {
|
|||||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||||
const { from, to } = segmentInput
|
const { from, to } = segmentInput
|
||||||
const _node = { ...node }
|
const _node = structuredClone(node)
|
||||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||||
const _node1 = getNode<PipeExpression>('PipeExpression')
|
const varDec = getNode<VariableDeclaration>('VariableDeclaration')
|
||||||
if (err(_node1)) return _node1
|
if (err(varDec)) return varDec
|
||||||
const { node: pipe } = _node1
|
const dec = varDec.node.declaration
|
||||||
const newVal = createLiteral(roundOff(to[1] - from[1], 2))
|
const newVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||||
if (replaceExistingCallback) {
|
if (replaceExistingCallback) {
|
||||||
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
||||||
@ -868,7 +887,11 @@ export const yLine: SketchLineHelper = {
|
|||||||
])
|
])
|
||||||
if (err(result)) return result
|
if (err(result)) return result
|
||||||
const { callExp, valueUsedInTransform } = result
|
const { callExp, valueUsedInTransform } = result
|
||||||
pipe.body[callIndex] = callExp
|
if (dec.init.type === 'PipeExpression') {
|
||||||
|
dec.init.body[callIndex] = callExp
|
||||||
|
} else {
|
||||||
|
dec.init = callExp
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -880,7 +903,11 @@ export const yLine: SketchLineHelper = {
|
|||||||
newVal,
|
newVal,
|
||||||
createPipeSubstitution(),
|
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 }
|
return { modifiedAst: _node, pathToNode }
|
||||||
},
|
},
|
||||||
updateArgs: ({ node, pathToNode, input }) => {
|
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('replaceExistingCallback is missing')
|
||||||
|
},
|
||||||
|
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 = {
|
export const angledLine: SketchLineHelper = {
|
||||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||||
@ -1984,6 +2300,7 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
|
|||||||
export const sketchLineHelperMapKw: { [key: string]: SketchLineHelperKw } = {
|
export const sketchLineHelperMapKw: { [key: string]: SketchLineHelperKw } = {
|
||||||
line,
|
line,
|
||||||
lineTo,
|
lineTo,
|
||||||
|
circleThreePoint,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export function changeSketchArguments(
|
export function changeSketchArguments(
|
||||||
@ -2051,30 +2368,36 @@ export function changeSketchArguments(
|
|||||||
export function getConstraintInfo(
|
export function getConstraintInfo(
|
||||||
callExpression: Node<CallExpression>,
|
callExpression: Node<CallExpression>,
|
||||||
code: string,
|
code: string,
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode,
|
||||||
|
filterValue?: string
|
||||||
): ConstrainInfo[] {
|
): ConstrainInfo[] {
|
||||||
const fnName = callExpression?.callee?.name || ''
|
const fnName = callExpression?.callee?.name || ''
|
||||||
if (!(fnName in sketchLineHelperMap)) return []
|
if (!(fnName in sketchLineHelperMap)) return []
|
||||||
return sketchLineHelperMap[fnName].getConstraintInfo(
|
return sketchLineHelperMap[fnName].getConstraintInfo(
|
||||||
callExpression,
|
callExpression,
|
||||||
code,
|
code,
|
||||||
pathToNode
|
pathToNode,
|
||||||
|
filterValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConstraintInfoKw(
|
export function getConstraintInfoKw(
|
||||||
callExpression: Node<CallExpressionKw>,
|
callExpression: Node<CallExpressionKw>,
|
||||||
code: string,
|
code: string,
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode,
|
||||||
|
filterValue?: string
|
||||||
): ConstrainInfo[] {
|
): ConstrainInfo[] {
|
||||||
const fnName = callExpression?.callee?.name || ''
|
const fnName = callExpression?.callee?.name || ''
|
||||||
const isAbsolute = findKwArg('endAbsolute', callExpression) !== undefined
|
const isAbsolute =
|
||||||
|
fnName === 'circleThreePoint' ||
|
||||||
|
findKwArg('endAbsolute', callExpression) !== undefined
|
||||||
if (!(fnName in sketchLineHelperMapKw)) return []
|
if (!(fnName in sketchLineHelperMapKw)) return []
|
||||||
const correctFnName = fnName === 'line' && isAbsolute ? 'lineTo' : fnName
|
const correctFnName = fnName === 'line' && isAbsolute ? 'lineTo' : fnName
|
||||||
return sketchLineHelperMapKw[correctFnName].getConstraintInfo(
|
return sketchLineHelperMapKw[correctFnName].getConstraintInfo(
|
||||||
callExpression,
|
callExpression,
|
||||||
code,
|
code,
|
||||||
pathToNode
|
pathToNode,
|
||||||
|
filterValue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2298,8 +2621,6 @@ function addTagToChamfer(
|
|||||||
if (err(variableDec)) return variableDec
|
if (err(variableDec)) return variableDec
|
||||||
const isPipeExpression = pipeExpr.node.type === 'PipeExpression'
|
const isPipeExpression = pipeExpr.node.type === 'PipeExpression'
|
||||||
|
|
||||||
console.log('pipeExpr', pipeExpr, variableDec)
|
|
||||||
// const callExpr = isPipeExpression ? pipeExpr.node.body[pipeIndex] : variableDec.node.init
|
|
||||||
const callExpr = isPipeExpression
|
const callExpr = isPipeExpression
|
||||||
? pipeExpr.node.body[pipeIndex]
|
? pipeExpr.node.body[pipeIndex]
|
||||||
: variableDec.node.init
|
: variableDec.node.init
|
||||||
@ -2380,7 +2701,6 @@ function addTagToChamfer(
|
|||||||
if (isPipeExpression) {
|
if (isPipeExpression) {
|
||||||
pipeExpr.node.body.splice(pipeIndex, 0, newExpressionToInsert)
|
pipeExpr.node.body.splice(pipeIndex, 0, newExpressionToInsert)
|
||||||
} else {
|
} else {
|
||||||
console.log('yo', createPipeExpression([newExpressionToInsert, callExpr]))
|
|
||||||
callExpr.arguments[1] = createPipeSubstitution()
|
callExpr.arguments[1] = createPipeSubstitution()
|
||||||
variableDec.node.init = createPipeExpression([
|
variableDec.node.init = createPipeExpression([
|
||||||
newExpressionToInsert,
|
newExpressionToInsert,
|
||||||
@ -2719,6 +3039,8 @@ export function isAbsoluteLine(lineCall: CallExpressionKw): boolean | Error {
|
|||||||
return new Error(
|
return new Error(
|
||||||
`line call has neither ${ARG_END} nor ${ARG_END_ABSOLUTE} params`
|
`line call has neither ${ARG_END} nor ${ARG_END_ABSOLUTE} params`
|
||||||
)
|
)
|
||||||
|
case 'circleThreePoint':
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return new Error(`Unknown sketch function ${name}`)
|
return new Error(`Unknown sketch function ${name}`)
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import {
|
|||||||
Literal,
|
Literal,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
LiteralValue,
|
LiteralValue,
|
||||||
recast,
|
|
||||||
LabeledArg,
|
LabeledArg,
|
||||||
VariableMap,
|
VariableMap,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
@ -217,14 +216,19 @@ function createStdlibCallExpressionKw(
|
|||||||
tool: ToolTip,
|
tool: ToolTip,
|
||||||
labeled: LabeledArg[],
|
labeled: LabeledArg[],
|
||||||
tag?: Expr,
|
tag?: Expr,
|
||||||
valueUsedInTransform?: number
|
valueUsedInTransform?: number,
|
||||||
|
unlabeled?: Expr
|
||||||
): CreatedSketchExprResult {
|
): CreatedSketchExprResult {
|
||||||
const args = labeled
|
const args = labeled
|
||||||
if (tag) {
|
if (tag) {
|
||||||
args.push(createLabeledArg(ARG_TAG, tag))
|
args.push(createLabeledArg(ARG_TAG, tag))
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
callExp: createCallExpressionStdLibKw(tool, null, args),
|
callExp: createCallExpressionStdLibKw(
|
||||||
|
tool,
|
||||||
|
unlabeled ? unlabeled : null,
|
||||||
|
args
|
||||||
|
),
|
||||||
valueUsedInTransform,
|
valueUsedInTransform,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1306,6 +1310,12 @@ export function getRemoveConstraintsTransform(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
sketchFnExp.type === 'CallExpressionKw' &&
|
||||||
|
sketchFnExp.callee.name === 'circleThreePoint'
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
const isAbsolute =
|
const isAbsolute =
|
||||||
// isAbsolute doesn't matter if the call is positional.
|
// isAbsolute doesn't matter if the call is positional.
|
||||||
sketchFnExp.type === 'CallExpression' ? false : isAbsoluteLine(sketchFnExp)
|
sketchFnExp.type === 'CallExpression' ? false : isAbsoluteLine(sketchFnExp)
|
||||||
@ -1320,7 +1330,6 @@ export function getRemoveConstraintsTransform(
|
|||||||
? getFirstArg(sketchFnExp)
|
? getFirstArg(sketchFnExp)
|
||||||
: getArgForEnd(sketchFnExp)
|
: getArgForEnd(sketchFnExp)
|
||||||
if (err(firstArg)) {
|
if (err(firstArg)) {
|
||||||
console.error(firstArg)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1351,7 +1360,7 @@ export function getRemoveConstraintsTransform(
|
|||||||
|
|
||||||
export function removeSingleConstraint({
|
export function removeSingleConstraint({
|
||||||
pathToCallExp,
|
pathToCallExp,
|
||||||
inputDetails,
|
inputDetails: inputToReplace,
|
||||||
ast,
|
ast,
|
||||||
}: {
|
}: {
|
||||||
pathToCallExp: PathToNode
|
pathToCallExp: PathToNode
|
||||||
@ -1384,12 +1393,12 @@ export function removeSingleConstraint({
|
|||||||
// So we should update the call expression to use the inputs, except for
|
// So we should update the call expression to use the inputs, except for
|
||||||
// the inputDetails, input where we should use the rawValue(s)
|
// the inputDetails, input where we should use the rawValue(s)
|
||||||
|
|
||||||
if (inputDetails.type === 'arrayItem') {
|
if (inputToReplace.type === 'arrayItem') {
|
||||||
const values = inputs.map((arg) => {
|
const values = inputs.map((arg) => {
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
(arg.type === 'arrayItem' || arg.type === 'arrayOrObjItem') &&
|
(arg.type === 'arrayItem' || arg.type === 'arrayOrObjItem') &&
|
||||||
arg.index === inputDetails.index
|
arg.index === inputToReplace.index
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return arg.expr
|
return arg.expr
|
||||||
@ -1397,9 +1406,9 @@ export function removeSingleConstraint({
|
|||||||
(rawValue) =>
|
(rawValue) =>
|
||||||
(rawValue.type === 'arrayItem' ||
|
(rawValue.type === 'arrayItem' ||
|
||||||
rawValue.type === 'arrayOrObjItem') &&
|
rawValue.type === 'arrayOrObjItem') &&
|
||||||
rawValue.index === inputDetails.index
|
rawValue.index === inputToReplace.index
|
||||||
)?.expr
|
)?.expr
|
||||||
return (arg.index === inputDetails.index && literal) || arg.expr
|
return (arg.index === inputToReplace.index && literal) || arg.expr
|
||||||
})
|
})
|
||||||
if (callExp.node.type === 'CallExpression') {
|
if (callExp.node.type === 'CallExpression') {
|
||||||
return createStdlibCallExpression(
|
return createStdlibCallExpression(
|
||||||
@ -1428,66 +1437,110 @@ export function removeSingleConstraint({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
inputDetails.type === 'arrayInObject' ||
|
inputToReplace.type === 'arrayInObject' ||
|
||||||
inputDetails.type === 'objectProperty'
|
inputToReplace.type === 'objectProperty'
|
||||||
) {
|
) {
|
||||||
const arrayDetailsNameBetterLater: {
|
const arrayInput: {
|
||||||
[key: string]: Parameters<typeof createArrayExpression>[0]
|
[key: string]: Parameters<typeof createArrayExpression>[0]
|
||||||
} = {}
|
} = {}
|
||||||
const otherThing: Parameters<typeof createObjectExpression>[0] = {}
|
const objInput: Parameters<typeof createObjectExpression>[0] = {}
|
||||||
inputs.forEach((arg) => {
|
const kwArgInput: ReturnType<typeof createLabeledArg>[] = []
|
||||||
|
inputs.forEach((currentArg) => {
|
||||||
if (
|
if (
|
||||||
arg.type !== 'objectProperty' &&
|
// should be one of these, return early to make TS happy.
|
||||||
arg.type !== 'arrayOrObjItem' &&
|
currentArg.type !== 'objectProperty' &&
|
||||||
arg.type !== 'arrayInObject'
|
currentArg.type !== 'arrayOrObjItem' &&
|
||||||
|
currentArg.type !== 'arrayInObject'
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
const rawLiteralArrayInObject = rawArgs.find(
|
const rawLiteralArrayInObject = rawArgs.find(
|
||||||
(rawValue) =>
|
(rawValue) =>
|
||||||
rawValue.type === 'arrayInObject' &&
|
rawValue.type === 'arrayInObject' &&
|
||||||
rawValue.key === inputDetails.key &&
|
rawValue.key === currentArg.key &&
|
||||||
rawValue.index === (arg.type === 'arrayInObject' ? arg.index : -1)
|
rawValue.index ===
|
||||||
|
(currentArg.type === 'arrayInObject' ? currentArg.index : -1)
|
||||||
)
|
)
|
||||||
const rawLiteralObjProp = rawArgs.find(
|
const rawLiteralObjProp = rawArgs.find(
|
||||||
(rawValue) =>
|
(rawValue) =>
|
||||||
(rawValue.type === 'objectProperty' ||
|
(rawValue.type === 'objectProperty' ||
|
||||||
rawValue.type === 'arrayOrObjItem' ||
|
rawValue.type === 'arrayOrObjItem' ||
|
||||||
rawValue.type === 'arrayInObject') &&
|
rawValue.type === 'arrayInObject') &&
|
||||||
rawValue.key === inputDetails.key
|
rawValue.key === inputToReplace.key
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
inputDetails.type === 'arrayInObject' &&
|
inputToReplace.type === 'arrayInObject' &&
|
||||||
rawLiteralArrayInObject?.type === 'arrayInObject' &&
|
rawLiteralArrayInObject?.type === 'arrayInObject' &&
|
||||||
rawLiteralArrayInObject?.index === inputDetails.index &&
|
rawLiteralArrayInObject?.index === inputToReplace.index &&
|
||||||
rawLiteralArrayInObject?.key === inputDetails.key
|
rawLiteralArrayInObject?.key === inputToReplace.key
|
||||||
) {
|
) {
|
||||||
if (!arrayDetailsNameBetterLater[arg.key])
|
if (!arrayInput[currentArg.key]) {
|
||||||
arrayDetailsNameBetterLater[arg.key] = []
|
arrayInput[currentArg.key] = []
|
||||||
arrayDetailsNameBetterLater[inputDetails.key][inputDetails.index] =
|
}
|
||||||
|
arrayInput[inputToReplace.key][inputToReplace.index] =
|
||||||
rawLiteralArrayInObject.expr
|
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 (
|
} else if (
|
||||||
inputDetails.type === 'objectProperty' &&
|
inputToReplace.type === 'objectProperty' &&
|
||||||
(rawLiteralObjProp?.type === 'objectProperty' ||
|
(rawLiteralObjProp?.type === 'objectProperty' ||
|
||||||
rawLiteralObjProp?.type === 'arrayOrObjItem') &&
|
rawLiteralObjProp?.type === 'arrayOrObjItem') &&
|
||||||
rawLiteralObjProp?.key === inputDetails.key &&
|
rawLiteralObjProp?.key === inputToReplace.key &&
|
||||||
arg.key === inputDetails.key
|
currentArg.key === inputToReplace.key
|
||||||
) {
|
) {
|
||||||
otherThing[inputDetails.key] = rawLiteralObjProp.expr
|
objInput[inputToReplace.key] = rawLiteralObjProp.expr
|
||||||
} else if (arg.type === 'arrayInObject') {
|
} else if (currentArg.type === 'arrayInObject') {
|
||||||
if (!arrayDetailsNameBetterLater[arg.key])
|
if (!arrayInput[currentArg.key]) arrayInput[currentArg.key] = []
|
||||||
arrayDetailsNameBetterLater[arg.key] = []
|
arrayInput[currentArg.key][currentArg.index] = currentArg.expr
|
||||||
arrayDetailsNameBetterLater[arg.key][arg.index] = arg.expr
|
let existingKwgForKey = kwArgInput.find(
|
||||||
} else if (arg.type === 'objectProperty') {
|
(kwArg) => kwArg.label.name === currentArg.key
|
||||||
otherThing[arg.key] = arg.expr
|
)
|
||||||
|
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] = {}
|
const createObjParam: Parameters<typeof createObjectExpression>[0] = {}
|
||||||
Object.entries(arrayDetailsNameBetterLater).forEach(([key, value]) => {
|
Object.entries(arrayInput).forEach(([key, value]) => {
|
||||||
createObjParam[key] = createArrayExpression(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({
|
const objExp = createObjectExpression({
|
||||||
...createObjParam,
|
...createObjParam,
|
||||||
...otherThing,
|
...objInput,
|
||||||
})
|
})
|
||||||
return createStdlibCallExpression(
|
return createStdlibCallExpression(
|
||||||
callExp.node.callee.name as any,
|
callExp.node.callee.name as any,
|
||||||
@ -1571,6 +1624,16 @@ function getTransformMapPathKw(
|
|||||||
}
|
}
|
||||||
| false {
|
| false {
|
||||||
const name = sketchFnExp.callee.name as ToolTip
|
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 isAbsolute = findKwArg(ARG_END_ABSOLUTE, sketchFnExp) !== undefined
|
||||||
const nameAbsolute = name === 'line' ? 'lineTo' : name
|
const nameAbsolute = name === 'line' ? 'lineTo' : name
|
||||||
if (!toolTips.includes(name)) {
|
if (!toolTips.includes(name)) {
|
||||||
@ -1989,6 +2052,13 @@ export function transformAstSketchLines({
|
|||||||
radius: seg.radius,
|
radius: seg.radius,
|
||||||
from,
|
from,
|
||||||
}
|
}
|
||||||
|
: seg.type === 'CircleThreePoint'
|
||||||
|
? {
|
||||||
|
type: 'circle-three-point-segment',
|
||||||
|
p1: seg.p1,
|
||||||
|
p2: seg.p2,
|
||||||
|
p3: seg.p3,
|
||||||
|
}
|
||||||
: {
|
: {
|
||||||
type: 'straight-segment',
|
type: 'straight-segment',
|
||||||
to,
|
to,
|
||||||
|
@ -45,6 +45,13 @@ interface ArcSegmentInput {
|
|||||||
center: [number, number]
|
center: [number, number]
|
||||||
radius: 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.
|
* 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).
|
* - 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.
|
* - 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.
|
* Interface for adding or replacing a sketch stblib call expression to a sketch.
|
||||||
@ -85,6 +95,9 @@ export type InputArgKeys =
|
|||||||
| 'intersectTag'
|
| 'intersectTag'
|
||||||
| 'radius'
|
| 'radius'
|
||||||
| 'center'
|
| 'center'
|
||||||
|
| 'p1'
|
||||||
|
| 'p2'
|
||||||
|
| 'p3'
|
||||||
export interface SingleValueInput<T> {
|
export interface SingleValueInput<T> {
|
||||||
type: 'singleValue'
|
type: 'singleValue'
|
||||||
argType: LineInputsType
|
argType: LineInputsType
|
||||||
@ -239,7 +252,8 @@ export interface SketchLineHelper {
|
|||||||
getConstraintInfo: (
|
getConstraintInfo: (
|
||||||
callExp: Node<CallExpression>,
|
callExp: Node<CallExpression>,
|
||||||
code: string,
|
code: string,
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode,
|
||||||
|
filterValue?: string
|
||||||
) => ConstrainInfo[]
|
) => ConstrainInfo[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,6 +281,7 @@ export interface SketchLineHelperKw {
|
|||||||
getConstraintInfo: (
|
getConstraintInfo: (
|
||||||
callExp: Node<CallExpressionKw>,
|
callExp: Node<CallExpressionKw>,
|
||||||
code: string,
|
code: string,
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode,
|
||||||
|
filterValue?: string
|
||||||
) => ConstrainInfo[]
|
) => ConstrainInfo[]
|
||||||
}
|
}
|
||||||
|
@ -11,23 +11,50 @@ import {
|
|||||||
LiteralValue,
|
LiteralValue,
|
||||||
NumericSuffix,
|
NumericSuffix,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { filterArtifacts } from 'lang/std/artifactGraph'
|
import { filterArtifacts, getFaceCodeRef } from 'lang/std/artifactGraph'
|
||||||
import { isArray, isOverlap } from 'lib/utils'
|
import { isArray, isOverlap } from 'lib/utils'
|
||||||
|
|
||||||
export function updatePathToNodeFromMap(
|
/**
|
||||||
oldPath: PathToNode,
|
* Updates pathToNode body indices to account for the insertion of an expression
|
||||||
pathToNodeMap: { [key: number]: PathToNode }
|
* 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 {
|
): PathToNode {
|
||||||
const updatedPathToNode = structuredClone(oldPath)
|
if (exprInsertIndex < 0) return pathToNode
|
||||||
let max = 0
|
const bodyIndex = Number(pathToNode[1][0])
|
||||||
Object.values(pathToNodeMap).forEach((path) => {
|
if (bodyIndex < exprInsertIndex) return pathToNode
|
||||||
const index = Number(path[1][0])
|
const clone = structuredClone(pathToNode)
|
||||||
if (index > max) {
|
clone[1][0] = bodyIndex + 1
|
||||||
max = index
|
return clone
|
||||||
}
|
}
|
||||||
})
|
|
||||||
updatedPathToNode[1][0] = max
|
export function updateSketchDetailsNodePaths({
|
||||||
return updatedPathToNode
|
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(
|
export function isCursorInSketchCommandRange(
|
||||||
@ -36,20 +63,30 @@ export function isCursorInSketchCommandRange(
|
|||||||
): string | false {
|
): string | false {
|
||||||
const overlappingEntries = filterArtifacts(
|
const overlappingEntries = filterArtifacts(
|
||||||
{
|
{
|
||||||
types: ['segment', 'path'],
|
types: ['segment', 'path', 'plane', 'cap', 'wall'],
|
||||||
predicate: (artifact) => {
|
predicate: (artifact) => {
|
||||||
|
const codeRefRange = getFaceCodeRef(artifact)?.range
|
||||||
return selectionRanges.graphSelections.some(
|
return selectionRanges.graphSelections.some(
|
||||||
(selection) =>
|
(selection) =>
|
||||||
isArray(selection?.codeRef?.range) &&
|
isArray(selection?.codeRef?.range) &&
|
||||||
isArray(artifact?.codeRef?.range) &&
|
isArray(codeRefRange) &&
|
||||||
isOverlap(selection?.codeRef?.range, artifact.codeRef.range)
|
isOverlap(selection?.codeRef?.range, codeRefRange)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
artifactGraph
|
artifactGraph
|
||||||
)
|
)
|
||||||
const firstEntry = [...overlappingEntries.values()]?.[0]
|
const firstEntry = [...overlappingEntries.values()]?.[0]
|
||||||
const parentId = firstEntry?.type === 'segment' ? firstEntry.pathId : false
|
const parentId =
|
||||||
|
firstEntry?.type === 'segment'
|
||||||
|
? firstEntry.pathId
|
||||||
|
: ((firstEntry?.type === 'plane' ||
|
||||||
|
firstEntry?.type === 'cap' ||
|
||||||
|
firstEntry?.type === 'wall') &&
|
||||||
|
firstEntry.pathIds?.length) ||
|
||||||
|
false
|
||||||
|
? firstEntry.pathIds[0]
|
||||||
|
: false
|
||||||
|
|
||||||
return parentId
|
return parentId
|
||||||
? parentId
|
? parentId
|
||||||
@ -81,11 +118,27 @@ export function findKwArg(
|
|||||||
label: string,
|
label: string,
|
||||||
call: CallExpressionKw
|
call: CallExpressionKw
|
||||||
): Expr | undefined {
|
): Expr | undefined {
|
||||||
return call.arguments.find((arg) => {
|
return call?.arguments?.find((arg) => {
|
||||||
return arg.label.name === label
|
return arg.label.name === label
|
||||||
})?.arg
|
})?.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.
|
Search the keyword arguments from a call for an argument with one of these labels.
|
||||||
*/
|
*/
|
||||||
|
@ -58,7 +58,7 @@ export type ModelingCommandSchema = {
|
|||||||
Revolve: {
|
Revolve: {
|
||||||
selection: Selections
|
selection: Selections
|
||||||
angle: KclCommandValue
|
angle: KclCommandValue
|
||||||
axisOrEdge: string
|
axisOrEdge: 'Axis' | 'Edge'
|
||||||
axis: string
|
axis: string
|
||||||
edge: Selections
|
edge: Selections
|
||||||
}
|
}
|
||||||
|
@ -75,9 +75,9 @@ segAng(rectangleSegmentA001),
|
|||||||
|
|
||||||
// ast is edited in place from the updateCenterRectangleSketch
|
// ast is edited in place from the updateCenterRectangleSketch
|
||||||
const expectedSourceCode = `sketch001 = startSketchOn('XZ')
|
const expectedSourceCode = `sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([80, 120], %)
|
|> startProfileAt([120.37, 80], %)
|
||||||
|> angledLine([0, 80], %, $rectangleSegmentA001)
|
|> angledLine([0, 0], %, $rectangleSegmentA001)
|
||||||
|> angledLine([segAng(rectangleSegmentA001) + 90, 120], %, $rectangleSegmentB001)
|
|> angledLine([segAng(rectangleSegmentA001) + 90, 0], %, $rectangleSegmentB001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001),
|
segAng(rectangleSegmentA001),
|
||||||
-segLen(rectangleSegmentA001)
|
-segLen(rectangleSegmentA001)
|
||||||
|
@ -37,7 +37,7 @@ import {
|
|||||||
*/
|
*/
|
||||||
export const getRectangleCallExpressions = (
|
export const getRectangleCallExpressions = (
|
||||||
rectangleOrigin: [number, number],
|
rectangleOrigin: [number, number],
|
||||||
tags: [string, string, string]
|
tag: string
|
||||||
) => [
|
) => [
|
||||||
createCallExpressionStdLib('angledLine', [
|
createCallExpressionStdLib('angledLine', [
|
||||||
createArrayExpression([
|
createArrayExpression([
|
||||||
@ -45,30 +45,28 @@ export const getRectangleCallExpressions = (
|
|||||||
createLiteral(0), // This will be the width of the rectangle
|
createLiteral(0), // This will be the width of the rectangle
|
||||||
]),
|
]),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
createTagDeclarator(tags[0]),
|
createTagDeclarator(tag),
|
||||||
]),
|
]),
|
||||||
createCallExpressionStdLib('angledLine', [
|
createCallExpressionStdLib('angledLine', [
|
||||||
createArrayExpression([
|
createArrayExpression([
|
||||||
createBinaryExpression([
|
createBinaryExpression([
|
||||||
createCallExpressionStdLib('segAng', [createIdentifier(tags[0])]),
|
createCallExpressionStdLib('segAng', [createIdentifier(tag)]),
|
||||||
'+',
|
'+',
|
||||||
createLiteral(90),
|
createLiteral(90),
|
||||||
]), // 90 offset from the previous line
|
]), // 90 offset from the previous line
|
||||||
createLiteral(0), // This will be the height of the rectangle
|
createLiteral(0), // This will be the height of the rectangle
|
||||||
]),
|
]),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
createTagDeclarator(tags[1]),
|
|
||||||
]),
|
]),
|
||||||
createCallExpressionStdLib('angledLine', [
|
createCallExpressionStdLib('angledLine', [
|
||||||
createArrayExpression([
|
createArrayExpression([
|
||||||
createCallExpressionStdLib('segAng', [createIdentifier(tags[0])]), // same angle as the first line
|
createCallExpressionStdLib('segAng', [createIdentifier(tag)]), // same angle as the first line
|
||||||
createUnaryExpression(
|
createUnaryExpression(
|
||||||
createCallExpressionStdLib('segLen', [createIdentifier(tags[0])]),
|
createCallExpressionStdLib('segLen', [createIdentifier(tag)]),
|
||||||
'-'
|
'-'
|
||||||
), // negative height
|
), // negative height
|
||||||
]),
|
]),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
createTagDeclarator(tags[2]),
|
|
||||||
]),
|
]),
|
||||||
createCallExpressionStdLibKw('line', null, [
|
createCallExpressionStdLibKw('line', null, [
|
||||||
createLabeledArg(
|
createLabeledArg(
|
||||||
@ -95,12 +93,12 @@ export function updateRectangleSketch(
|
|||||||
y: number,
|
y: number,
|
||||||
tag: string
|
tag: string
|
||||||
) {
|
) {
|
||||||
;((pipeExpression.body[2] as CallExpression)
|
;((pipeExpression.body[1] as CallExpression)
|
||||||
.arguments[0] as ArrayExpression) = createArrayExpression([
|
.arguments[0] as ArrayExpression) = createArrayExpression([
|
||||||
createLiteral(x >= 0 ? 0 : 180),
|
createLiteral(x >= 0 ? 0 : 180),
|
||||||
createLiteral(Math.abs(x)),
|
createLiteral(Math.abs(x)),
|
||||||
])
|
])
|
||||||
;((pipeExpression.body[3] as CallExpression)
|
;((pipeExpression.body[2] as CallExpression)
|
||||||
.arguments[0] as ArrayExpression) = createArrayExpression([
|
.arguments[0] as ArrayExpression) = createArrayExpression([
|
||||||
createBinaryExpression([
|
createBinaryExpression([
|
||||||
createCallExpressionStdLib('segAng', [createIdentifier(tag)]),
|
createCallExpressionStdLib('segAng', [createIdentifier(tag)]),
|
||||||
@ -129,8 +127,7 @@ export function updateCenterRectangleSketch(
|
|||||||
let startX = originX - Math.abs(deltaX)
|
let startX = originX - Math.abs(deltaX)
|
||||||
let startY = originY - Math.abs(deltaY)
|
let startY = originY - Math.abs(deltaY)
|
||||||
|
|
||||||
// pipeExpression.body[1] is startProfileAt
|
let callExpression = pipeExpression.body[0]
|
||||||
let callExpression = pipeExpression.body[1]
|
|
||||||
if (isCallExpression(callExpression)) {
|
if (isCallExpression(callExpression)) {
|
||||||
const arrayExpression = callExpression.arguments[0]
|
const arrayExpression = callExpression.arguments[0]
|
||||||
if (isArrayExpression(arrayExpression)) {
|
if (isArrayExpression(arrayExpression)) {
|
||||||
@ -144,7 +141,7 @@ export function updateCenterRectangleSketch(
|
|||||||
const twoX = deltaX * 2
|
const twoX = deltaX * 2
|
||||||
const twoY = deltaY * 2
|
const twoY = deltaY * 2
|
||||||
|
|
||||||
callExpression = pipeExpression.body[2]
|
callExpression = pipeExpression.body[1]
|
||||||
if (isCallExpression(callExpression)) {
|
if (isCallExpression(callExpression)) {
|
||||||
const arrayExpression = callExpression.arguments[0]
|
const arrayExpression = callExpression.arguments[0]
|
||||||
if (isArrayExpression(arrayExpression)) {
|
if (isArrayExpression(arrayExpression)) {
|
||||||
@ -160,7 +157,7 @@ export function updateCenterRectangleSketch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
callExpression = pipeExpression.body[3]
|
callExpression = pipeExpression.body[2]
|
||||||
if (isCallExpression(callExpression)) {
|
if (isCallExpression(callExpression)) {
|
||||||
const arrayExpression = callExpression.arguments[0]
|
const arrayExpression = callExpression.arguments[0]
|
||||||
if (isArrayExpression(arrayExpression)) {
|
if (isArrayExpression(arrayExpression)) {
|
||||||
|
@ -40,6 +40,7 @@ import {
|
|||||||
CodeRef,
|
CodeRef,
|
||||||
getCodeRefsByArtifactId,
|
getCodeRefsByArtifactId,
|
||||||
ArtifactId,
|
ArtifactId,
|
||||||
|
getFaceCodeRef,
|
||||||
} from 'lang/std/artifactGraph'
|
} from 'lang/std/artifactGraph'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { DefaultPlaneStr } from './planes'
|
import { DefaultPlaneStr } from './planes'
|
||||||
@ -276,18 +277,19 @@ export function getEventForSegmentSelection(
|
|||||||
}
|
}
|
||||||
if (!id || !group) return null
|
if (!id || !group) return null
|
||||||
const artifact = engineCommandManager.artifactGraph.get(id)
|
const artifact = engineCommandManager.artifactGraph.get(id)
|
||||||
const codeRefs = getCodeRefsByArtifactId(
|
if (!artifact) return null
|
||||||
id,
|
const node = getNodeFromPath<Expr>(kclManager.ast, group.userData.pathToNode)
|
||||||
engineCommandManager.artifactGraph
|
if (err(node)) return null
|
||||||
)
|
|
||||||
if (!artifact || !codeRefs) return null
|
|
||||||
return {
|
return {
|
||||||
type: 'Set selection',
|
type: 'Set selection',
|
||||||
data: {
|
data: {
|
||||||
selectionType: 'singleCodeCursor',
|
selectionType: 'singleCodeCursor',
|
||||||
selection: {
|
selection: {
|
||||||
artifact,
|
artifact,
|
||||||
codeRef: codeRefs[0],
|
codeRef: {
|
||||||
|
pathToNode: group?.userData?.pathToNode,
|
||||||
|
range: [node.node.start, node.node.end, 0],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -572,8 +574,7 @@ export function getSelectionTypeDisplayText(
|
|||||||
const selectionsByType = getSelectionCountByType(selection)
|
const selectionsByType = getSelectionCountByType(selection)
|
||||||
if (selectionsByType === 'none') return null
|
if (selectionsByType === 'none') return null
|
||||||
|
|
||||||
return selectionsByType
|
return [...selectionsByType.entries()]
|
||||||
.entries()
|
|
||||||
.map(
|
.map(
|
||||||
// Hack for showing "face" instead of "extrude-wall" in command bar text
|
// Hack for showing "face" instead of "extrude-wall" in command bar text
|
||||||
([type, count]) =>
|
([type, count]) =>
|
||||||
@ -581,7 +582,6 @@ export function getSelectionTypeDisplayText(
|
|||||||
count > 1 ? 's' : ''
|
count > 1 ? 's' : ''
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
.toArray()
|
|
||||||
.join(', ')
|
.join(', ')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -591,7 +591,7 @@ export function canSubmitSelectionArg(
|
|||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
selectionsByType !== 'none' &&
|
selectionsByType !== 'none' &&
|
||||||
selectionsByType.entries().every(([type, count]) => {
|
[...selectionsByType.entries()].every(([type, count]) => {
|
||||||
const foundIndex = argument.selectionTypes.findIndex((s) => s === type)
|
const foundIndex = argument.selectionTypes.findIndex((s) => s === type)
|
||||||
return (
|
return (
|
||||||
foundIndex !== -1 &&
|
foundIndex !== -1 &&
|
||||||
@ -614,8 +614,9 @@ export function codeToIdSelections(
|
|||||||
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
||||||
const overlappingEntries = Array.from(engineCommandManager.artifactGraph)
|
const overlappingEntries = Array.from(engineCommandManager.artifactGraph)
|
||||||
.map(([id, artifact]) => {
|
.map(([id, artifact]) => {
|
||||||
if (!('codeRef' in artifact && artifact.codeRef)) return null
|
const codeRef = getFaceCodeRef(artifact)
|
||||||
return isOverlap(artifact.codeRef.range, selection.range)
|
if (!codeRef) return null
|
||||||
|
return isOverlap(codeRef.range, selection.range)
|
||||||
? {
|
? {
|
||||||
artifact,
|
artifact,
|
||||||
selection,
|
selection,
|
||||||
@ -672,6 +673,27 @@ export function codeToIdSelections(
|
|||||||
id: entry.artifact.solid2dId,
|
id: entry.artifact.solid2dId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (entry.artifact.type === 'plane') {
|
||||||
|
bestCandidate = {
|
||||||
|
artifact: entry.artifact,
|
||||||
|
selection,
|
||||||
|
id: entry.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entry.artifact.type === 'cap') {
|
||||||
|
bestCandidate = {
|
||||||
|
artifact: entry.artifact,
|
||||||
|
selection,
|
||||||
|
id: entry.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entry.artifact.type === 'wall') {
|
||||||
|
bestCandidate = {
|
||||||
|
artifact: entry.artifact,
|
||||||
|
selection,
|
||||||
|
id: entry.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
if (type === 'extrude-wall' && entry.artifact.type === 'segment') {
|
if (type === 'extrude-wall' && entry.artifact.type === 'segment') {
|
||||||
if (!entry.artifact.surfaceId) return
|
if (!entry.artifact.surfaceId) return
|
||||||
const wall = engineCommandManager.artifactGraph.get(
|
const wall = engineCommandManager.artifactGraph.get(
|
||||||
@ -867,7 +889,6 @@ export function updateSelections(
|
|||||||
JSON.stringify(pathToNode)
|
JSON.stringify(pathToNode)
|
||||||
) {
|
) {
|
||||||
artifact = a
|
artifact = a
|
||||||
console.log('found artifact', a)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { CustomIconName } from 'components/CustomIcon'
|
import { CustomIconName } from 'components/CustomIcon'
|
||||||
import { DEV } from 'env'
|
import { DEV } from 'env'
|
||||||
import { commandBarActor, commandBarMachine } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
import {
|
import {
|
||||||
canRectangleOrCircleTool,
|
|
||||||
isClosedSketch,
|
|
||||||
isEditingExistingSketch,
|
isEditingExistingSketch,
|
||||||
modelingMachine,
|
modelingMachine,
|
||||||
pipeHasCircle,
|
pipeHasCircle,
|
||||||
@ -72,7 +70,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
icon: 'sketch',
|
icon: 'sketch',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
title: ({ sketchPathId }) =>
|
title: ({ sketchPathId }) =>
|
||||||
`${sketchPathId ? 'Edit' : 'Start'} Sketch`,
|
sketchPathId ? 'Edit Sketch' : 'Start Sketch',
|
||||||
showTitle: true,
|
showTitle: true,
|
||||||
hotkey: 'S',
|
hotkey: 'S',
|
||||||
description: 'Start drawing a 2D sketch',
|
description: 'Start drawing a 2D sketch',
|
||||||
@ -366,22 +364,14 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
{
|
{
|
||||||
id: 'line',
|
id: 'line',
|
||||||
onClick: ({ modelingState, modelingSend }) => {
|
onClick: ({ modelingState, modelingSend }) => {
|
||||||
if (modelingState.matches({ Sketch: { 'Line tool': 'No Points' } })) {
|
modelingSend({
|
||||||
// Exit the sketch state if there are no points and they press ESC
|
type: 'change tool',
|
||||||
modelingSend({
|
data: {
|
||||||
type: 'Cancel',
|
tool: !modelingState.matches({ Sketch: 'Line tool' })
|
||||||
})
|
? 'line'
|
||||||
} else {
|
: 'none',
|
||||||
// Exit the tool if there are points and they press ESC
|
},
|
||||||
modelingSend({
|
})
|
||||||
type: 'change tool',
|
|
||||||
data: {
|
|
||||||
tool: !modelingState.matches({ Sketch: 'Line tool' })
|
|
||||||
? 'line'
|
|
||||||
: 'none',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
icon: 'line',
|
icon: 'line',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
@ -392,8 +382,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
}) ||
|
}) ||
|
||||||
state.matches({
|
state.matches({
|
||||||
Sketch: { 'Circle tool': 'Awaiting Radius' },
|
Sketch: { 'Circle tool': 'Awaiting Radius' },
|
||||||
}) ||
|
}),
|
||||||
isClosedSketch(state.context),
|
|
||||||
title: 'Line',
|
title: 'Line',
|
||||||
hotkey: (state) =>
|
hotkey: (state) =>
|
||||||
state.matches({ Sketch: 'Line tool' }) ? ['Esc', 'L'] : 'L',
|
state.matches({ Sketch: 'Line tool' }) ? ['Esc', 'L'] : 'L',
|
||||||
@ -473,14 +462,10 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
icon: 'circle',
|
icon: 'circle',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
title: 'Center circle',
|
title: 'Center circle',
|
||||||
disabled: (state) =>
|
disabled: (state) => state.matches('Sketch no face'),
|
||||||
state.matches('Sketch no face') ||
|
|
||||||
(!canRectangleOrCircleTool(state.context) &&
|
|
||||||
!state.matches({ Sketch: 'Circle tool' }) &&
|
|
||||||
!state.matches({ Sketch: 'circle3PointToolSelect' })),
|
|
||||||
isActive: (state) =>
|
isActive: (state) =>
|
||||||
state.matches({ Sketch: 'Circle tool' }) ||
|
state.matches({ Sketch: 'Circle tool' }) ||
|
||||||
state.matches({ Sketch: 'circle3PointToolSelect' }),
|
state.matches({ Sketch: 'Circle three point tool' }),
|
||||||
hotkey: (state) =>
|
hotkey: (state) =>
|
||||||
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
|
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
|
||||||
showTitle: false,
|
showTitle: false,
|
||||||
@ -494,9 +479,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: {
|
data: {
|
||||||
tool: !modelingState.matches({
|
tool: !modelingState.matches({
|
||||||
Sketch: 'circle3PointToolSelect',
|
Sketch: 'Circle three point tool',
|
||||||
})
|
})
|
||||||
? 'circle3Points'
|
? 'circleThreePointNeo'
|
||||||
: 'none',
|
: 'none',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -522,10 +507,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
}),
|
}),
|
||||||
icon: 'rectangle',
|
icon: 'rectangle',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
disabled: (state) =>
|
disabled: (state) => state.matches('Sketch no face'),
|
||||||
state.matches('Sketch no face') ||
|
|
||||||
(!canRectangleOrCircleTool(state.context) &&
|
|
||||||
!state.matches({ Sketch: 'Rectangle tool' })),
|
|
||||||
title: 'Corner rectangle',
|
title: 'Corner rectangle',
|
||||||
hotkey: (state) =>
|
hotkey: (state) =>
|
||||||
state.matches({ Sketch: 'Rectangle tool' }) ? ['Esc', 'R'] : 'R',
|
state.matches({ Sketch: 'Rectangle tool' }) ? ['Esc', 'R'] : 'R',
|
||||||
@ -548,10 +530,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
}),
|
}),
|
||||||
icon: 'arc',
|
icon: 'arc',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
disabled: (state) =>
|
disabled: (state) => state.matches('Sketch no face'),
|
||||||
state.matches('Sketch no face') ||
|
|
||||||
(!canRectangleOrCircleTool(state.context) &&
|
|
||||||
!state.matches({ Sketch: 'Center Rectangle tool' })),
|
|
||||||
title: 'Center rectangle',
|
title: 'Center rectangle',
|
||||||
hotkey: (state) =>
|
hotkey: (state) =>
|
||||||
state.matches({ Sketch: 'Center Rectangle tool' })
|
state.matches({ Sketch: 'Center Rectangle tool' })
|
||||||
|
@ -97,3 +97,7 @@ export function trap<T>(
|
|||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function reject(errOrString: Error | string): Promise<never> {
|
||||||
|
return Promise.reject(errOrString)
|
||||||
|
}
|
||||||
|
@ -188,6 +188,9 @@ pub struct Wall {
|
|||||||
pub sweep_id: ArtifactId,
|
pub sweep_id: ArtifactId,
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub path_ids: Vec<ArtifactId>,
|
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)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||||
@ -201,6 +204,9 @@ pub struct Cap {
|
|||||||
pub sweep_id: ArtifactId,
|
pub sweep_id: ArtifactId,
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub path_ids: Vec<ArtifactId>,
|
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)]
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||||
@ -584,7 +590,7 @@ fn artifacts_to_update(
|
|||||||
responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
|
responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
|
||||||
current_plane_id: Option<Uuid>,
|
current_plane_id: Option<Uuid>,
|
||||||
_ast: &Node<Program>,
|
_ast: &Node<Program>,
|
||||||
_exec_artifacts: &IndexMap<ArtifactId, Artifact>,
|
exec_artifacts: &IndexMap<ArtifactId, Artifact>,
|
||||||
) -> Result<Vec<Artifact>, KclError> {
|
) -> Result<Vec<Artifact>, KclError> {
|
||||||
// TODO: Build path-to-node from artifact_command source range. Right now,
|
// TODO: Build path-to-node from artifact_command source range. Right now,
|
||||||
// we're serializing an empty array, and the TS wrapper fills it in with the
|
// we're serializing an empty array, and the TS wrapper fills it in with the
|
||||||
@ -634,6 +640,17 @@ fn artifacts_to_update(
|
|||||||
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
|
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
|
||||||
sweep_id: wall.sweep_id,
|
sweep_id: wall.sweep_id,
|
||||||
path_ids: wall.path_ids.clone(),
|
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 => {
|
Some(_) | None => {
|
||||||
@ -683,6 +700,17 @@ fn artifacts_to_update(
|
|||||||
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
|
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
|
||||||
sweep_id: wall.sweep_id,
|
sweep_id: wall.sweep_id,
|
||||||
path_ids: vec![id],
|
path_ids: vec![id],
|
||||||
|
face_code_ref: wall.face_code_ref.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if let Some(Artifact::Cap(cap)) = plane {
|
||||||
|
return_arr.push(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: vec![id],
|
||||||
|
face_code_ref: cap.face_code_ref.clone(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return Ok(return_arr);
|
return Ok(return_arr);
|
||||||
@ -809,12 +837,31 @@ fn artifacts_to_update(
|
|||||||
source_ranges: vec![range],
|
source_ranges: vec![range],
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
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),
|
||||||
|
// TODO: If we didn't find it, it's probably a bug.
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
return_arr.push(Artifact::Wall(Wall {
|
return_arr.push(Artifact::Wall(Wall {
|
||||||
id: face_id,
|
id: face_id,
|
||||||
seg_id: curve_id,
|
seg_id: curve_id,
|
||||||
edge_cut_edge_ids: Vec::new(),
|
edge_cut_edge_ids: Vec::new(),
|
||||||
sweep_id: path_sweep_id,
|
sweep_id: path_sweep_id,
|
||||||
path_ids: vec![],
|
path_ids: Vec::new(),
|
||||||
|
face_code_ref: CodeRef {
|
||||||
|
range: sketch_on_face_source_range,
|
||||||
|
path_to_node: Vec::new(),
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
let mut new_seg = seg.clone();
|
let mut new_seg = seg.clone();
|
||||||
new_seg.surface_id = Some(face_id);
|
new_seg.surface_id = Some(face_id);
|
||||||
@ -843,12 +890,29 @@ fn artifacts_to_update(
|
|||||||
source_ranges: vec![range],
|
source_ranges: vec![range],
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
return_arr.push(Artifact::Cap(Cap {
|
return_arr.push(Artifact::Cap(Cap {
|
||||||
id: face_id,
|
id: face_id,
|
||||||
sub_type,
|
sub_type,
|
||||||
edge_cut_edge_ids: Vec::new(),
|
edge_cut_edge_ids: Vec::new(),
|
||||||
sweep_id: path_sweep_id,
|
sweep_id: path_sweep_id,
|
||||||
path_ids: Vec::new(),
|
path_ids: Vec::new(),
|
||||||
|
face_code_ref: CodeRef {
|
||||||
|
range: sketch_on_face_source_range,
|
||||||
|
path_to_node: Vec::new(),
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
|
let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -253,9 +253,9 @@ pub struct Plane {
|
|||||||
pub value: PlaneType,
|
pub value: PlaneType,
|
||||||
/// Origin of the plane.
|
/// Origin of the plane.
|
||||||
pub origin: Point3d,
|
pub origin: Point3d,
|
||||||
/// What should the plane’s X axis be?
|
/// What should the plane's X axis be?
|
||||||
pub x_axis: Point3d,
|
pub x_axis: Point3d,
|
||||||
/// What should the plane’s Y axis be?
|
/// What should the plane's Y axis be?
|
||||||
pub y_axis: Point3d,
|
pub y_axis: Point3d,
|
||||||
/// The z-axis (normal).
|
/// The z-axis (normal).
|
||||||
pub z_axis: Point3d,
|
pub z_axis: Point3d,
|
||||||
@ -376,9 +376,9 @@ pub struct Face {
|
|||||||
pub artifact_id: ArtifactId,
|
pub artifact_id: ArtifactId,
|
||||||
/// The tag of the face.
|
/// The tag of the face.
|
||||||
pub value: String,
|
pub value: String,
|
||||||
/// What should the face’s X axis be?
|
/// What should the face's X axis be?
|
||||||
pub x_axis: Point3d,
|
pub x_axis: Point3d,
|
||||||
/// What should the face’s Y axis be?
|
/// What should the face's Y axis be?
|
||||||
pub y_axis: Point3d,
|
pub y_axis: Point3d,
|
||||||
/// The z-axis (normal).
|
/// The z-axis (normal).
|
||||||
pub z_axis: Point3d,
|
pub z_axis: Point3d,
|
||||||
@ -764,6 +764,19 @@ pub enum Path {
|
|||||||
/// This is used to compute the tangential angle.
|
/// This is used to compute the tangential angle.
|
||||||
ccw: bool,
|
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.
|
/// A path that is horizontal.
|
||||||
Horizontal {
|
Horizontal {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
@ -806,6 +819,7 @@ enum PathType {
|
|||||||
TangentialArc,
|
TangentialArc,
|
||||||
TangentialArcTo,
|
TangentialArcTo,
|
||||||
Circle,
|
Circle,
|
||||||
|
CircleThreePoint,
|
||||||
Horizontal,
|
Horizontal,
|
||||||
AngledLineTo,
|
AngledLineTo,
|
||||||
Arc,
|
Arc,
|
||||||
@ -818,6 +832,7 @@ impl From<&Path> for PathType {
|
|||||||
Path::TangentialArcTo { .. } => Self::TangentialArcTo,
|
Path::TangentialArcTo { .. } => Self::TangentialArcTo,
|
||||||
Path::TangentialArc { .. } => Self::TangentialArc,
|
Path::TangentialArc { .. } => Self::TangentialArc,
|
||||||
Path::Circle { .. } => Self::Circle,
|
Path::Circle { .. } => Self::Circle,
|
||||||
|
Path::CircleThreePoint { .. } => Self::CircleThreePoint,
|
||||||
Path::Horizontal { .. } => Self::Horizontal,
|
Path::Horizontal { .. } => Self::Horizontal,
|
||||||
Path::AngledLineTo { .. } => Self::AngledLineTo,
|
Path::AngledLineTo { .. } => Self::AngledLineTo,
|
||||||
Path::Base { .. } => Self::Base,
|
Path::Base { .. } => Self::Base,
|
||||||
@ -836,6 +851,7 @@ impl Path {
|
|||||||
Path::TangentialArcTo { base, .. } => base.geo_meta.id,
|
Path::TangentialArcTo { base, .. } => base.geo_meta.id,
|
||||||
Path::TangentialArc { base, .. } => base.geo_meta.id,
|
Path::TangentialArc { base, .. } => base.geo_meta.id,
|
||||||
Path::Circle { 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,
|
Path::Arc { base, .. } => base.geo_meta.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -849,6 +865,7 @@ impl Path {
|
|||||||
Path::TangentialArcTo { base, .. } => base.tag.clone(),
|
Path::TangentialArcTo { base, .. } => base.tag.clone(),
|
||||||
Path::TangentialArc { base, .. } => base.tag.clone(),
|
Path::TangentialArc { base, .. } => base.tag.clone(),
|
||||||
Path::Circle { base, .. } => base.tag.clone(),
|
Path::Circle { base, .. } => base.tag.clone(),
|
||||||
|
Path::CircleThreePoint { base, .. } => base.tag.clone(),
|
||||||
Path::Arc { base, .. } => base.tag.clone(),
|
Path::Arc { base, .. } => base.tag.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -862,6 +879,7 @@ impl Path {
|
|||||||
Path::TangentialArcTo { base, .. } => base,
|
Path::TangentialArcTo { base, .. } => base,
|
||||||
Path::TangentialArc { base, .. } => base,
|
Path::TangentialArc { base, .. } => base,
|
||||||
Path::Circle { base, .. } => base,
|
Path::Circle { base, .. } => base,
|
||||||
|
Path::CircleThreePoint { base, .. } => base,
|
||||||
Path::Arc { base, .. } => base,
|
Path::Arc { base, .. } => base,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -899,6 +917,15 @@ impl Path {
|
|||||||
linear_distance(self.get_from(), self.get_to())
|
linear_distance(self.get_from(), self.get_to())
|
||||||
}
|
}
|
||||||
Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius,
|
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 { .. } => {
|
Self::Arc { .. } => {
|
||||||
// TODO: Call engine utils to figure this out.
|
// TODO: Call engine utils to figure this out.
|
||||||
linear_distance(self.get_from(), self.get_to())
|
linear_distance(self.get_from(), self.get_to())
|
||||||
@ -915,6 +942,7 @@ impl Path {
|
|||||||
Path::TangentialArcTo { base, .. } => Some(base),
|
Path::TangentialArcTo { base, .. } => Some(base),
|
||||||
Path::TangentialArc { base, .. } => Some(base),
|
Path::TangentialArc { base, .. } => Some(base),
|
||||||
Path::Circle { base, .. } => Some(base),
|
Path::Circle { base, .. } => Some(base),
|
||||||
|
Path::CircleThreePoint { base, .. } => Some(base),
|
||||||
Path::Arc { base, .. } => Some(base),
|
Path::Arc { base, .. } => Some(base),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -934,6 +962,17 @@ impl Path {
|
|||||||
ccw: *ccw,
|
ccw: *ccw,
|
||||||
radius: *radius,
|
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 { .. } => {
|
Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {
|
||||||
let base = self.get_base();
|
let base = self.get_base();
|
||||||
GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
|
GetTangentialInfoFromPathsResult::PreviousPoint(base.from)
|
||||||
|
@ -243,7 +243,8 @@ pub(crate) async fn do_post_extrude(
|
|||||||
Path::Arc { .. }
|
Path::Arc { .. }
|
||||||
| Path::TangentialArc { .. }
|
| Path::TangentialArc { .. }
|
||||||
| Path::TangentialArcTo { .. }
|
| Path::TangentialArcTo { .. }
|
||||||
| Path::Circle { .. } => {
|
| Path::Circle { .. }
|
||||||
|
| Path::CircleThreePoint { .. } => {
|
||||||
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
|
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
|
||||||
face_id: *actual_face_id,
|
face_id: *actual_face_id,
|
||||||
tag: path.get_base().tag.clone(),
|
tag: path.get_base().tag.clone(),
|
||||||
|
@ -181,6 +181,9 @@ pub async fn circle_three_point(exec_state: &mut ExecState, args: Args) -> Resul
|
|||||||
tag = {docs = "Identifier for the circle to reference elsewhere."},
|
tag = {docs = "Identifier for the circle to reference elsewhere."},
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
// Similar to inner_circle, but needs to retain 3-point information in the
|
||||||
|
// path so it can be used for other features, otherwise it's lost.
|
||||||
async fn inner_circle_three_point(
|
async fn inner_circle_three_point(
|
||||||
p1: [f64; 2],
|
p1: [f64; 2],
|
||||||
p2: [f64; 2],
|
p2: [f64; 2],
|
||||||
@ -191,18 +194,69 @@ async fn inner_circle_three_point(
|
|||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Sketch, KclError> {
|
) -> Result<Sketch, KclError> {
|
||||||
let center = calculate_circle_center(p1, p2, p3);
|
let center = calculate_circle_center(p1, p2, p3);
|
||||||
inner_circle(
|
// It can be the distance to any of the 3 points - they all lay on the circumference.
|
||||||
CircleData {
|
let radius = distance(center.into(), p2.into());
|
||||||
center,
|
|
||||||
// It can be the distance to any of the 3 points - they all lay on the circumference.
|
let sketch_surface = match sketch_surface_or_group {
|
||||||
radius: distance(center.into(), p2.into()),
|
SketchOrSurface::SketchSurface(surface) => surface,
|
||||||
},
|
SketchOrSurface::Sketch(group) => group.on,
|
||||||
sketch_surface_or_group,
|
};
|
||||||
tag,
|
let sketch = crate::std::sketch::inner_start_profile_at(
|
||||||
|
[center[0] + radius, center[1]],
|
||||||
|
sketch_surface,
|
||||||
|
None,
|
||||||
exec_state,
|
exec_state,
|
||||||
args,
|
args.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
|
|
||||||
|
let from = [center[0] + radius, center[1]];
|
||||||
|
let angle_start = Angle::zero();
|
||||||
|
let angle_end = Angle::turn();
|
||||||
|
|
||||||
|
let id = exec_state.next_uuid();
|
||||||
|
|
||||||
|
args.batch_modeling_cmd(
|
||||||
|
id,
|
||||||
|
ModelingCmd::from(mcmd::ExtendPath {
|
||||||
|
path: sketch.id.into(),
|
||||||
|
segment: PathSegment::Arc {
|
||||||
|
start: angle_start,
|
||||||
|
end: angle_end,
|
||||||
|
center: KPoint2d::from(center).map(LengthUnit),
|
||||||
|
radius: radius.into(),
|
||||||
|
relative: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let current_path = Path::CircleThreePoint {
|
||||||
|
base: BasePath {
|
||||||
|
from,
|
||||||
|
to: from,
|
||||||
|
tag: tag.clone(),
|
||||||
|
geo_meta: GeoMeta {
|
||||||
|
id,
|
||||||
|
metadata: args.source_range.into(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
p1,
|
||||||
|
p2,
|
||||||
|
p3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_sketch = sketch.clone();
|
||||||
|
if let Some(tag) = &tag {
|
||||||
|
new_sketch.add_tag(tag, ¤t_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_sketch.paths.push(current_path);
|
||||||
|
|
||||||
|
args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: new_sketch.id }))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(new_sketch)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type of the polygon
|
/// Type of the polygon
|
||||||
|
@ -49,7 +49,7 @@ flowchart LR
|
|||||||
27[Wall]
|
27[Wall]
|
||||||
28[Wall]
|
28[Wall]
|
||||||
29[Wall]
|
29[Wall]
|
||||||
30["Plane<br>[544, 571, 0]"]
|
30["Cap End"]
|
||||||
31["SweepEdge Opposite"]
|
31["SweepEdge Opposite"]
|
||||||
32["SweepEdge Adjacent"]
|
32["SweepEdge Adjacent"]
|
||||||
33["SweepEdge Opposite"]
|
33["SweepEdge Opposite"]
|
||||||
@ -124,7 +124,7 @@ flowchart LR
|
|||||||
26 --- 27
|
26 --- 27
|
||||||
26 --- 28
|
26 --- 28
|
||||||
26 --- 29
|
26 --- 29
|
||||||
26 x--> 30
|
26 --- 30
|
||||||
26 --- 31
|
26 --- 31
|
||||||
26 --- 32
|
26 --- 32
|
||||||
26 --- 33
|
26 --- 33
|
||||||
|
@ -55,22 +55,28 @@ description: Variables in memory after executing circle_three_point.kcl
|
|||||||
0
|
0
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ccw": true,
|
|
||||||
"center": [
|
|
||||||
24.749999999999996,
|
|
||||||
19.749999999999996
|
|
||||||
],
|
|
||||||
"from": [
|
"from": [
|
||||||
30.0059,
|
30.0059,
|
||||||
19.75
|
19.75
|
||||||
],
|
],
|
||||||
"radius": 5.255949010407163,
|
"p1": [
|
||||||
|
25.0,
|
||||||
|
25.0
|
||||||
|
],
|
||||||
|
"p2": [
|
||||||
|
30.0,
|
||||||
|
20.0
|
||||||
|
],
|
||||||
|
"p3": [
|
||||||
|
27.0,
|
||||||
|
15.0
|
||||||
|
],
|
||||||
"tag": null,
|
"tag": null,
|
||||||
"to": [
|
"to": [
|
||||||
30.0059,
|
30.0059,
|
||||||
19.75
|
19.75
|
||||||
],
|
],
|
||||||
"type": "Circle"
|
"type": "CircleThreePoint"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"on": {
|
"on": {
|
||||||
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 55 KiB |
@ -20,7 +20,7 @@ flowchart LR
|
|||||||
11[Wall]
|
11[Wall]
|
||||||
12[Wall]
|
12[Wall]
|
||||||
13["Cap Start"]
|
13["Cap Start"]
|
||||||
14["Plane<br>[298, 351, 0]"]
|
14["Cap End"]
|
||||||
15["SweepEdge Opposite"]
|
15["SweepEdge Opposite"]
|
||||||
16["SweepEdge Adjacent"]
|
16["SweepEdge Adjacent"]
|
||||||
17["SweepEdge Opposite"]
|
17["SweepEdge Opposite"]
|
||||||
@ -59,7 +59,7 @@ flowchart LR
|
|||||||
8 --- 11
|
8 --- 11
|
||||||
8 --- 12
|
8 --- 12
|
||||||
8 --- 13
|
8 --- 13
|
||||||
8 x--> 14
|
8 --- 14
|
||||||
8 --- 15
|
8 --- 15
|
||||||
8 --- 16
|
8 --- 16
|
||||||
8 --- 17
|
8 --- 17
|
||||||
|
@ -23,7 +23,7 @@ flowchart LR
|
|||||||
11[Wall]
|
11[Wall]
|
||||||
12[Wall]
|
12[Wall]
|
||||||
13["Cap Start"]
|
13["Cap Start"]
|
||||||
14["Plane<br>[298, 323, 0]"]
|
14["Cap End"]
|
||||||
15["SweepEdge Opposite"]
|
15["SweepEdge Opposite"]
|
||||||
16["SweepEdge Adjacent"]
|
16["SweepEdge Adjacent"]
|
||||||
17["SweepEdge Opposite"]
|
17["SweepEdge Opposite"]
|
||||||
@ -71,7 +71,7 @@ flowchart LR
|
|||||||
8 --- 11
|
8 --- 11
|
||||||
8 --- 12
|
8 --- 12
|
||||||
8 --- 13
|
8 --- 13
|
||||||
8 x--> 14
|
8 --- 14
|
||||||
8 --- 15
|
8 --- 15
|
||||||
8 --- 16
|
8 --- 16
|
||||||
8 --- 17
|
8 --- 17
|
||||||
|
@ -23,7 +23,7 @@ flowchart LR
|
|||||||
11[Wall]
|
11[Wall]
|
||||||
12[Wall]
|
12[Wall]
|
||||||
13["Cap Start"]
|
13["Cap Start"]
|
||||||
14["Plane<br>[298, 323, 0]"]
|
14["Cap End"]
|
||||||
15["SweepEdge Opposite"]
|
15["SweepEdge Opposite"]
|
||||||
16["SweepEdge Adjacent"]
|
16["SweepEdge Adjacent"]
|
||||||
17["SweepEdge Opposite"]
|
17["SweepEdge Opposite"]
|
||||||
@ -71,7 +71,7 @@ flowchart LR
|
|||||||
8 --- 11
|
8 --- 11
|
||||||
8 --- 12
|
8 --- 12
|
||||||
8 --- 13
|
8 --- 13
|
||||||
8 x--> 14
|
8 --- 14
|
||||||
8 --- 15
|
8 --- 15
|
||||||
8 --- 16
|
8 --- 16
|
||||||
8 --- 17
|
8 --- 17
|
||||||
|
@ -22,7 +22,7 @@ flowchart LR
|
|||||||
10[Wall]
|
10[Wall]
|
||||||
11[Wall]
|
11[Wall]
|
||||||
12[Wall]
|
12[Wall]
|
||||||
13["Plane<br>[303, 328, 0]"]
|
13["Cap Start"]
|
||||||
14["Cap End"]
|
14["Cap End"]
|
||||||
15["SweepEdge Opposite"]
|
15["SweepEdge Opposite"]
|
||||||
16["SweepEdge Adjacent"]
|
16["SweepEdge Adjacent"]
|
||||||
@ -70,7 +70,7 @@ flowchart LR
|
|||||||
8 --- 10
|
8 --- 10
|
||||||
8 --- 11
|
8 --- 11
|
||||||
8 --- 12
|
8 --- 12
|
||||||
8 x--> 13
|
8 --- 13
|
||||||
8 --- 14
|
8 --- 14
|
||||||
8 --- 15
|
8 --- 15
|
||||||
8 --- 16
|
8 --- 16
|
||||||
|