SketchOnFace UI (#1664)
* always enter edit mode * initial blocking of extra code-mirror updates * dry out code * rejig selections * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * clean up * stream clean up * update export * sketch mode can be entered and exited for extrude faces But has bugs * startSketchOn working in some cases, editsketch animation working but not orientation of instersection plane etc * Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" This reverts commit406fca4c55
. * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * remove comment * add sketch on face e2e test * tweenCamToNegYAxis should respect reduced motion * initial sketch on face working with test * remove temporary toolbar button and xState flow * un-used vars * snapshot test tweak * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * type tidy up * Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" This reverts commitc39b8ebf95
. * Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" This reverts commitfecf6f490a
. * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * rename * sketch on sketch on sketch * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * typo * startSketchOn Endcaps end works, start is weird still * clear selections for entity_ids that are not recognised * fix sketch on end cap of second order extrustion * tiny clean up * fix sketch on close segment/face * clean up 'lastCodeMirrorSelectionUpdatedFromScene' * add code mode test for sketchOnExtrudedFace * make end cap selection more robust * update js artifacts for extrudes * update kcl docs * clean up --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -76,6 +76,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -244,6 +246,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -76,6 +76,8 @@ startSketchOn('XZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -244,6 +246,8 @@ startSketchOn('XZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -77,6 +77,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -245,6 +247,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -82,6 +82,8 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -250,6 +252,8 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -76,6 +76,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -244,6 +246,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -75,6 +75,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -243,6 +245,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -86,6 +86,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -254,6 +256,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -79,6 +79,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -247,6 +249,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -71,6 +71,8 @@ const rectangle = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -135,6 +137,8 @@ const rectangle = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -302,6 +306,8 @@ const rectangle = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -72,6 +72,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -240,6 +242,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -67,6 +67,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -68,6 +68,8 @@ const square = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -236,6 +238,8 @@ const square = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -403,6 +407,8 @@ const square = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn("YZ")
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn("YZ")
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn('-XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -234,6 +236,8 @@ startSketchOn('-XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -72,6 +72,8 @@ const part = rectShape([0, 0], 20, 20)
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -240,6 +242,8 @@ const part = rectShape([0, 0], 20, 20)
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -80,6 +80,8 @@ const part = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -77,6 +77,8 @@ const part = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -69,6 +69,8 @@ const part001 = startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -67,6 +67,8 @@ startSketchOn("YZ")
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -67,6 +67,8 @@ startSketchOn("YZ")
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -67,6 +67,8 @@ startSketchOn("YZ")
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -57,6 +57,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -128,6 +130,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -65,6 +65,8 @@ startSketchAt([0, 0])
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -254,6 +254,8 @@ string
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -75,6 +75,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -243,6 +245,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -65,6 +65,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -233,6 +235,8 @@ startSketchOn('-YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -234,6 +236,8 @@ startSketchOn('YZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -234,6 +236,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -66,6 +66,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -234,6 +236,8 @@ startSketchOn('XY')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -67,6 +67,8 @@ startSketchOn('XZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
@ -235,6 +237,8 @@ startSketchOn('XZ')
|
||||
},
|
||||
} |
|
||||
{
|
||||
// the face id the sketch is on
|
||||
faceId: uuid,
|
||||
// The id of the face.
|
||||
id: uuid,
|
||||
// The original sketch group id of the object we are sketching on.
|
||||
|
@ -20,6 +20,8 @@ const commonPoints = {
|
||||
startAt: '[9.06, -12.22]',
|
||||
num1: 9.14,
|
||||
num2: 18.2,
|
||||
// num1: 9.64,
|
||||
// num2: 19.19,
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
@ -76,6 +78,7 @@ test('Basic sketch', async ({ page }) => {
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const part001 = startSketchOn('-XZ')`
|
||||
)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
||||
|
||||
@ -86,7 +89,6 @@ test('Basic sketch', async ({ page }) => {
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -625,7 +627,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
||||
const emptySpaceClick = () =>
|
||||
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
|
||||
const topHorzSegmentClick = () =>
|
||||
page.mouse.click(709, 289).then(() => page.waitForTimeout(100))
|
||||
page.mouse.click(709, 290).then(() => page.waitForTimeout(100))
|
||||
const bottomHorzSegmentClick = () =>
|
||||
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
|
||||
|
||||
@ -640,13 +642,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
||||
await page.waitForTimeout(700) // wait for animation
|
||||
|
||||
const startXPx = 600
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
@ -727,13 +728,18 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|
||||
await emptySpaceClick()
|
||||
|
||||
// select segment in editor than another segment in scene and check there are two cursors
|
||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||
await page.waitForTimeout(300)
|
||||
await page.keyboard.down('Shift')
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
||||
// TODO change this back to shift click in the scene, not cmd click in the editor
|
||||
await bottomHorzSegmentClick()
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(1)
|
||||
|
||||
await page.keyboard.down(process.platform === 'linux' ? 'Control' : 'Meta')
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
|
||||
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||
await page.waitForTimeout(500)
|
||||
await page.keyboard.up(process.platform === 'linux' ? 'Control' : 'Meta')
|
||||
|
||||
// clear selection by clicking on nothing
|
||||
await emptySpaceClick()
|
||||
@ -918,13 +924,13 @@ test('Can add multiple sketches', async ({ page }) => {
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
const startXPx = 600
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
@ -1372,10 +1378,129 @@ test('Snap to close works (at any scale)', async ({ page }) => {
|
||||
) => `const part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %)
|
||||
|> line([${roundOff(scale * 175.36)}, 0], %)
|
||||
|> line([0, -${roundOff(scale * 175.37) + fudge}], %)
|
||||
|> line([0, -${roundOff(scale * 175.36) + fudge}], %)
|
||||
|> close(%)`
|
||||
|
||||
await doSnapAtDifferentScales([0, 100, 100], codeTemplate(0.01, 0.01))
|
||||
|
||||
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
|
||||
})
|
||||
|
||||
test('Sketch on face', async ({ page, context }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
|> line([2.48, 2.44], %)
|
||||
|> line([2.66, 1.17], %)
|
||||
|> line([3.75, 0.46], %)
|
||||
|> line([4.99, -0.46], %)
|
||||
|> line([3.3, -2.12], %)
|
||||
|> line([2.16, -3.33], %)
|
||||
|> line([0.85, -3.08], %)
|
||||
|> line([-0.18, -3.36], %)
|
||||
|> line([-3.86, -2.73], %)
|
||||
|> line([-17.67, 0.85], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.mouse.click(793, 133)
|
||||
|
||||
const firstClickPosition = [612, 238]
|
||||
const secondClickPosition = [661, 242]
|
||||
const thirdClickPosition = [609, 267]
|
||||
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||
|> startProfileAt([1.03, 1.03], %)
|
||||
|> line([4.18, -0.35], %)
|
||||
|> line([-4.44, -2.13], %)
|
||||
|> close(%)`)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
await u.updateCamPosition([1049, 239, 686])
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
|
||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
const pointToDragFirst = [691, 237]
|
||||
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
|
||||
steps: 5,
|
||||
})
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||
|> startProfileAt([1.03, 1.03], %)
|
||||
|> line([2.81, -0.33], %)
|
||||
|> line([-4.44, -2.13], %)
|
||||
|> close(%)`)
|
||||
|
||||
// exit sketch
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
|
||||
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||
|> startProfileAt([1.03, 1.03], %)
|
||||
|> line([2.81, -0.33], %)
|
||||
|> line([-4.44, -2.13], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`)
|
||||
})
|
||||
|
@ -612,7 +612,7 @@ test('Client side scene scale should match engine scale mm', async ({
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.33], %)`)
|
||||
|> startProfileAt([230.03, -310.32], %)`)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
@ -622,7 +622,7 @@ test('Client side scene scale should match engine scale mm', async ({
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.33], %)
|
||||
|> startProfileAt([230.03, -310.32], %)
|
||||
|> line([232.2, 0], %)`)
|
||||
|
||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||
@ -632,7 +632,7 @@ test('Client side scene scale should match engine scale mm', async ({
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([230.03, -310.33], %)
|
||||
|> startProfileAt([230.03, -310.32], %)
|
||||
|> line([232.2, 0], %)
|
||||
|> tangentialArcTo([694.43, -78.12], %)`)
|
||||
|
||||
@ -658,3 +658,48 @@ test('Client side scene scale should match engine scale mm', async ({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
|
||||
test('Sketch on face with none z-up', async ({ page, context }) => {
|
||||
const u = getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([1.4, 2.47], %)
|
||||
|> line({ to: [9.31, 10.55], tag: 'seg01' }, %)
|
||||
|> line([11.91, -10.42], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)
|
||||
const part002 = startSketchOn(part001, 'seg01')
|
||||
|> startProfileAt([-2.89, 1.82], %)
|
||||
|> line([4.68, 3.05], %)
|
||||
|> line({ to: [0, -7.79], tag: 'seg02' }, %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
// click at 641, 135
|
||||
await page.mouse.click(641, 135)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
|
||||
await page.waitForTimeout(200)
|
||||
})
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 52 KiB |
Binary file not shown.
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 54 KiB |
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
@ -425,6 +425,7 @@ export class CameraControls {
|
||||
if (this.camera instanceof OrthographicCamera) return
|
||||
const { x: px, y: py, z: pz } = this.camera.position
|
||||
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
||||
const oldCamUp = this.camera.up.clone()
|
||||
const aspect = window.innerWidth / window.innerHeight
|
||||
this.lastPerspectiveFov = this.camera.fov
|
||||
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||
@ -436,7 +437,8 @@ export class CameraControls {
|
||||
z_near,
|
||||
z_far
|
||||
)
|
||||
this.camera.up.set(0, 0, 1)
|
||||
|
||||
this.camera.up.copy(oldCamUp)
|
||||
this.camera.layers.enable(SKETCH_LAYER)
|
||||
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||
@ -458,13 +460,14 @@ export class CameraControls {
|
||||
}
|
||||
private createPerspectiveCamera = () => {
|
||||
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
||||
const previousCamUp = this.camera.up.clone()
|
||||
this.camera = new PerspectiveCamera(
|
||||
this.lastPerspectiveFov,
|
||||
window.innerWidth / window.innerHeight,
|
||||
z_near,
|
||||
z_far
|
||||
)
|
||||
this.camera.up.set(0, 0, 1)
|
||||
this.camera.up.copy(previousCamUp)
|
||||
this.camera.layers.enable(SKETCH_LAYER)
|
||||
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
||||
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
||||
@ -618,7 +621,7 @@ export class CameraControls {
|
||||
didChange = true
|
||||
}
|
||||
|
||||
this.safeLookAtTarget()
|
||||
this.safeLookAtTarget(this.camera.up)
|
||||
|
||||
// Update the camera's matrices
|
||||
this.camera.updateMatrixWorld()
|
||||
@ -683,6 +686,7 @@ export class CameraControls {
|
||||
targetAngle = -Math.PI / 2,
|
||||
duration = 500
|
||||
): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
// should tween the camera so that it has an xPosition of 0, and forcing it's yPosition to be negative
|
||||
// zPosition should stay the same
|
||||
const xyRadius = Math.sqrt(
|
||||
@ -693,22 +697,7 @@ export class CameraControls {
|
||||
this.camera.position.y - this.target.y,
|
||||
this.camera.position.x - this.target.x
|
||||
)
|
||||
this._isCamMovingCallback(true, true)
|
||||
return new Promise((resolve) => {
|
||||
new TWEEN.Tween({ angle: xyAngle })
|
||||
.to({ angle: targetAngle }, duration)
|
||||
.onUpdate((obj) => {
|
||||
const x = xyRadius * Math.cos(obj.angle)
|
||||
const y = xyRadius * Math.sin(obj.angle)
|
||||
this.camera.position.set(
|
||||
this.target.x + x,
|
||||
this.target.y + y,
|
||||
this.camera.position.z
|
||||
)
|
||||
this.update()
|
||||
this.onCameraChange()
|
||||
})
|
||||
.onComplete((obj) => {
|
||||
const camAtTime = (obj: { angle: number }) => {
|
||||
const x = xyRadius * Math.cos(obj.angle)
|
||||
const y = xyRadius * Math.sin(obj.angle)
|
||||
this.camera.position.set(
|
||||
@ -718,13 +707,27 @@ export class CameraControls {
|
||||
)
|
||||
this.update()
|
||||
this.onCameraChange()
|
||||
}
|
||||
const onComplete = (obj: { angle: number }) => {
|
||||
camAtTime(obj)
|
||||
this._isCamMovingCallback(false, true)
|
||||
|
||||
// resolve after a couple of frames
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => resolve())
|
||||
})
|
||||
})
|
||||
}
|
||||
this._isCamMovingCallback(true, true)
|
||||
|
||||
if (isReducedMotion()) {
|
||||
onComplete({ angle: targetAngle })
|
||||
return
|
||||
}
|
||||
|
||||
new TWEEN.Tween({ angle: xyAngle })
|
||||
.to({ angle: targetAngle }, duration)
|
||||
.onUpdate(camAtTime)
|
||||
.onComplete(onComplete)
|
||||
.start()
|
||||
})
|
||||
}
|
||||
@ -778,6 +781,8 @@ export class CameraControls {
|
||||
targetQuaternion,
|
||||
animationProgress
|
||||
)
|
||||
const up = new Vector3(0, 0, 1).applyQuaternion(currentQ)
|
||||
this.camera.up.copy(up)
|
||||
const currentTarget = tempVec.lerpVectors(
|
||||
initialTarget,
|
||||
targetPosition,
|
||||
@ -802,7 +807,7 @@ export class CameraControls {
|
||||
|
||||
const onComplete = async () => {
|
||||
if (isReducedMotion() && toOrthographic) {
|
||||
cameraAtTime(0.99)
|
||||
cameraAtTime(0.9999)
|
||||
this.useOrthographicCamera()
|
||||
} else if (toOrthographic) {
|
||||
await this.animateToOrthographic()
|
||||
@ -863,37 +868,40 @@ export class CameraControls {
|
||||
|
||||
animateFovChange() // Start the animation
|
||||
})
|
||||
animateToPerspective = () =>
|
||||
animateToPerspective = (targetCamUp = new Vector3(0, 0, 1)) =>
|
||||
new Promise((resolve) => {
|
||||
if (this.syncDirection === 'engineToClient')
|
||||
if (this.syncDirection === 'engineToClient') {
|
||||
console.warn(
|
||||
'animate To Perspective not design to work with engineToClient syncDirection.'
|
||||
)
|
||||
}
|
||||
this.isFovAnimationInProgress = true
|
||||
// Immediately set the camera to perspective with a very low FOV
|
||||
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
||||
this.lastPerspectiveFov = 4
|
||||
let currentFov = 4
|
||||
this.camera.updateProjectionMatrix()
|
||||
const fovAnimationStep = (targetFov - currentFov) / FRAMES_TO_ANIMATE_IN
|
||||
const initialCameraUp = this.camera.up.clone()
|
||||
this.usePerspectiveCamera()
|
||||
const tempVec = new Vector3()
|
||||
|
||||
const animateFovChange = () => {
|
||||
if (this.camera instanceof OrthographicCamera) return
|
||||
if (this.camera.fov < targetFov) {
|
||||
// Increase the FOV
|
||||
currentFov = Math.min(currentFov + fovAnimationStep, targetFov)
|
||||
// this.camera.fov = currentFov
|
||||
this.camera.updateProjectionMatrix()
|
||||
const cameraAtTime = (t: number) => {
|
||||
currentFov =
|
||||
this.lastPerspectiveFov + (targetFov - this.lastPerspectiveFov) * t
|
||||
const currentUp = tempVec.lerpVectors(initialCameraUp, targetCamUp, t)
|
||||
this.camera.up.copy(currentUp)
|
||||
this.dollyZoom(currentFov)
|
||||
requestAnimationFrame(animateFovChange) // Continue the animation
|
||||
} else {
|
||||
// Set the flag to false as the FOV animation is complete
|
||||
}
|
||||
|
||||
const onComplete = () => {
|
||||
this.isFovAnimationInProgress = false
|
||||
resolve(true)
|
||||
}
|
||||
}
|
||||
animateFovChange() // Start the animation
|
||||
|
||||
new TWEEN.Tween({ t: 0 })
|
||||
.to({ t: 1 }, isReducedMotion() ? 50 : FRAMES_TO_ANIMATE_IN * 16) // Assuming 60fps, hence 16ms per frame
|
||||
.easing(TWEEN.Easing.Quadratic.InOut)
|
||||
.onUpdate(({ t }) => cameraAtTime(t))
|
||||
.onComplete(onComplete)
|
||||
.start()
|
||||
})
|
||||
|
||||
reactCameraPropertiesCallback: (a: ReactCameraProperties) => void = () => {}
|
||||
|
@ -40,3 +40,12 @@ export function isQuaternionVertical(q: Quaternion) {
|
||||
// no x or y components means it's vertical
|
||||
return compareVec2Epsilon2([v.x, v.y], [0, 0])
|
||||
}
|
||||
|
||||
export function quaternionFromUpNForward(up: Vector3, forward: Vector3) {
|
||||
const dummyCam = new PerspectiveCamera()
|
||||
dummyCam.up.copy(up)
|
||||
dummyCam.position.copy(forward)
|
||||
dummyCam.lookAt(0, 0, 0)
|
||||
dummyCam.updateMatrix()
|
||||
return dummyCam.quaternion.clone()
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
Group,
|
||||
Intersection,
|
||||
LineCurve3,
|
||||
Matrix4,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
Object3D,
|
||||
@ -37,7 +36,7 @@ import {
|
||||
Y_AXIS,
|
||||
YZ_PLANE,
|
||||
} from './sceneInfra'
|
||||
import { isQuaternionVertical } from './helpers'
|
||||
import { isQuaternionVertical, quaternionFromUpNForward } from './helpers'
|
||||
import {
|
||||
CallExpression,
|
||||
getTangentialArcToInfo,
|
||||
@ -55,7 +54,7 @@ import {
|
||||
} from 'lang/wasm'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { executeAst } from 'useStore'
|
||||
import { executeAst, useStore } from 'useStore'
|
||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||
import {
|
||||
createArcGeometry,
|
||||
@ -70,16 +69,22 @@ import {
|
||||
changeSketchArguments,
|
||||
updateStartProfileAtArgs,
|
||||
} from 'lang/std/sketch'
|
||||
import { isReducedMotion, throttle } from 'lib/utils'
|
||||
import { throttle } from 'lib/utils'
|
||||
import {
|
||||
createArrayExpression,
|
||||
createCallExpressionStdLib,
|
||||
createLiteral,
|
||||
createPipeSubstitution,
|
||||
} from 'lang/modifyAst'
|
||||
import { getEventForSegmentSelection } from 'lib/selections'
|
||||
import {
|
||||
getEventForSegmentSelection,
|
||||
sendSelectEventToEngine,
|
||||
} from 'lib/selections'
|
||||
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
||||
import { createGridHelper, orthoScale, perspScale } from './helpers'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { SketchDetails } from 'machines/modelingMachine'
|
||||
|
||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||
|
||||
@ -164,7 +169,7 @@ class SceneEntities {
|
||||
console.warn('createIntersectionPlane called when it already exists')
|
||||
return
|
||||
}
|
||||
const hundredM = 1000000
|
||||
const hundredM = 100_0000
|
||||
const planeGeometry = new PlaneGeometry(hundredM, hundredM)
|
||||
const planeMaterial = new MeshBasicMaterial({
|
||||
color: 0xff0000,
|
||||
@ -178,7 +183,12 @@ class SceneEntities {
|
||||
this.intersectionPlane.layers.set(INTERSECTION_PLANE_LAYER)
|
||||
this.scene.add(this.intersectionPlane)
|
||||
}
|
||||
createSketchAxis(sketchPathToNode: PathToNode) {
|
||||
createSketchAxis(
|
||||
sketchPathToNode: PathToNode,
|
||||
forward: [number, number, number],
|
||||
up: [number, number, number],
|
||||
sketchPosition?: [number, number, number]
|
||||
) {
|
||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||
const baseXColor = 0x000055
|
||||
const baseYColor = 0x550000
|
||||
@ -238,14 +248,12 @@ class SceneEntities {
|
||||
child.layers.set(SKETCH_LAYER)
|
||||
})
|
||||
|
||||
const quat = quaternionFromSketchGroup(
|
||||
sketchGroupFromPathToNode({
|
||||
pathToNode: sketchPathToNode,
|
||||
ast: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
const quat = quaternionFromUpNForward(
|
||||
new Vector3(...up),
|
||||
new Vector3(...forward)
|
||||
)
|
||||
this.axisGroup.setRotationFromQuaternion(quat)
|
||||
sketchPosition && this.axisGroup.position.set(...sketchPosition)
|
||||
this.scene.add(this.axisGroup)
|
||||
}
|
||||
removeIntersectionPlane() {
|
||||
@ -258,10 +266,16 @@ class SceneEntities {
|
||||
ast,
|
||||
// is draft line assumes the last segment is a draft line, and mods it as the user moves the mouse
|
||||
draftSegment,
|
||||
forward,
|
||||
up,
|
||||
position,
|
||||
}: {
|
||||
sketchPathToNode: PathToNode
|
||||
ast?: Program
|
||||
draftSegment?: DraftSegment
|
||||
forward: [number, number, number]
|
||||
up: [number, number, number]
|
||||
position?: [number, number, number]
|
||||
}) {
|
||||
sceneInfra.resetMouseListeners()
|
||||
this.createIntersectionPlane()
|
||||
@ -286,6 +300,7 @@ class SceneEntities {
|
||||
if (!Array.isArray(sketchGroup?.value)) return
|
||||
this.sceneProgramMemory = programMemory
|
||||
const group = new Group()
|
||||
position && group.position.set(...position)
|
||||
group.userData = {
|
||||
type: SKETCH_GROUP_SEGMENTS,
|
||||
pathToNode: sketchPathToNode,
|
||||
@ -377,13 +392,18 @@ class SceneEntities {
|
||||
this.activeSegments[JSON.stringify(segPathToNode)] = seg
|
||||
})
|
||||
|
||||
this.currentSketchQuaternion = quaternionFromSketchGroup(sketchGroup)
|
||||
this.currentSketchQuaternion = quaternionFromUpNForward(
|
||||
new Vector3(...up),
|
||||
new Vector3(...forward)
|
||||
)
|
||||
group.setRotationFromQuaternion(this.currentSketchQuaternion)
|
||||
this.intersectionPlane &&
|
||||
this.intersectionPlane.setRotationFromQuaternion(
|
||||
this.currentSketchQuaternion
|
||||
)
|
||||
|
||||
this.intersectionPlane &&
|
||||
position &&
|
||||
this.intersectionPlane.position.set(...position)
|
||||
this.scene.add(group)
|
||||
if (!draftSegment) {
|
||||
sceneInfra.setCallbacks({
|
||||
@ -453,7 +473,13 @@ class SceneEntities {
|
||||
|
||||
kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
this.setupSketch({ sketchPathToNode, draftSegment })
|
||||
this.setupSketch({
|
||||
sketchPathToNode,
|
||||
draftSegment,
|
||||
forward,
|
||||
up,
|
||||
position,
|
||||
})
|
||||
},
|
||||
onMove: (args) => {
|
||||
this.onDragSegment({
|
||||
@ -476,21 +502,37 @@ class SceneEntities {
|
||||
}
|
||||
updateAstAndRejigSketch = async (
|
||||
sketchPathToNode: PathToNode,
|
||||
modifiedAst: Program
|
||||
modifiedAst: Program,
|
||||
forward: [number, number, number],
|
||||
up: [number, number, number],
|
||||
origin: [number, number, number]
|
||||
) => {
|
||||
await kclManager.updateAst(modifiedAst, false)
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
this.setupSketch({ sketchPathToNode })
|
||||
this.setupSketch({ sketchPathToNode, forward, up, position: origin })
|
||||
}
|
||||
setUpDraftArc = async (sketchPathToNode: PathToNode) => {
|
||||
setUpDraftArc = async (
|
||||
sketchPathToNode: PathToNode,
|
||||
forward: [number, number, number],
|
||||
up: [number, number, number]
|
||||
) => {
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
this.setupSketch({ sketchPathToNode, draftSegment: 'tangentialArcTo' })
|
||||
this.setupSketch({
|
||||
sketchPathToNode,
|
||||
draftSegment: 'tangentialArcTo',
|
||||
forward,
|
||||
up,
|
||||
})
|
||||
}
|
||||
setUpDraftLine = async (sketchPathToNode: PathToNode) => {
|
||||
setUpDraftLine = async (
|
||||
sketchPathToNode: PathToNode,
|
||||
forward: [number, number, number],
|
||||
up: [number, number, number]
|
||||
) => {
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
this.setupSketch({ sketchPathToNode, draftSegment: 'line' })
|
||||
this.setupSketch({ sketchPathToNode, draftSegment: 'line', forward, up })
|
||||
}
|
||||
onDraftLineMouseMove = () => {}
|
||||
prepareTruncatedMemoryAndAst = (
|
||||
@ -785,10 +827,10 @@ class SceneEntities {
|
||||
}
|
||||
}
|
||||
async animateAfterSketch() {
|
||||
if (isReducedMotion()) {
|
||||
sceneInfra.camControls.usePerspectiveCamera()
|
||||
return
|
||||
}
|
||||
// if (isReducedMotion()) {
|
||||
// sceneInfra.camControls.usePerspectiveCamera()
|
||||
// return
|
||||
// }
|
||||
await sceneInfra.camControls.animateToPerspective()
|
||||
}
|
||||
removeSketchGrid() {
|
||||
@ -853,26 +895,81 @@ class SceneEntities {
|
||||
const type: DefaultPlane = selected.userData.type
|
||||
selected.material.color = defaultPlaneColor(type)
|
||||
},
|
||||
onClick: (args) => {
|
||||
onClick: async (args) => {
|
||||
const checkExtrudeFaceClick = async (): Promise<boolean> => {
|
||||
const { streamDimensions } = useStore.getState()
|
||||
const { entity_id } = await sendSelectEventToEngine(
|
||||
args?.mouseEvent,
|
||||
document.getElementById('video-stream') as HTMLVideoElement,
|
||||
streamDimensions
|
||||
)
|
||||
if (!entity_id) return false
|
||||
const artifact = engineCommandManager.artifactMap[entity_id]
|
||||
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info')
|
||||
return false
|
||||
const faceInfo: Models['FaceIsPlanar_type'] = (
|
||||
await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'face_is_planar',
|
||||
object_id: entity_id,
|
||||
},
|
||||
})
|
||||
)?.data?.data
|
||||
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
||||
return false
|
||||
const { z_axis, origin, y_axis } = faceInfo
|
||||
const pathToNode = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
artifact.range
|
||||
)
|
||||
|
||||
sceneInfra.modelingSend({
|
||||
type: 'Select default plane',
|
||||
data: {
|
||||
type: 'extrudeFace',
|
||||
zAxis: [z_axis.x, z_axis.y, z_axis.z],
|
||||
yAxis: [y_axis.x, y_axis.y, y_axis.z],
|
||||
position: [origin.x, origin.y, origin.z].map(
|
||||
(num) => num / sceneInfra._baseUnitMultiplier
|
||||
) as [number, number, number],
|
||||
extrudeSegmentPathToNode: pathToNode,
|
||||
cap:
|
||||
artifact?.additionalData?.type === 'cap'
|
||||
? artifact.additionalData.info
|
||||
: 'none',
|
||||
},
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
if (await checkExtrudeFaceClick()) return
|
||||
|
||||
if (!args || !args.intersects?.[0]) return
|
||||
if (args.mouseEvent.which !== 1) return
|
||||
const { intersects } = args
|
||||
const type = intersects?.[0].object.name || ''
|
||||
const posNorm = Number(intersects?.[0]?.normal?.z) > 0
|
||||
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
|
||||
let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
|
||||
let zAxis: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
|
||||
let yAxis: [number, number, number] = [0, 1, 0]
|
||||
if (type === YZ_PLANE) {
|
||||
planeString = posNorm ? 'YZ' : '-YZ'
|
||||
normal = posNorm ? [1, 0, 0] : [-1, 0, 0]
|
||||
zAxis = posNorm ? [1, 0, 0] : [-1, 0, 0]
|
||||
yAxis = [0, 0, 1]
|
||||
} else if (type === XZ_PLANE) {
|
||||
planeString = posNorm ? 'XZ' : '-XZ'
|
||||
normal = posNorm ? [0, 1, 0] : [0, -1, 0]
|
||||
zAxis = posNorm ? [0, 1, 0] : [0, -1, 0]
|
||||
yAxis = [0, 0, 1]
|
||||
}
|
||||
sceneInfra.modelingSend({
|
||||
type: 'Select default plane',
|
||||
data: {
|
||||
type: 'defaultPlane',
|
||||
plane: planeString,
|
||||
normal,
|
||||
zAxis,
|
||||
yAxis,
|
||||
},
|
||||
})
|
||||
},
|
||||
@ -1002,33 +1099,10 @@ export function sketchGroupFromPathToNode({
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
).node
|
||||
// console.trace('where from?')
|
||||
return programMemory.root[varDec?.id?.name || ''] as SketchGroup
|
||||
}
|
||||
|
||||
export function quaternionFromSketchGroup(
|
||||
sketchGroup: SketchGroup
|
||||
): Quaternion {
|
||||
// TODO figure what is happening in the executor that it's some times returning
|
||||
// [x,y,z] and sometimes {x,y,z}
|
||||
if (!sketchGroup?.zAxis) {
|
||||
// sometimes sketchGroup is undefined,
|
||||
// I don't quiet understand the circumstances yet
|
||||
// and it's very intermittent so leaving this here for now
|
||||
console.log('no zAxis', sketchGroup)
|
||||
console.trace('no zAxis')
|
||||
}
|
||||
const zAxisVec = massageFormats(sketchGroup?.zAxis)
|
||||
const yAxisVec = massageFormats(sketchGroup?.yAxis)
|
||||
const xAxisVec = new Vector3().crossVectors(yAxisVec, zAxisVec).normalize()
|
||||
|
||||
let yAxisVecNormalized = yAxisVec.clone().normalize()
|
||||
let zAxisVecNormalized = zAxisVec.clone().normalize()
|
||||
|
||||
let rotationMatrix = new Matrix4()
|
||||
rotationMatrix.makeBasis(xAxisVec, yAxisVecNormalized, zAxisVecNormalized)
|
||||
return new Quaternion().setFromRotationMatrix(rotationMatrix)
|
||||
}
|
||||
|
||||
function colorSegment(object: any, color: number) {
|
||||
const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START])
|
||||
if (segmentHead) {
|
||||
@ -1063,10 +1137,68 @@ export function getSketchQuaternion(
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
const zAxis = sketchGroup?.zAxis || sketchNormalBackUp
|
||||
return getQuaternionFromZAxis(massageFormats(zAxis))
|
||||
}
|
||||
export async function getSketchOrientationDetails(
|
||||
sketchPathToNode: PathToNode
|
||||
): Promise<{
|
||||
quat: Quaternion
|
||||
sketchDetails: SketchDetails
|
||||
}> {
|
||||
const sketchGroup = sketchGroupFromPathToNode({
|
||||
pathToNode: sketchPathToNode,
|
||||
ast: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
if (sketchGroup.on.type === 'plane') {
|
||||
const zAxis = sketchGroup?.zAxis
|
||||
return {
|
||||
quat: getQuaternionFromZAxis(massageFormats(zAxis)),
|
||||
sketchDetails: {
|
||||
sketchPathToNode,
|
||||
zAxis: [zAxis.x, zAxis.y, zAxis.z],
|
||||
yAxis: [sketchGroup.yAxis.x, sketchGroup.yAxis.y, sketchGroup.yAxis.z],
|
||||
origin: [0, 0, 0],
|
||||
},
|
||||
}
|
||||
}
|
||||
if (sketchGroup.on.type === 'face') {
|
||||
const faceInfo: Models['FaceIsPlanar_type'] = (
|
||||
await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'face_is_planar',
|
||||
object_id: sketchGroup.on.faceId,
|
||||
},
|
||||
})
|
||||
)?.data?.data
|
||||
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
||||
throw new Error('faceInfo')
|
||||
const { z_axis, y_axis, origin } = faceInfo
|
||||
const quaternion = quaternionFromUpNForward(
|
||||
new Vector3(y_axis.x, y_axis.y, y_axis.z),
|
||||
new Vector3(z_axis.x, z_axis.y, z_axis.z)
|
||||
)
|
||||
return {
|
||||
quat: quaternion,
|
||||
sketchDetails: {
|
||||
sketchPathToNode,
|
||||
zAxis: [z_axis.x, z_axis.y, z_axis.z],
|
||||
yAxis: [y_axis.x, y_axis.y, y_axis.z],
|
||||
origin: [origin.x, origin.y, origin.z],
|
||||
},
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
'sketchGroup.on.type not recognized, has a new type been added?'
|
||||
)
|
||||
}
|
||||
|
||||
export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
|
||||
const dummyCam = new PerspectiveCamera()
|
||||
dummyCam.up.set(0, 0, 1)
|
||||
const _zAxis = massageFormats(zAxis)
|
||||
dummyCam.position.copy(_zAxis)
|
||||
dummyCam.position.copy(zAxis)
|
||||
dummyCam.lookAt(0, 0, 0)
|
||||
dummyCam.updateMatrix()
|
||||
const quaternion = dummyCam.quaternion.clone()
|
||||
@ -1075,7 +1207,7 @@ export function getSketchQuaternion(
|
||||
|
||||
// because vertical quaternions are a gimbal lock, for the orbit controls
|
||||
// it's best to set them explicitly to the vertical position with a known good camera up
|
||||
if (isVert && _zAxis.z < 0) {
|
||||
if (isVert && zAxis.z < 0) {
|
||||
quaternion.set(0, 1, 0, 0)
|
||||
} else if (isVert) {
|
||||
quaternion.set(0, 0, 0, 1)
|
||||
|
@ -37,8 +37,10 @@ export const ZOOM_MAGIC_NUMBER = 63.5
|
||||
|
||||
export const INTERSECTION_PLANE_LAYER = 1
|
||||
export const SKETCH_LAYER = 2
|
||||
export const DEBUG_SHOW_INTERSECTION_PLANE = false
|
||||
export const DEBUG_SHOW_BOTH_SCENES = false
|
||||
|
||||
// redundant types so that it can be changed temporarily but CI will catch the wrong type
|
||||
export const DEBUG_SHOW_INTERSECTION_PLANE: false = false
|
||||
export const DEBUG_SHOW_BOTH_SCENES: false = false
|
||||
|
||||
export const RAYCASTABLE_PLANE = 'raycastable-plane'
|
||||
export const DEFAULT_PLANES = 'default-planes'
|
||||
@ -97,13 +99,13 @@ class SceneInfra {
|
||||
_baseUnitMultiplier = 1
|
||||
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
|
||||
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
|
||||
onClickCallback: (arg: OnClickCallbackArgs) => void = () => {}
|
||||
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
||||
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
||||
setCallbacks = (callbacks: {
|
||||
onDrag?: (arg: OnDragCallbackArgs) => void
|
||||
onMove?: (arg: OnMoveCallbackArgs) => void
|
||||
onClick?: (arg?: OnClickCallbackArgs) => void
|
||||
onClick?: (arg: OnClickCallbackArgs) => void
|
||||
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
|
||||
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
|
||||
}) => {
|
||||
@ -272,16 +274,19 @@ class SceneInfra {
|
||||
let transformedPoint = intersectPoint.clone()
|
||||
if (transformedPoint) {
|
||||
transformedPoint.applyQuaternion(inversePlaneQuaternion)
|
||||
transformedPoint?.sub(
|
||||
new Vector3(...planePosition).applyQuaternion(inversePlaneQuaternion)
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
twoD: new Vector2(
|
||||
const twoD = new Vector2(
|
||||
// I think the intersection plane doesn't get scale when nearly everything else does, maybe that should change
|
||||
transformedPoint.x / this._baseUnitMultiplier,
|
||||
transformedPoint.y / this._baseUnitMultiplier
|
||||
), // z should be 0
|
||||
) // z should be 0
|
||||
const planePositionCorrected = new Vector3(
|
||||
...planePosition
|
||||
).applyQuaternion(inversePlaneQuaternion)
|
||||
twoD.sub(new Vector2(...planePositionCorrected))
|
||||
|
||||
return {
|
||||
twoD,
|
||||
threeD: intersectPoint.divideScalar(this._baseUnitMultiplier),
|
||||
intersection: planeIntersects[0],
|
||||
}
|
||||
@ -464,7 +469,7 @@ class SceneInfra {
|
||||
intersects,
|
||||
})
|
||||
} else {
|
||||
this.onClickCallback()
|
||||
this.onClickCallback({ mouseEvent, intersects })
|
||||
}
|
||||
// Clear the selected state whether it was dragged or not
|
||||
this.selected = null
|
||||
@ -478,7 +483,7 @@ class SceneInfra {
|
||||
intersects,
|
||||
})
|
||||
} else {
|
||||
this.onClickCallback()
|
||||
this.onClickCallback({ mouseEvent, intersects })
|
||||
}
|
||||
}
|
||||
showDefaultPlanes() {
|
||||
|
@ -38,7 +38,26 @@ describe('processMemory', () => {
|
||||
myVar: 5,
|
||||
myFn: undefined,
|
||||
otherVar: 3,
|
||||
theExtrude: [],
|
||||
theExtrude: [
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: '',
|
||||
id: expect.any(String),
|
||||
sourceRange: [170, 194],
|
||||
},
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: '',
|
||||
id: expect.any(String),
|
||||
sourceRange: [202, 230],
|
||||
},
|
||||
],
|
||||
theSketch: [
|
||||
{ type: 'ToPoint', to: [-3.35, 0.17], from: [0, 0], name: '' },
|
||||
{ type: 'ToPoint', to: [0.98, 5.16], from: [-3.35, 0.17], name: '' },
|
||||
|
@ -23,9 +23,9 @@ import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
||||
import { pathMapToSelections } from 'lang/util'
|
||||
import { useStore } from 'useStore'
|
||||
import {
|
||||
Selections,
|
||||
canExtrudeSelection,
|
||||
handleSelectionBatch,
|
||||
handleSelectionWithShift,
|
||||
isSelectionLastLine,
|
||||
isSketchPipe,
|
||||
} from 'lib/selections'
|
||||
@ -34,14 +34,20 @@ import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
||||
import { getSketchQuaternion } from 'clientSideScene/sceneEntities'
|
||||
import { startSketchOnDefault } from 'lang/modifyAst'
|
||||
import { Program } from 'lang/wasm'
|
||||
import { isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import {
|
||||
getSketchQuaternion,
|
||||
getSketchOrientationDetails,
|
||||
} from 'clientSideScene/sceneEntities'
|
||||
import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst'
|
||||
import { Program, parse } from 'lang/wasm'
|
||||
import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
|
||||
import { TEST } from 'env'
|
||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
import { Models } from '@kittycad/lib/dist/types/src'
|
||||
import toast from 'react-hot-toast'
|
||||
import { EditorSelection } from '@uiw/react-codemirror'
|
||||
import { Vector3 } from 'three'
|
||||
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -69,9 +75,15 @@ export const ModelingMachineProvider = ({
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
useSetupEngineManager(streamRef, token)
|
||||
|
||||
const { isShiftDown, editorView } = useStore((s) => ({
|
||||
const {
|
||||
isShiftDown,
|
||||
editorView,
|
||||
setLastCodeMirrorSelectionUpdatedFromScene,
|
||||
} = useStore((s) => ({
|
||||
isShiftDown: s.isShiftDown,
|
||||
editorView: s.editorView,
|
||||
setLastCodeMirrorSelectionUpdatedFromScene:
|
||||
s.setLastCodeMirrorSelectionUpdatedFromScene,
|
||||
}))
|
||||
|
||||
// Settings machine setup
|
||||
@ -92,92 +104,98 @@ export const ModelingMachineProvider = ({
|
||||
{
|
||||
actions: {
|
||||
'sketch exit execute': () => {
|
||||
try {
|
||||
kclManager.executeAst(parse(kclManager.code))
|
||||
} catch (e) {
|
||||
kclManager.executeAst()
|
||||
}
|
||||
},
|
||||
'Set selection': assign(({ selectionRanges }, event) => {
|
||||
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
|
||||
const setSelections = event.data
|
||||
if (!editorView) return {}
|
||||
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
|
||||
return { selectionRanges: setSelections.selection }
|
||||
else if (setSelections.selectionType === 'otherSelection') {
|
||||
const {
|
||||
codeMirrorSelection,
|
||||
selectionRangeTypeMap,
|
||||
otherSelections,
|
||||
} = handleSelectionWithShift({
|
||||
otherSelection: setSelections.selection,
|
||||
currentSelections: selectionRanges,
|
||||
isShiftDown,
|
||||
})
|
||||
setTimeout(() => {
|
||||
editorView.dispatch({
|
||||
selection: codeMirrorSelection,
|
||||
})
|
||||
})
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
selectionRanges: {
|
||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
||||
otherSelections,
|
||||
},
|
||||
const dispatchSelection = (selection?: EditorSelection) => {
|
||||
if (!selection) return // TODO less of hack for the below please
|
||||
setLastCodeMirrorSelectionUpdatedFromScene(Date.now())
|
||||
setTimeout(() => editorView.dispatch({ selection }))
|
||||
}
|
||||
let selections: Selections = {
|
||||
codeBasedSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
if (setSelections.selectionType === 'singleCodeCursor') {
|
||||
if (!setSelections.selection && isShiftDown) {
|
||||
} else if (!setSelections.selection && !isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
} else if (setSelections.selection && !isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: [setSelections.selection],
|
||||
otherSelections: [],
|
||||
}
|
||||
} else if (setSelections.selection && isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: [
|
||||
...selectionRanges.codeBasedSelections,
|
||||
setSelections.selection,
|
||||
],
|
||||
otherSelections: selectionRanges.otherSelections,
|
||||
}
|
||||
}
|
||||
} else if (setSelections.selectionType === 'singleCodeCursor') {
|
||||
// This DOES NOT set the `selectionRanges` in xstate context
|
||||
// instead it updates/dispatches to the editor, which in turn updates the xstate context
|
||||
// I've found this the best way to deal with the editor without causing an infinite loop
|
||||
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
|
||||
// because we want to respect the user manually placing the cursor too.
|
||||
|
||||
// for more details on how selections see `src/lib/selections.ts`.
|
||||
|
||||
const {
|
||||
engineEvents,
|
||||
codeMirrorSelection,
|
||||
selectionRangeTypeMap,
|
||||
otherSelections,
|
||||
} = handleSelectionWithShift({
|
||||
codeSelection: setSelections.selection,
|
||||
currentSelections: selectionRanges,
|
||||
isShiftDown,
|
||||
updateSceneObjectColors,
|
||||
} = handleSelectionBatch({
|
||||
selections,
|
||||
})
|
||||
if (codeMirrorSelection) {
|
||||
setTimeout(() => {
|
||||
editorView.dispatch({
|
||||
selection: codeMirrorSelection,
|
||||
})
|
||||
})
|
||||
}
|
||||
if (!setSelections.selection) {
|
||||
codeMirrorSelection && dispatchSelection(codeMirrorSelection)
|
||||
engineEvents &&
|
||||
engineEvents.forEach((event) =>
|
||||
engineCommandManager.sendSceneCommand(event)
|
||||
)
|
||||
updateSceneObjectColors()
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
selectionRanges: {
|
||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
||||
otherSelections,
|
||||
},
|
||||
selectionRanges: selections,
|
||||
}
|
||||
}
|
||||
|
||||
if (setSelections.selectionType === 'mirrorCodeMirrorSelections') {
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
selectionRanges: {
|
||||
selectionRanges: setSelections.selection,
|
||||
}
|
||||
}
|
||||
|
||||
if (setSelections.selectionType === 'otherSelection') {
|
||||
if (isShiftDown) {
|
||||
selections = {
|
||||
codeBasedSelections: selectionRanges.codeBasedSelections,
|
||||
otherSelections,
|
||||
},
|
||||
otherSelections: [setSelections.selection],
|
||||
}
|
||||
} else {
|
||||
selections = {
|
||||
codeBasedSelections: [],
|
||||
otherSelections: [setSelections.selection],
|
||||
}
|
||||
}
|
||||
// This DOES NOT set the `selectionRanges` in xstate context
|
||||
// same as comment above
|
||||
const { codeMirrorSelection, selectionRangeTypeMap } =
|
||||
const { engineEvents, updateSceneObjectColors } =
|
||||
handleSelectionBatch({
|
||||
selections: setSelections.selection,
|
||||
})
|
||||
if (codeMirrorSelection) {
|
||||
setTimeout(() => {
|
||||
editorView.dispatch({
|
||||
selection: codeMirrorSelection,
|
||||
})
|
||||
selections,
|
||||
})
|
||||
engineEvents &&
|
||||
engineEvents.forEach((event) =>
|
||||
engineCommandManager.sendSceneCommand(event)
|
||||
)
|
||||
updateSceneObjectColors()
|
||||
return {
|
||||
selectionRanges: selections,
|
||||
}
|
||||
return { selectionRangeTypeMap }
|
||||
}
|
||||
|
||||
return {}
|
||||
}),
|
||||
'Engine export': (_, event) => {
|
||||
if (event.type !== 'Export' || TEST) return
|
||||
@ -255,10 +273,10 @@ export const ModelingMachineProvider = ({
|
||||
kclManager.kclErrors.length === 0 && kclManager.ast.body.length > 0,
|
||||
},
|
||||
services: {
|
||||
'AST-undo-startSketchOn': async ({ sketchPathToNode }) => {
|
||||
if (!sketchPathToNode) return
|
||||
'AST-undo-startSketchOn': async ({ sketchDetails }) => {
|
||||
if (!sketchDetails) return
|
||||
const newAst: Program = JSON.parse(JSON.stringify(kclManager.ast))
|
||||
const varDecIndex = sketchPathToNode[1][0]
|
||||
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
|
||||
// remove body item at varDecIndex
|
||||
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
||||
await kclManager.executeAstMock(newAst, { updates: 'code' })
|
||||
@ -267,28 +285,69 @@ export const ModelingMachineProvider = ({
|
||||
onDrag: () => {},
|
||||
})
|
||||
},
|
||||
'animate-to-face': async (_, { data: { plane, normal } }) => {
|
||||
'animate-to-face': async (_, { data }) => {
|
||||
if (data.type === 'extrudeFace') {
|
||||
const { modifiedAst, pathToNode: pathToNewSketchNode } =
|
||||
sketchOnExtrudedFace(
|
||||
kclManager.ast,
|
||||
data.extrudeSegmentPathToNode,
|
||||
kclManager.programMemory,
|
||||
data.cap
|
||||
)
|
||||
await kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
||||
|
||||
const forward = new Vector3(...data.zAxis)
|
||||
const up = new Vector3(...data.yAxis)
|
||||
|
||||
let target = new Vector3(...data.position).multiplyScalar(
|
||||
sceneInfra._baseUnitMultiplier
|
||||
)
|
||||
const quaternion = quaternionFromUpNForward(up, forward)
|
||||
await sceneInfra.camControls.tweenCameraToQuaternion(
|
||||
quaternion,
|
||||
target
|
||||
)
|
||||
|
||||
return {
|
||||
sketchPathToNode: pathToNewSketchNode,
|
||||
zAxis: data.zAxis,
|
||||
yAxis: data.yAxis,
|
||||
origin: data.position,
|
||||
}
|
||||
}
|
||||
const { modifiedAst, pathToNode } = startSketchOnDefault(
|
||||
kclManager.ast,
|
||||
plane
|
||||
data.plane
|
||||
)
|
||||
await kclManager.updateAst(modifiedAst, false)
|
||||
const quaternion = getSketchQuaternion(pathToNode, normal)
|
||||
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
|
||||
const quat = await getSketchQuaternion(pathToNode, data.zAxis)
|
||||
await sceneInfra.camControls.tweenCameraToQuaternion(quat)
|
||||
return {
|
||||
sketchPathToNode: pathToNode,
|
||||
sketchNormalBackUp: normal,
|
||||
zAxis: data.zAxis,
|
||||
yAxis: data.yAxis,
|
||||
origin: [0, 0, 0],
|
||||
}
|
||||
},
|
||||
'animate-to-sketch': async ({
|
||||
sketchPathToNode,
|
||||
sketchNormalBackUp,
|
||||
}) => {
|
||||
const quaternion = getSketchQuaternion(
|
||||
sketchPathToNode || [],
|
||||
sketchNormalBackUp
|
||||
'animate-to-sketch': async ({ selectionRanges }) => {
|
||||
const sourceRange = selectionRanges.codeBasedSelections[0].range
|
||||
const sketchPathToNode = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
sourceRange
|
||||
)
|
||||
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
|
||||
const info = await getSketchOrientationDetails(sketchPathToNode || [])
|
||||
await sceneInfra.camControls.tweenCameraToQuaternion(
|
||||
info.quat,
|
||||
new Vector3(...info.sketchDetails.origin)
|
||||
)
|
||||
return {
|
||||
sketchPathToNode: sketchPathToNode || [],
|
||||
zAxis: info.sketchDetails.zAxis || null,
|
||||
yAxis: info.sketchDetails.yAxis || null,
|
||||
origin: info.sketchDetails.origin.map(
|
||||
(a) => a / sceneInfra._baseUnitMultiplier
|
||||
) as [number, number, number],
|
||||
}
|
||||
},
|
||||
'Get horizontal info': async ({
|
||||
selectionRanges,
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { useStore } from '../useStore'
|
||||
import { getNormalisedCoordinates } from '../lib/utils'
|
||||
import Loading from './Loading'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useKclContext } from 'lang/KclSingleton'
|
||||
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
|
||||
import { NetworkHealthState, useNetworkStatus } from './NetworkHealthIndicator'
|
||||
import { butName } from 'lib/cameraControls'
|
||||
import { sendSelectEventToEngine } from 'lib/selections'
|
||||
|
||||
export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
@ -60,50 +59,14 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
setClickCoords({ x, y })
|
||||
}
|
||||
|
||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
|
||||
clientX,
|
||||
clientY,
|
||||
ctrlKey,
|
||||
}) => {
|
||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
if (!videoRef.current) return
|
||||
setButtonDownInStream(undefined)
|
||||
if (state.matches('Sketch')) return
|
||||
if (state.matches('Sketch no face')) return
|
||||
const { x, y } = getNormalisedCoordinates({
|
||||
clientX,
|
||||
clientY,
|
||||
el: videoRef.current,
|
||||
...streamDimensions,
|
||||
})
|
||||
|
||||
const newCmdId = uuidv4()
|
||||
const interaction = ctrlKey ? 'pan' : 'rotate'
|
||||
|
||||
const command: Models['WebSocketRequest_type'] = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'camera_drag_end',
|
||||
interaction,
|
||||
window: { x, y },
|
||||
},
|
||||
cmd_id: newCmdId,
|
||||
}
|
||||
|
||||
if (!didDragInStream) {
|
||||
command.cmd = {
|
||||
type: 'select_with_point',
|
||||
selected_at_window: { x, y },
|
||||
selection_type: 'add',
|
||||
}
|
||||
engineCommandManager.sendSceneCommand(command)
|
||||
} else if (didDragInStream) {
|
||||
command.cmd = {
|
||||
type: 'handle_mouse_drag_end',
|
||||
window: { x, y },
|
||||
}
|
||||
void engineCommandManager.sendSceneCommand(command)
|
||||
} else {
|
||||
engineCommandManager.sendSceneCommand(command)
|
||||
if (!didDragInStream && butName(e).left) {
|
||||
sendSelectEventToEngine(e, videoRef.current, streamDimensions)
|
||||
}
|
||||
|
||||
setDidDragInStream(false)
|
||||
@ -143,6 +106,7 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
|
||||
disablePictureInPicture
|
||||
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
||||
id="video-stream"
|
||||
/>
|
||||
<ClientSideScene cameraControls={settings.context?.cameraControls} />
|
||||
{!isNetworkOkay && !isLoading && (
|
||||
|
@ -3,6 +3,7 @@ import ReactCodeMirror, {
|
||||
Extension,
|
||||
ViewUpdate,
|
||||
keymap,
|
||||
SelectionRange,
|
||||
} from '@uiw/react-codemirror'
|
||||
import { TEST } from 'env'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
@ -75,7 +76,7 @@ export const TextEditor = ({
|
||||
})
|
||||
|
||||
const {
|
||||
context: { selectionRanges, selectionRangeTypeMap },
|
||||
context: { selectionRanges },
|
||||
send,
|
||||
state,
|
||||
} = useModelingContext()
|
||||
@ -91,10 +92,27 @@ export const TextEditor = ({
|
||||
if (isNetworkOkay) kclManager.setCodeAndExecute(newCode)
|
||||
else kclManager.setCode(newCode)
|
||||
} //, []);
|
||||
const lastSelection = useRef('')
|
||||
const onUpdate = (viewUpdate: ViewUpdate) => {
|
||||
if (!editorView) {
|
||||
setEditorView(viewUpdate.view)
|
||||
}
|
||||
const selString = stringifyRanges(
|
||||
viewUpdate?.state?.selection?.ranges || []
|
||||
)
|
||||
if (selString === lastSelection.current) {
|
||||
// onUpdate is noisy and is fired a lot by extensions
|
||||
// since we're only interested in selections changes we can ignore most of these.
|
||||
return
|
||||
}
|
||||
lastSelection.current = selString
|
||||
|
||||
if (
|
||||
// TODO find a less lazy way of getting the last
|
||||
Date.now() - useStore.getState().lastCodeMirrorSelectionUpdatedFromScene <
|
||||
150
|
||||
)
|
||||
return // update triggered by scene selection
|
||||
if (sceneInfra.selected) return // mid drag
|
||||
const ignoreEvents: ModelingMachineEvent['type'][] = [
|
||||
'Equip Line tool',
|
||||
@ -104,7 +122,6 @@ export const TextEditor = ({
|
||||
const eventInfo = processCodeMirrorRanges({
|
||||
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
||||
selectionRanges,
|
||||
selectionRangeTypeMap,
|
||||
isShiftDown,
|
||||
})
|
||||
if (!eventInfo) return
|
||||
@ -226,3 +243,7 @@ export const TextEditor = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function stringifyRanges(ranges: readonly SelectionRange[]): string {
|
||||
return ranges.map(({ to, from }) => `${to}->${from}`).join('&')
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { executeAst, executeCode } from 'useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { KCLError } from './errors'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import {
|
||||
EngineCommandManager,
|
||||
engineCommandManager,
|
||||
@ -14,6 +15,8 @@ import {
|
||||
Program,
|
||||
ProgramMemory,
|
||||
recast,
|
||||
SketchGroup,
|
||||
ExtrudeGroup,
|
||||
} from 'lang/wasm'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
@ -235,7 +238,6 @@ class KclManager {
|
||||
updateCode = false,
|
||||
executionId?: number
|
||||
) {
|
||||
console.trace('executeAst')
|
||||
const currentExecutionId = executionId || Date.now()
|
||||
this._cancelTokens.set(currentExecutionId, false)
|
||||
|
||||
@ -245,6 +247,7 @@ class KclManager {
|
||||
ast,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
})
|
||||
enterEditMode(programMemory)
|
||||
this.isExecuting = false
|
||||
// Check the cancellation token for this execution before applying side effects
|
||||
if (this._cancelTokens.get(currentExecutionId)) {
|
||||
@ -333,6 +336,7 @@ class KclManager {
|
||||
}
|
||||
if (!result.isChange) return
|
||||
const { logs, errors, programMemory, ast } = result
|
||||
enterEditMode(programMemory)
|
||||
this.logs = logs
|
||||
this.kclErrors = errors
|
||||
this.programMemory = programMemory
|
||||
@ -520,3 +524,18 @@ function safteLSSetItem(key: string, value: string) {
|
||||
if (typeof window === 'undefined') return
|
||||
localStorage?.setItem(key, value)
|
||||
}
|
||||
|
||||
function enterEditMode(programMemory: ProgramMemory) {
|
||||
const firstSketchOrExtrudeGroup = Object.values(programMemory.root).find(
|
||||
(node) => node.type === 'ExtrudeGroup' || node.type === 'SketchGroup'
|
||||
) as SketchGroup | ExtrudeGroup
|
||||
firstSketchOrExtrudeGroup &&
|
||||
engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'edit_mode_enter',
|
||||
target: firstSketchOrExtrudeGroup.id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -74,16 +74,56 @@ const mySketch001 = startSketchOn('XY')
|
||||
expect(sketch001).toEqual({
|
||||
type: 'ExtrudeGroup',
|
||||
id: expect.any(String),
|
||||
value: [],
|
||||
value: [
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: '',
|
||||
id: expect.any(String),
|
||||
sourceRange: [77, 102],
|
||||
},
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: '',
|
||||
id: expect.any(String),
|
||||
sourceRange: [108, 132],
|
||||
},
|
||||
],
|
||||
sketchGroupValues: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
from: [0, 0],
|
||||
to: [-1.59, -1.54],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
id: expect.any(String),
|
||||
sourceRange: [77, 102],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ToPoint',
|
||||
from: [-1.59, -1.54],
|
||||
to: [0.46, -5.82],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
id: expect.any(String),
|
||||
sourceRange: [108, 132],
|
||||
},
|
||||
},
|
||||
],
|
||||
height: 2,
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
endCapId: null,
|
||||
startCapId: null,
|
||||
sketchGroupValues: expect.any(Array),
|
||||
xAxis: { x: 1, y: 0, z: 0 },
|
||||
yAxis: { x: 0, y: 1, z: 0 },
|
||||
zAxis: { x: 0, y: 0, z: 1 },
|
||||
startCapId: expect.any(String),
|
||||
endCapId: expect.any(String),
|
||||
__meta: [{ sourceRange: [46, 71] }],
|
||||
})
|
||||
})
|
||||
@ -117,32 +157,149 @@ const sk2 = startSketchOn('XY')
|
||||
{
|
||||
type: 'ExtrudeGroup',
|
||||
id: expect.any(String),
|
||||
value: [],
|
||||
value: [
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: '',
|
||||
id: expect.any(String),
|
||||
sourceRange: [69, 89],
|
||||
},
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: 'p',
|
||||
id: expect.any(String),
|
||||
sourceRange: [95, 118],
|
||||
},
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: '',
|
||||
id: expect.any(String),
|
||||
sourceRange: [124, 143],
|
||||
},
|
||||
],
|
||||
sketchGroupValues: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
from: [0, 0],
|
||||
to: [-2.5, 0],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
id: expect.any(String),
|
||||
sourceRange: [69, 89],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ToPoint',
|
||||
from: [-2.5, 0],
|
||||
to: [0, 10],
|
||||
name: 'p',
|
||||
__geoMeta: {
|
||||
id: expect.any(String),
|
||||
sourceRange: [95, 118],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ToPoint',
|
||||
from: [0, 10],
|
||||
to: [2.5, 0],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
id: expect.any(String),
|
||||
sourceRange: [124, 143],
|
||||
},
|
||||
},
|
||||
],
|
||||
height: 2,
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
endCapId: null,
|
||||
startCapId: null,
|
||||
sketchGroupValues: expect.any(Array),
|
||||
xAxis: { x: 1, y: 0, z: 0 },
|
||||
yAxis: { x: 0, y: 1, z: 0 },
|
||||
zAxis: { x: 0, y: 0, z: 1 },
|
||||
startCapId: expect.any(String),
|
||||
endCapId: expect.any(String),
|
||||
__meta: [{ sourceRange: [38, 63] }],
|
||||
},
|
||||
{
|
||||
type: 'ExtrudeGroup',
|
||||
id: expect.any(String),
|
||||
value: [],
|
||||
value: [
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: '',
|
||||
id: expect.any(String),
|
||||
sourceRange: [374, 394],
|
||||
},
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: 'p',
|
||||
id: expect.any(String),
|
||||
sourceRange: [400, 422],
|
||||
},
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
faceId: expect.any(String),
|
||||
name: '',
|
||||
id: expect.any(String),
|
||||
sourceRange: [428, 447],
|
||||
},
|
||||
],
|
||||
sketchGroupValues: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
from: [0, 0],
|
||||
to: [-2.5, 0],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
id: expect.any(String),
|
||||
sourceRange: [374, 394],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ToPoint',
|
||||
from: [-2.5, 0],
|
||||
to: [0, 3],
|
||||
name: 'p',
|
||||
__geoMeta: {
|
||||
id: expect.any(String),
|
||||
sourceRange: [400, 422],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ToPoint',
|
||||
from: [0, 3],
|
||||
to: [2.5, 0],
|
||||
name: '',
|
||||
__geoMeta: {
|
||||
id: expect.any(String),
|
||||
sourceRange: [428, 447],
|
||||
},
|
||||
},
|
||||
],
|
||||
height: 2,
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1],
|
||||
|
||||
endCapId: null,
|
||||
startCapId: null,
|
||||
sketchGroupValues: expect.any(Array),
|
||||
xAxis: { x: 1, y: 0, z: 0 },
|
||||
yAxis: { x: 0, y: 1, z: 0 },
|
||||
zAxis: { x: 0, y: 0, z: 1 },
|
||||
startCapId: expect.any(String),
|
||||
endCapId: expect.any(String),
|
||||
__meta: [{ sourceRange: [343, 368] }],
|
||||
},
|
||||
])
|
||||
|
@ -12,8 +12,10 @@ import {
|
||||
addSketchTo,
|
||||
giveSketchFnCallTag,
|
||||
moveValueIntoNewVariable,
|
||||
sketchOnExtrudedFace,
|
||||
} from './modifyAst'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { getNodePathFromSourceRange } from './queryAst'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
|
||||
@ -274,3 +276,89 @@ const yo2 = hmm([identifierGuy + 5])`
|
||||
expect(newCode).toContain(`const yo2 = hmm([newVar])`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('testing sketchOnExtrudedFace', () => {
|
||||
test('it should be able to extrude on regular segments', async () => {
|
||||
const code = `const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([3.58, 2.06], %)
|
||||
|> line([9.7, 9.19], %)
|
||||
|> line([8.62, -9.57], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const snippet = `line([9.7, 9.19], %)`
|
||||
const range: [number, number] = [
|
||||
code.indexOf(snippet),
|
||||
code.indexOf(snippet) + snippet.length,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
|
||||
const { modifiedAst } = sketchOnExtrudedFace(ast, pathToNode, programMemory)
|
||||
const newCode = recast(modifiedAst)
|
||||
expect(newCode).toContain(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([3.58, 2.06], %)
|
||||
|> line([9.7, 9.19], %, 'seg01')
|
||||
|> line([8.62, -9.57], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)
|
||||
const part002 = startSketchOn(part001, 'seg01')`)
|
||||
})
|
||||
test('it should be able to extrude on close segments', async () => {
|
||||
const code = `const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([3.58, 2.06], %)
|
||||
|> line([9.7, 9.19], %)
|
||||
|> line([8.62, -9.57], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const snippet = `close(%)`
|
||||
const range: [number, number] = [
|
||||
code.indexOf(snippet),
|
||||
code.indexOf(snippet) + snippet.length,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
|
||||
const { modifiedAst } = sketchOnExtrudedFace(ast, pathToNode, programMemory)
|
||||
const newCode = recast(modifiedAst)
|
||||
expect(newCode).toContain(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([3.58, 2.06], %)
|
||||
|> line([9.7, 9.19], %)
|
||||
|> line([8.62, -9.57], %)
|
||||
|> close(%, 'seg01')
|
||||
|> extrude(5 + 7, %)
|
||||
const part002 = startSketchOn(part001, 'seg01')`)
|
||||
})
|
||||
test('it should be able to extrude on start-end caps', async () => {
|
||||
const code = `const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([3.58, 2.06], %)
|
||||
|> line([9.7, 9.19], %)
|
||||
|> line([8.62, -9.57], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const snippet = `startProfileAt([3.58, 2.06], %)`
|
||||
const range: [number, number] = [
|
||||
code.indexOf(snippet),
|
||||
code.indexOf(snippet) + snippet.length,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
|
||||
const { modifiedAst } = sketchOnExtrudedFace(
|
||||
ast,
|
||||
pathToNode,
|
||||
programMemory,
|
||||
'end'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
expect(newCode).toContain(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([3.58, 2.06], %)
|
||||
|> line([9.7, 9.19], %)
|
||||
|> line([8.62, -9.57], %)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)
|
||||
const part002 = startSketchOn(part001, 'END')`)
|
||||
})
|
||||
})
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { ToolTip } from '../useStore'
|
||||
import { Selection } from 'lib/selections'
|
||||
import {
|
||||
Program,
|
||||
@ -64,7 +63,6 @@ export function addStartProfileAt(
|
||||
pathToNode: PathToNode,
|
||||
at: [number, number]
|
||||
): { modifiedAst: Program; pathToNode: PathToNode } {
|
||||
console.log('addStartProfileAt called')
|
||||
const variableDeclaration = getNodeFromPath<VariableDeclaration>(
|
||||
node,
|
||||
pathToNode,
|
||||
@ -317,12 +315,12 @@ export function extrudeSketch(
|
||||
export function sketchOnExtrudedFace(
|
||||
node: Program,
|
||||
pathToNode: PathToNode,
|
||||
programMemory: ProgramMemory
|
||||
programMemory: ProgramMemory,
|
||||
cap: 'none' | 'start' | 'end' = 'none'
|
||||
): { modifiedAst: Program; pathToNode: PathToNode } {
|
||||
let _node = { ...node }
|
||||
const newSketchName = findUniqueName(node, 'part')
|
||||
const { node: oldSketchNode, shallowPath: pathToOldSketch } =
|
||||
getNodeFromPath<VariableDeclarator>(
|
||||
const { node: oldSketchNode } = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'VariableDeclarator',
|
||||
@ -335,6 +333,8 @@ export function sketchOnExtrudedFace(
|
||||
'CallExpression'
|
||||
)
|
||||
|
||||
let _tag = ''
|
||||
if (cap === 'none') {
|
||||
const { modifiedAst, tag } = addTagForSketchOnFace(
|
||||
{
|
||||
previousProgramMemory: programMemory,
|
||||
@ -343,34 +343,34 @@ export function sketchOnExtrudedFace(
|
||||
},
|
||||
expression.callee.name
|
||||
)
|
||||
_tag = tag
|
||||
_node = modifiedAst
|
||||
} else {
|
||||
_tag = cap.toUpperCase()
|
||||
}
|
||||
|
||||
const newSketch = createVariableDeclaration(
|
||||
newSketchName,
|
||||
createPipeExpression([
|
||||
createCallExpressionStdLib('startSketchAt', [
|
||||
createArrayExpression([createLiteral(0), createLiteral(0)]),
|
||||
]),
|
||||
createCallExpressionStdLib('lineTo', [
|
||||
createArrayExpression([createLiteral(1), createLiteral(1)]),
|
||||
createPipeSubstitution(),
|
||||
]),
|
||||
createCallExpression('transform', [
|
||||
createCallExpressionStdLib('getExtrudeWallTransform', [
|
||||
createLiteral(tag),
|
||||
createCallExpressionStdLib('startSketchOn', [
|
||||
createIdentifier(oldSketchName),
|
||||
]),
|
||||
createPipeSubstitution(),
|
||||
]),
|
||||
createLiteral(_tag),
|
||||
]),
|
||||
'const'
|
||||
)
|
||||
const expressionIndex = getLastIndex(pathToOldSketch)
|
||||
|
||||
const expressionIndex = pathToNode[1][0] as number
|
||||
_node.body.splice(expressionIndex + 1, 0, newSketch)
|
||||
const newpathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[expressionIndex + 1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
]
|
||||
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode: [...pathToNode.slice(0, -1), [expressionIndex, 'index']],
|
||||
pathToNode: newpathToNode,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +150,7 @@ log(5, myVar)
|
||||
const recasted = recast(ast)
|
||||
expect(recasted.trim()).toBe(code.trim())
|
||||
})
|
||||
it('recast long object exectution', () => {
|
||||
it('recast long object execution', () => {
|
||||
const code = `const three = 3
|
||||
const yo = {
|
||||
aStr: 'str',
|
||||
@ -163,7 +163,7 @@ const yo = {
|
||||
const recasted = recast(ast)
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('recast short object exectution', () => {
|
||||
it('recast short object execution', () => {
|
||||
const code = `const yo = { key: 'val' }
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
|
@ -13,6 +13,10 @@ interface CommandInfo {
|
||||
range: SourceRange
|
||||
pathToNode: PathToNode
|
||||
parentId?: string
|
||||
additionalData?: {
|
||||
type: 'cap'
|
||||
info: 'start' | 'end'
|
||||
}
|
||||
}
|
||||
|
||||
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
||||
@ -1069,14 +1073,42 @@ export class EngineCommandManager {
|
||||
} as const
|
||||
this.artifactMap[id] = artifact
|
||||
if (
|
||||
command.commandType === 'entity_linear_pattern' ||
|
||||
command.commandType === 'entity_circular_pattern'
|
||||
(command.commandType === 'entity_linear_pattern' &&
|
||||
modelingResponse.type === 'entity_linear_pattern') ||
|
||||
(command.commandType === 'entity_circular_pattern' &&
|
||||
modelingResponse.type === 'entity_circular_pattern')
|
||||
) {
|
||||
const entities = (modelingResponse as any)?.data?.entity_ids
|
||||
const entities = modelingResponse.data.entity_ids
|
||||
entities?.forEach((entity: string) => {
|
||||
this.artifactMap[entity] = artifact
|
||||
})
|
||||
}
|
||||
if (
|
||||
command?.commandType === 'solid3d_get_extrusion_face_info' &&
|
||||
modelingResponse.type === 'solid3d_get_extrusion_face_info'
|
||||
) {
|
||||
console.log('modelingResposne', modelingResponse)
|
||||
const parent = this.artifactMap[command?.parentId || '']
|
||||
modelingResponse.data.faces.forEach((face) => {
|
||||
if (face.cap !== 'none' && face.face_id && parent) {
|
||||
this.artifactMap[face.face_id] = {
|
||||
...parent,
|
||||
commandType: 'solid3d_get_extrusion_face_info',
|
||||
additionalData: {
|
||||
type: 'cap',
|
||||
info: face.cap === 'bottom' ? 'start' : 'end',
|
||||
},
|
||||
}
|
||||
}
|
||||
const curveArtifact = this.artifactMap[face?.curve_id || '']
|
||||
if (curveArtifact && face?.face_id) {
|
||||
this.artifactMap[face.face_id] = {
|
||||
...curveArtifact,
|
||||
commandType: 'solid3d_get_extrusion_face_info',
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
resolve({
|
||||
id,
|
||||
commandType: command.commandType,
|
||||
@ -1388,12 +1420,6 @@ export class EngineCommandManager {
|
||||
const promise = new Promise((_resolve, reject) => {
|
||||
resolve = _resolve
|
||||
})
|
||||
const getParentId = (): string | undefined => {
|
||||
if (command.type === 'extend_path') {
|
||||
return command.path
|
||||
}
|
||||
// TODO handle other commands that have a parent
|
||||
}
|
||||
const pathToNode = ast
|
||||
? getNodePathFromSourceRange(ast, range || [0, 0])
|
||||
: []
|
||||
@ -1402,7 +1428,6 @@ export class EngineCommandManager {
|
||||
pathToNode,
|
||||
type: 'pending',
|
||||
commandType: command.type,
|
||||
parentId: getParentId(),
|
||||
promise,
|
||||
resolve,
|
||||
}
|
||||
@ -1419,10 +1444,14 @@ export class EngineCommandManager {
|
||||
resolve = _resolve
|
||||
})
|
||||
const getParentId = (): string | undefined => {
|
||||
if (command.type === 'extend_path') {
|
||||
return command.path
|
||||
if (command.type === 'extend_path') return command.path
|
||||
if (command.type === 'solid3d_get_extrusion_face_info') {
|
||||
const edgeArtifact = this.artifactMap[command.edge_id]
|
||||
// edges's parent id is to the original "start_path" artifact
|
||||
if (edgeArtifact?.parentId) return edgeArtifact.parentId
|
||||
}
|
||||
// TODO handle other commands that have a parent
|
||||
if (command.type === 'close_path') return command.path_id
|
||||
// handle other commands that have a parent here
|
||||
}
|
||||
const pathToNode = ast
|
||||
? getNodePathFromSourceRange(ast, range || [0, 0])
|
||||
|
@ -1155,11 +1155,14 @@ export function addTagForSketchOnFace(
|
||||
a: ModifyAstBase,
|
||||
expressionName: string
|
||||
) {
|
||||
if (expressionName === 'close') {
|
||||
return addTag(1)(a)
|
||||
}
|
||||
if (expressionName in sketchLineHelperMap) {
|
||||
const { addTag } = sketchLineHelperMap[expressionName]
|
||||
return addTag(a)
|
||||
}
|
||||
throw new Error('not a sketch line helper')
|
||||
throw new Error(`"${expressionName}" is not a sketch line helper`)
|
||||
}
|
||||
|
||||
function isAngleLiteral(lineArugement: Value): boolean {
|
||||
@ -1174,7 +1177,7 @@ function isAngleLiteral(lineArugement: Value): boolean {
|
||||
|
||||
type addTagFn = (a: ModifyAstBase) => { modifiedAst: Program; tag: string }
|
||||
|
||||
function addTag(): addTagFn {
|
||||
function addTag(tagIndex = 2): addTagFn {
|
||||
return ({ node, pathToNode }) => {
|
||||
const _node = { ...node }
|
||||
const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
|
||||
@ -1184,12 +1187,12 @@ function addTag(): addTagFn {
|
||||
)
|
||||
// Tag is always 3rd expression now, using arg index feels brittle
|
||||
// but we can come up with a better way to identify tag later.
|
||||
const thirdArg = primaryCallExp.arguments?.[2]
|
||||
const thirdArg = primaryCallExp.arguments?.[tagIndex]
|
||||
const tagLiteral =
|
||||
thirdArg || (createLiteral(findUniqueName(_node, 'seg', 2)) as Literal)
|
||||
const isTagExisting = !!thirdArg
|
||||
if (!isTagExisting) {
|
||||
primaryCallExp.arguments[2] = tagLiteral
|
||||
primaryCallExp.arguments[tagIndex] = tagLiteral
|
||||
}
|
||||
if ('value' in tagLiteral) {
|
||||
// Now TypeScript knows tagLiteral has a value property
|
||||
|
@ -66,6 +66,7 @@ export type { Position } from '../wasm-lib/kcl/bindings/Position'
|
||||
export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
|
||||
export type { Path } from '../wasm-lib/kcl/bindings/Path'
|
||||
export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
|
||||
export type { ExtrudeGroup } from '../wasm-lib/kcl/bindings/ExtrudeGroup'
|
||||
export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
||||
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
||||
|
||||
|
@ -39,7 +39,7 @@ export interface MouseGuard {
|
||||
rotate: MouseGuardHandler
|
||||
}
|
||||
|
||||
const butName = (e: React.MouseEvent) => ({
|
||||
export const butName = (e: React.MouseEvent) => ({
|
||||
middle: !!(e.buttons & 4) || e.button === 1,
|
||||
right: !!(e.buttons & 2) || e.button === 2,
|
||||
left: !!(e.buttons & 1) || e.button === 0,
|
||||
|
@ -117,7 +117,7 @@ export const modelingMachineConfig: CommandSetConfig<
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['face'],
|
||||
selectionTypes: ['extrude-wall', 'start-cap', 'end-cap'],
|
||||
multiple: false, // TODO: multiple selection
|
||||
required: true,
|
||||
skip: true,
|
||||
|
@ -6,7 +6,7 @@ import { v4 as uuidv4 } from 'uuid'
|
||||
import { EditorSelection } from '@codemirror/state'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import { SelectionRange } from '@uiw/react-codemirror'
|
||||
import { isOverlap } from 'lib/utils'
|
||||
import { getNormalisedCoordinates, isOverlap } from 'lib/utils'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
import { Program } from 'lang/wasm'
|
||||
import {
|
||||
@ -22,70 +22,12 @@ import {
|
||||
getParentGroup,
|
||||
PROFILE_START,
|
||||
} from 'clientSideScene/sceneEntities'
|
||||
import { Mesh } from 'three'
|
||||
import { Mesh, Object3D, Object3DEventMap } from 'three'
|
||||
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
|
||||
|
||||
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
||||
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
||||
|
||||
/*
|
||||
How selections work is complex due to the nature that we rely on the engine
|
||||
to tell what has been selected after we send a click command. But than the
|
||||
app needs these selections to be based on cursors, therefore the app must
|
||||
be in control of selections. On top of that because we need to set cursor
|
||||
positions in code-mirror for selections, both from app logic, and still
|
||||
allow the user to add multiple cursors like a normal editor, it's best to
|
||||
let code mirror control cursor positions and associate those source ranges
|
||||
with entity ids from code-mirror events later.
|
||||
|
||||
So it's a lot of back and forth. conceptually the back and forth is:
|
||||
|
||||
1) we send a click command to the engine
|
||||
2) the engine sends back ids of entities that were clicked
|
||||
3) we associate that source ranges with those ids
|
||||
4) we set the codemirror selection based on those source ranges (taking
|
||||
into account if the user is holding shift to add to current selections
|
||||
or not). we also create and remember a SelectionRangeTypeMap
|
||||
5) Code mirror fires a an event that cursors have changed, we loop through
|
||||
these ranges and associate them with entity ids again with the ArtifactMap,
|
||||
but also we can pick up selection types using the SelectionRangeTypeMap
|
||||
6) we clear all previous selections in the engine and set the new ones
|
||||
|
||||
The above is less likely to get stale but below is some more details,
|
||||
because this wonders all over the code-base, I've tried to centeralise it
|
||||
by putting relevant utils in this file. All of the functions below are
|
||||
pure with the exception of getEventForSelectWithPoint which makes a call
|
||||
to the engine, but it's a query call (not mutation) so I'm okay with this.
|
||||
Actual side effects that change cursors or tell the engine what's selected
|
||||
are still done throughout the in their relevant parts in the codebase.
|
||||
|
||||
In detail:
|
||||
|
||||
1) Click commands are mostly sent in stream.tsx search for
|
||||
"select_with_point"
|
||||
2) The handler for when the engine sends back entity ids calls
|
||||
getEventForSelectWithPoint, it fires an XState event to update our
|
||||
selections is xstate context
|
||||
3 and 4) The XState handler for the above uses handleSelectionBatch and
|
||||
handleSelectionWithShift to update the selections in xstate context as
|
||||
well as returning our SelectionRangeTypeMap and a codeMirror specific
|
||||
event to be dispatched.
|
||||
5) The codeMirror handler for changes to the cursor uses
|
||||
processCodeMirrorRanges to associate the ranges back with their original
|
||||
types and the entity ids (the id can vary depending on the type, as
|
||||
there's only one source range for a given segment, but depending on if
|
||||
the user selected the segment directly or the vertex, the id will be
|
||||
different)
|
||||
6) We take all of the ids and create events for the engine with
|
||||
resetAndSetEngineEntitySelectionCmds
|
||||
|
||||
An important note is that if a user changes the cursor directly themselves
|
||||
then they skip directly to step 5, And these selections get a type of
|
||||
"default".
|
||||
|
||||
There are a few more nuances than this, but best to find them in the code.
|
||||
*/
|
||||
|
||||
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
|
||||
|
||||
export type Selection = {
|
||||
@ -93,7 +35,9 @@ export type Selection = {
|
||||
| 'default'
|
||||
| 'line-end'
|
||||
| 'line-mid'
|
||||
| 'face'
|
||||
| 'extrude-wall'
|
||||
| 'start-cap'
|
||||
| 'end-cap'
|
||||
| 'point'
|
||||
| 'edge'
|
||||
| 'line'
|
||||
@ -106,15 +50,6 @@ export type Selections = {
|
||||
codeBasedSelections: Selection[]
|
||||
}
|
||||
|
||||
export interface SelectionRangeTypeMap {
|
||||
[key: number]: Selection['type']
|
||||
}
|
||||
|
||||
interface RangeAndId {
|
||||
id: string
|
||||
range: SourceRange
|
||||
}
|
||||
|
||||
export async function getEventForSelectWithPoint(
|
||||
{
|
||||
data,
|
||||
@ -139,8 +74,32 @@ export async function getEventForSelectWithPoint(
|
||||
},
|
||||
}
|
||||
}
|
||||
const sourceRange = engineCommandManager.artifactMap[data.entity_id]?.range
|
||||
if (engineCommandManager.artifactMap[data.entity_id]) {
|
||||
const _artifact = engineCommandManager.artifactMap[data.entity_id]
|
||||
const sourceRange = _artifact?.range
|
||||
if (_artifact) {
|
||||
if (_artifact.commandType === 'solid3d_get_extrusion_face_info') {
|
||||
if (_artifact?.additionalData)
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: {
|
||||
range: sourceRange,
|
||||
type:
|
||||
_artifact?.additionalData.info === 'end'
|
||||
? 'end-cap'
|
||||
: 'start-cap',
|
||||
},
|
||||
},
|
||||
}
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: sourceRange, type: 'extrude-wall' },
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
@ -148,46 +107,17 @@ export async function getEventForSelectWithPoint(
|
||||
selection: { range: sourceRange, type: 'default' },
|
||||
},
|
||||
}
|
||||
}
|
||||
if (!sketchEnginePathId) return null
|
||||
// selected a vertex
|
||||
const res = await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'path_get_curve_uuids_for_vertices',
|
||||
vertex_ids: [data.entity_id],
|
||||
path_id: sketchEnginePathId,
|
||||
},
|
||||
})
|
||||
const curveIds = res?.data?.data?.curve_ids
|
||||
const ranges: RangeAndId[] = curveIds
|
||||
.map(
|
||||
(id: string): RangeAndId => ({
|
||||
id,
|
||||
range: engineCommandManager.artifactMap[id].range,
|
||||
})
|
||||
)
|
||||
.sort((a: RangeAndId, b: RangeAndId) => a.range[0] - b.range[0])
|
||||
// default to the head of the curve selected
|
||||
const _sourceRange = ranges?.[0].range
|
||||
const artifact = engineCommandManager.artifactMap[ranges?.[0]?.id]
|
||||
if (artifact.type === 'result') {
|
||||
artifact.headVertexId = data.entity_id
|
||||
}
|
||||
} else {
|
||||
// if we don't recognise the entity, select nothing
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
// line-end is used to indicate that headVertexId should be sent to the engine as "selected"
|
||||
// not the whole curve
|
||||
selection: { range: _sourceRange, type: 'line-end' },
|
||||
},
|
||||
data: { selectionType: 'singleCodeCursor' },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getEventForSegmentSelection(
|
||||
obj: any
|
||||
obj: Object3D<Object3DEventMap>
|
||||
): ModelingMachineEvent | null {
|
||||
const group = getParentGroup(obj, [
|
||||
STRAIGHT_SEGMENT,
|
||||
@ -231,107 +161,54 @@ export function handleSelectionBatch({
|
||||
}: {
|
||||
selections: Selections
|
||||
}): {
|
||||
selectionRangeTypeMap: SelectionRangeTypeMap
|
||||
codeMirrorSelection?: EditorSelection
|
||||
engineEvents: Models['WebSocketRequest_type'][]
|
||||
codeMirrorSelection: EditorSelection
|
||||
otherSelections: Axis[]
|
||||
updateSceneObjectColors: () => void
|
||||
} {
|
||||
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
|
||||
const selectionRangeTypeMap: SelectionRangeTypeMap = {}
|
||||
const engineEvents: Models['WebSocketRequest_type'][] =
|
||||
resetAndSetEngineEntitySelectionCmds(
|
||||
codeToIdSelections(selections.codeBasedSelections)
|
||||
)
|
||||
selections.codeBasedSelections.forEach(({ range, type }) => {
|
||||
if (range?.[1]) {
|
||||
ranges.push(EditorSelection.cursor(range[1]))
|
||||
selectionRangeTypeMap[range[1]] = type
|
||||
}
|
||||
})
|
||||
if (ranges.length)
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
engineEvents,
|
||||
codeMirrorSelection: EditorSelection.create(
|
||||
ranges,
|
||||
selections.codeBasedSelections.length - 1
|
||||
),
|
||||
otherSelections: selections.otherSelections,
|
||||
updateSceneObjectColors: () =>
|
||||
updateSceneObjectColors(selections.codeBasedSelections),
|
||||
}
|
||||
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
codeMirrorSelection: EditorSelection.create(
|
||||
[EditorSelection.cursor(kclManager.code.length)],
|
||||
0
|
||||
),
|
||||
engineEvents,
|
||||
otherSelections: selections.otherSelections,
|
||||
updateSceneObjectColors: () =>
|
||||
updateSceneObjectColors(selections.codeBasedSelections),
|
||||
}
|
||||
}
|
||||
|
||||
export function handleSelectionWithShift({
|
||||
codeSelection,
|
||||
otherSelection,
|
||||
currentSelections,
|
||||
isShiftDown,
|
||||
}: {
|
||||
codeSelection?: Selection
|
||||
otherSelection?: Axis
|
||||
currentSelections: Selections
|
||||
isShiftDown: boolean
|
||||
}): {
|
||||
selectionRangeTypeMap: SelectionRangeTypeMap
|
||||
otherSelections: Axis[]
|
||||
codeMirrorSelection?: EditorSelection
|
||||
} {
|
||||
const code = kclManager.code
|
||||
if (codeSelection && otherSelection) {
|
||||
throw new Error('cannot have both code and other selection')
|
||||
}
|
||||
if (!codeSelection && !otherSelection) {
|
||||
return handleSelectionBatch({
|
||||
selections: {
|
||||
otherSelections: [],
|
||||
codeBasedSelections: [
|
||||
{
|
||||
range: [0, code.length ? code.length : 0],
|
||||
type: 'default',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
}
|
||||
if (otherSelection) {
|
||||
return handleSelectionBatch({
|
||||
selections: {
|
||||
codeBasedSelections: isShiftDown
|
||||
? currentSelections.codeBasedSelections
|
||||
: [
|
||||
{
|
||||
range: [0, code.length ? code.length : 0],
|
||||
type: 'default',
|
||||
},
|
||||
],
|
||||
otherSelections: [otherSelection],
|
||||
},
|
||||
})
|
||||
}
|
||||
const isEndOfFileDumbySelection =
|
||||
currentSelections.codeBasedSelections.length === 1 &&
|
||||
currentSelections.codeBasedSelections[0].range[0] === kclManager.code.length
|
||||
const newCodeBasedSelections = !isShiftDown
|
||||
? [codeSelection!]
|
||||
: isEndOfFileDumbySelection
|
||||
? [codeSelection!]
|
||||
: [...currentSelections.codeBasedSelections, codeSelection!]
|
||||
const selections: Selections = {
|
||||
otherSelections: isShiftDown ? currentSelections.otherSelections : [],
|
||||
codeBasedSelections: newCodeBasedSelections,
|
||||
}
|
||||
return handleSelectionBatch({ selections })
|
||||
}
|
||||
|
||||
type SelectionToEngine = { type: Selection['type']; id: string }
|
||||
|
||||
export function processCodeMirrorRanges({
|
||||
codeMirrorRanges,
|
||||
selectionRanges,
|
||||
selectionRangeTypeMap,
|
||||
isShiftDown,
|
||||
}: {
|
||||
codeMirrorRanges: readonly SelectionRange[]
|
||||
selectionRanges: Selections
|
||||
selectionRangeTypeMap: SelectionRangeTypeMap
|
||||
isShiftDown: boolean
|
||||
}): null | {
|
||||
modelingEvent: ModelingMachineEvent
|
||||
@ -349,41 +226,13 @@ export function processCodeMirrorRanges({
|
||||
if (!isChange) return null
|
||||
const codeBasedSelections: Selections['codeBasedSelections'] =
|
||||
codeMirrorRanges.map(({ from, to }) => {
|
||||
if (selectionRangeTypeMap[to]) {
|
||||
return {
|
||||
type: selectionRangeTypeMap[to],
|
||||
range: [from, to],
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'default',
|
||||
range: [from, to],
|
||||
}
|
||||
})
|
||||
const idBasedSelections: SelectionToEngine[] = codeBasedSelections
|
||||
.flatMap(({ type, range }): null | SelectionToEngine[] => {
|
||||
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
||||
const entriesWithOverlap = Object.entries(
|
||||
engineCommandManager.artifactMap || {}
|
||||
).filter(([_, artifact]) => {
|
||||
return artifact.range && isOverlap(artifact.range, range)
|
||||
? artifact
|
||||
: false
|
||||
})
|
||||
if (entriesWithOverlap.length) {
|
||||
return entriesWithOverlap.map(([id, artifact]) => ({
|
||||
type,
|
||||
id:
|
||||
type === 'line-end' &&
|
||||
artifact.type === 'result' &&
|
||||
artifact.headVertexId
|
||||
? artifact.headVertexId
|
||||
: id,
|
||||
}))
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter(Boolean) as any
|
||||
const idBasedSelections: SelectionToEngine[] =
|
||||
codeToIdSelections(codeBasedSelections)
|
||||
|
||||
if (!selectionRanges) return null
|
||||
updateSceneObjectColors(codeBasedSelections)
|
||||
@ -486,24 +335,21 @@ export type CommonASTNode = {
|
||||
ast: Program
|
||||
}
|
||||
|
||||
export function buildCommonNodeFromSelection(
|
||||
selectionRanges: Selections,
|
||||
i: number
|
||||
) {
|
||||
function buildCommonNodeFromSelection(selectionRanges: Selections, i: number) {
|
||||
return {
|
||||
selection: selectionRanges.codeBasedSelections[i],
|
||||
ast: kclManager.ast,
|
||||
}
|
||||
}
|
||||
|
||||
export function nodeHasExtrude(node: CommonASTNode) {
|
||||
function nodeHasExtrude(node: CommonASTNode) {
|
||||
return doesPipeHaveCallExp({
|
||||
calleeName: 'extrude',
|
||||
...node,
|
||||
})
|
||||
}
|
||||
|
||||
export function nodeHasClose(node: CommonASTNode) {
|
||||
function nodeHasClose(node: CommonASTNode) {
|
||||
return doesPipeHaveCallExp({
|
||||
calleeName: 'close',
|
||||
...node,
|
||||
@ -521,7 +367,7 @@ export function canExtrudeSelection(selection: Selections) {
|
||||
)
|
||||
}
|
||||
|
||||
export function canExtrudeSelectionItem(selection: Selections, i: number) {
|
||||
function canExtrudeSelectionItem(selection: Selections, i: number) {
|
||||
const commonNode = buildCommonNodeFromSelection(selection, i)
|
||||
|
||||
return (
|
||||
@ -547,7 +393,7 @@ export function getSelectionType(
|
||||
return selection.codeBasedSelections
|
||||
.map((s, i) => {
|
||||
if (canExtrudeSelectionItem(selection, i)) {
|
||||
return ['face', 1] as ResolvedSelectionType // This is implicitly determining what a face is, which is bad
|
||||
return ['extrude-wall', 1] as ResolvedSelectionType // This is implicitly determining what a face is, which is bad
|
||||
} else {
|
||||
return ['other', 1] as ResolvedSelectionType
|
||||
}
|
||||
@ -590,3 +436,100 @@ export function canSubmitSelectionArg(
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function codeToIdSelections(
|
||||
codeBasedSelections: Selection[]
|
||||
): SelectionToEngine[] {
|
||||
return codeBasedSelections
|
||||
.flatMap(({ type, range, ...rest }): null | SelectionToEngine[] => {
|
||||
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
||||
const entriesWithOverlap = Object.entries(
|
||||
engineCommandManager.artifactMap || {}
|
||||
)
|
||||
.map(([id, artifact]) => {
|
||||
return artifact.range && isOverlap(artifact.range, range)
|
||||
? {
|
||||
artifact,
|
||||
selection: { type, range, ...rest },
|
||||
id,
|
||||
}
|
||||
: false
|
||||
})
|
||||
.filter(Boolean)
|
||||
let bestCandidate
|
||||
entriesWithOverlap.forEach((entry) => {
|
||||
if (!entry) return
|
||||
if (
|
||||
type === 'default' &&
|
||||
entry.artifact.commandType === 'extend_path'
|
||||
) {
|
||||
bestCandidate = entry
|
||||
return
|
||||
}
|
||||
if (
|
||||
type === 'start-cap' &&
|
||||
entry.artifact.commandType === 'solid3d_get_extrusion_face_info' &&
|
||||
entry?.artifact?.additionalData?.info === 'start'
|
||||
) {
|
||||
bestCandidate = entry
|
||||
return
|
||||
}
|
||||
if (
|
||||
type === 'end-cap' &&
|
||||
entry.artifact.commandType === 'solid3d_get_extrusion_face_info' &&
|
||||
entry?.artifact?.additionalData?.info === 'end'
|
||||
) {
|
||||
bestCandidate = entry
|
||||
return
|
||||
}
|
||||
if (
|
||||
type === 'extrude-wall' &&
|
||||
entry.artifact.commandType === 'solid3d_get_extrusion_face_info'
|
||||
) {
|
||||
bestCandidate = entry
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
if (bestCandidate) {
|
||||
const _bestCandidate = bestCandidate as {
|
||||
artifact: any
|
||||
selection: any
|
||||
id: string
|
||||
}
|
||||
return [
|
||||
{
|
||||
type,
|
||||
id: _bestCandidate.id,
|
||||
},
|
||||
]
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter(Boolean) as any
|
||||
}
|
||||
|
||||
export function sendSelectEventToEngine(
|
||||
e: MouseEvent | React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||
el: HTMLVideoElement,
|
||||
streamDimensions: { streamWidth: number; streamHeight: number }
|
||||
) {
|
||||
const { x, y } = getNormalisedCoordinates({
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
el,
|
||||
...streamDimensions,
|
||||
})
|
||||
const result: Promise<Models['SelectWithPoint_type']> = engineCommandManager
|
||||
.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'select_with_point',
|
||||
selected_at_window: { x, y },
|
||||
selection_type: 'add',
|
||||
},
|
||||
cmd_id: uuidv4(),
|
||||
})
|
||||
.then((res) => res.data.data)
|
||||
return result
|
||||
}
|
||||
|
@ -1,13 +1,6 @@
|
||||
import { PathToNode, VariableDeclarator } from 'lang/wasm'
|
||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||
import {
|
||||
Axis,
|
||||
Selection,
|
||||
SelectionRangeTypeMap,
|
||||
Selections,
|
||||
} from 'lib/selections'
|
||||
import { Axis, Selection, Selections } from 'lib/selections'
|
||||
import { assign, createMachine } from 'xstate'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { kclManager } from 'lang/KclSingleton'
|
||||
import {
|
||||
@ -26,7 +19,6 @@ import {
|
||||
} from 'components/Toolbar/EqualLength'
|
||||
import { addStartProfileAt, extrudeSketch } from 'lang/modifyAst'
|
||||
import { getNodeFromPath } from '../lang/queryAst'
|
||||
import { CallExpression, PipeExpression } from '../lang/wasm'
|
||||
import {
|
||||
applyConstraintEqualAngle,
|
||||
equalAngleInfo,
|
||||
@ -45,10 +37,10 @@ import { ModelingCommandSchema } from 'lib/commandBarConfigs/modelingCommandConf
|
||||
import {
|
||||
DefaultPlaneStr,
|
||||
sceneEntitiesManager,
|
||||
quaternionFromSketchGroup,
|
||||
sketchGroupFromPathToNode,
|
||||
} from 'clientSideScene/sceneEntities'
|
||||
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
||||
import { Vector3 } from 'three'
|
||||
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
||||
|
||||
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
||||
|
||||
@ -70,6 +62,13 @@ export type SetSelections =
|
||||
selection: Selections
|
||||
}
|
||||
|
||||
export interface SketchDetails {
|
||||
sketchPathToNode: PathToNode
|
||||
zAxis: [number, number, number]
|
||||
yAxis: [number, number, number]
|
||||
origin: [number, number, number]
|
||||
}
|
||||
|
||||
export type ModelingMachineEvent =
|
||||
| {
|
||||
type: 'Enter sketch'
|
||||
@ -77,9 +76,24 @@ export type ModelingMachineEvent =
|
||||
forceNewSketch?: boolean
|
||||
}
|
||||
}
|
||||
| { type: 'Sketch On Face' }
|
||||
| {
|
||||
type: 'Select default plane'
|
||||
data: { plane: DefaultPlaneStr; normal: [number, number, number] }
|
||||
data: {
|
||||
zAxis: [number, number, number]
|
||||
yAxis: [number, number, number]
|
||||
} & (
|
||||
| {
|
||||
type: 'defaultPlane'
|
||||
plane: DefaultPlaneStr
|
||||
}
|
||||
| {
|
||||
type: 'extrudeFace'
|
||||
position: [number, number, number]
|
||||
extrudeSegmentPathToNode: PathToNode
|
||||
cap: 'start' | 'end' | 'none'
|
||||
}
|
||||
)
|
||||
}
|
||||
| { type: 'Set selection'; data: SetSelections }
|
||||
| { type: 'Sketch no face' }
|
||||
@ -109,18 +123,15 @@ export type ModelingMachineEvent =
|
||||
| { type: 'Equip Line tool' }
|
||||
| { type: 'Equip tangential arc to' }
|
||||
| {
|
||||
type: 'done.invoke.animate-to-face'
|
||||
data: {
|
||||
sketchPathToNode: PathToNode
|
||||
sketchNormalBackUp: [number, number, number] | null
|
||||
}
|
||||
type: 'done.invoke.animate-to-face' | 'done.invoke.animate-to-sketch'
|
||||
data: SketchDetails
|
||||
}
|
||||
|
||||
export type MoveDesc = { line: number; snippet: string }
|
||||
|
||||
export const modelingMachine = createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBLWFJDWV5hVSZZTrlBUKjS1FZiUrDeWVDasaQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iaF07bCNLiRYlGiZUyZDbFYQzfIaLSQ5Sw0TGZQnM6eNodLoEW73J6wV7sD5UQyRZisMbcP4IQRrGiSdRmfIqUxrCrQlbKcoQ4RHDTWUx5DHNLGXXHXPjsB4AVwwX0psXGCSEFThkNEymFGlECkM0MM+0ZMgUa1MwuypgRhlFHlaEpufBYD3YiuiVN+qrpdg0klMwkFaU0ermwuhwlUkiBhxUyKUazt5za3mJH2IZEomFTb0+9BGnpVoESRrNkmMga0ahMplkhqOZVL+sM4ksCj1SfFOZJ70k3Y+AEkrj0AkEugNwvnvoWad7DIGyuqOe2rPNDRCLIKaKJa6JBXrRJ2Hf3eyeh7jkCRXn0oABbMB3fwAN0enHIJEw7p+Rf4RhkpkbapNEjTRUgKfQjA0FsUnBOQJHyJQNCPC4Tz7NN3nPbpL2vII7wfAJ3lQB5sAAL24dgPy-Gd4mLP9A0kVQzW3I18mRGRDXEI1yl1LJDBWYwKhkZCU3QtDc0w4huFgGUSDwfxCOIsi7g-fwIGwaTMzAKjlVnWiECsPdNzSGgqnSAD1GhNJ2QsCpRHBXUAJbYSxJ7FzB2HIgpJkuSX1dbB30wVT1IoigtKnJVqRo399OWBR-TyFdlCgtRBUs1lymsAVYRjPdnNQs8PK8h5ZNwfwAEEACFvH8AANbTItpKx21mcQxEMGxOP-NJLJ1eL41gndagaVxTjFY9RIK3FPNwaTirkyrqoATXqr09Ka7ZrDsZQ5H2ZQtXYiDijUOKgVsvi1ErPKJvQiTptmkr-DIKAuhWn8S3SKQQRBU15H1YRTEsuy4QPbKtX+gMrtzNyMMKmbvNKrp8HYPMKQ9HSove1rpD21QANkTjREsw4Tu1KD-oRBNhEh1zJu6O74f8JhHiZ3A1PIWVMBIJ41I00LXt06KrFNWYLU6jIjlUZRLP1P1+SqAMgQRDQxGpj5oduoqHoU0jyI-TA9EenAoCGcK0YaudlhkJlQ3bAEMhWQ1UR5f9Q1hfItRKVXTxu2H7p819-L1g2P2wY3+YxujzByU1-qdsxUUNbbtj1AMqjWf9HGGpp7RQ67xN9hnYFwEgmH8dhUFq8PGs4qRlhBNQA2qMxLOyK2ZDkeZ5m3Rx2699WC7m0qi5LsuK+W03vwF97oMyfjYVa4wNEs9qZj3Hud3nOYcj72nJLhwf-DAABHWUVMRqBkari3a3KFf-qyPIJHAop51bFqTHBbccdMHefamzW5JMC5nragE9qLV3yBYdu-EdzpE3oaWsMwTCcUOBaTIahDwjUxONKGu96YHweGAW8qAXz+HIAAu4sAr5rWsHFQ41h9qaH2DoQ6RxOIWD4rYbIYIzRCSwWNXOuC-7dAAEpgEEGAPgIRZT3GoYLOoUgWyGFrKkdIYgE6HXUFbdIqRkSdwTLafhOcRJCPzpKE+2BS4ABk8BgFHqgT8YD0aNQUNZCQKgbDCnSBZTRdReSK3bnuPaRpf5mJuBY0uIUYB3GwCpLm5BR5yMSFtOKIIPZ8V+go+slhoxrFUJkhESU+5lQAO6yQIkRHWylAo8xCpQfweAABmqACAQG4GAdouAnyoFeJIGA7BBDayUhRTAggmmoCSYgYyfpCkZADGYBW4hLJQXMO2U0aROEdypkY5M0NJClPKfJSpwyVK1M0g03AzSCCPAeERSQTBObsGaQ8W8fS-CDOObrUZ4zJkIGMtsI0NoWy6lNAGSy31Nw0FUCYPIkZDHZ12ahA5HBnwBwCkFXm9TxmtPaZ07pvT+mCF8m+D8YzLkTKcebPSGyrYtkUPYZEyj8hpSUEyMQcg7ClCBMUspKLiWBxqcFc52Kbl3IeSQJ5RFXmEv5QFMlzTfk0qZNuMQAExCaDSoKcsONVFaOZDyw5C1aoXKuW03AHS8D4o6YSkgAAjWAgg+DyopajSeEc-koNyTIWCcg6jrEOv+ba5YMhQSSrIbatQDUoqNTVE1LTRUPHuY8550r3l2odU6n5lLVrRQ2TMHIIIlA7m3FoHqfFpB6j2pxbIgYf47K7KJZFAQjWLTjTi81eKenWrTfawQehnWKs9XUeQFRbCIN1NCVsyIRauLsnqVsuV604Nck28qVV-CtpFQ8W5ibxWSpeW8gZ6a+0DuzW9KZnEkGsVassFYobJ18T9FC9k20do3qjQEJ6XQ21motV0rth7BBfvEVm114DvR5pSEaOocgLQaKKHBHkKi7JGgqGaWEH7Hr4G-VundSaJUpsA8B09YHnEQc4idbITUrAN3nIDec5YWzyFSHO5Y2yEUNqhqu8+yMf24stQBwlPH3gkYLGR6lBMWpWTJluZlAaOQWBWJelQ84rCYeE22hN+H92poGcJ0T05xO5s9cYGF+o1jKwOghpKVt5i0OtGIFE6Il2CJXbygITMHgszZhzLmGK6l2Oxb+ztBL3mee8-5XzDxBBnNCgZiKObkmW3ikoawr706EwDdUbYC85h7Tg76zD4WHw+c5tzIVoVNPbrFcmqVgHius0i2VmLFXKDxbNolqZyXhSpbmNA7LllsrSH5HlncwpCsuZMa5Gx5r7GYBHH0cccRFXeqjjRo4mQjh5CWQGgM5YesmVoYxPhHHl1qxm3Y8uDi8QRLLk9fCsTArxMSWeqeUzYR+n2GYcE6h5ZS0OgrFqrVHBZHZH3C7c3JADlwBwAgK2VBMjSRt60sGuT6kkMGWskJWr-TSOD2xkPoew7JKRqlub8hWzsqaR9NHtQRn+rMFYEhbBakhNufHs2ruYEkLgKVH4FtjhCMt177rrC1GkKafUvCMhan+0UJKMwFzKz1O1KCnEOeXdQA4yQAA5CuAAFVAeB2CwAIGVCAEBAgUVdIzI3dxFXzD9JqfLgpFhJS5NqFqqcsiKCUAoDXkO9f+EN8b03pBQqONJ51-SmhzAmGDKtrQep-VFF4iLVBpRTQrD7kT9gcOReNUsDyJPWQVDbkT4aYEjPIzLChboqCOeYd55J2JsnJZmJMkhJCOYshwTtUrwBaMts5irg6s507rm1YABV7sxLiQ8BJ5cBf9GF1H89fztrmG1EoLxh3-yV6gkyR93rLC6g7JNvZM-8APfn4vlp1xbsQ65w7hE79FA7jsttaE1oeRuzSMxqFOYdjUaYxPZWUVmCuDSV0E8AAeVwHbT-StX2W8Cn0EHALaUECgPYFgJNjXze30nxkZxMF1BMkyEWGfkQB3DihXDQzpU8UwQnymw+H8F538EaRIEoB6CW1UjAHYI5g805nNV+UEHanMDSRhSNEDAkGUS5DkH9BsEODmEsDgmAOwQuDIGwFvAlVaFHkZkEO6GCwE16Q0K0PuEEHLkEHYMoF+QqEAjIOyB9ShEOm2lSSsDBFBUDHUGchMO0PwF0PFXNWXyW0GEVSBEYw5R3EYkDGhDXnLFWDsHBBWBqG8Jh1MJ0PLkPj4GCh0KJFzAQJCw6R8LMIsNyJ7BsL2gYisFqAtHbjSFhGhCghywSkAN9BcKQhOF5wwHgCiDUKgFb2j0EEWDKFyEUDL3UC0B2yKEGJNC1EsFqCFGMDNGchxDAH6PX3pC0HKGRHmRDGAmhHUEXFbH1GkOMmUS9jWPwPZDihLxMHyQr0OiQQWGtEGlsHmAwwv3ymEQuPdRbAtAxySn1GB19UyxfiZTiNalSGqAAmhMwyGS+X82FXJW+JcTWFmDmUUH5BqFSHBWSABFbChW1HyHhRAMRUbXc1RT8nRViyxSRMMzbyMC0W1T7wUU3wNADTkEZFSCghlnmFsMwxjTjWRLnFcSkDUE6lrG2lMwBl23YV1HjFUHOj1BOxJM4zc0NXXU3VpIS3XyOGSAShyA21SkOinQ2jghHSqChMw2A0FLpOj11O2BWHshKCqDyFrEBjUCZGDAqFUBrzqHUwfAvneBtO1PwN1MZAyFWBVwlLXHkwR3+mUUcG2LlKzhVLO17FXQa1Kz82pMCy1I6x1PbF-3ZDslAgUJKEG3RzlnbAAnVDBw+NEify10wCFOpXsAsCGnbBsHUDWBiKjG5MEmQVtnH1TMn17EbO11zxbNzX2DKEyBKE7ItJ7OcOSD4jvR72721ADy5x5z52bNtPXyFHzXnn-GrFkCs0QDTwUOUUzzNFkC3KbN1wNztxNynOSV1FriUHan1HjxUEmIvKDWp2gyOEjC0GJN6L2UnP3NDO3C311EFAjJ3371YQbEYwOJbDbHP0YMv1n04Fv1HlfKmVDGjBjBUB+nBnXDyH+LNA5AfgMj7nQMgOt2wPQjgIIv0nNHKDqCTyZ1S2lKKCCUZyZTP3jC1C9hYIrisNWKgvdSmDkIkBWVUDqH+i-2cLkKgnsDnQmNNBSM0N8KgH8P0LYr4ikADG1CtHkFp1kJ5BMHUEjDXiONUIETaCKPSIrkkWyL8NKI+CMscAx1hCYlanZE5UnUjF5HsHIMswAnaKwpcr8IyICLsQAApyEmA9AABKNikQCQCXTJRiTQFhIoHhRTF9QJOlZU8C2K-S+K-Q-wZK1AVKtK2qlK9KzKsQT6OQUYvKiYydBESnHcawA0muYUFwFwIAA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBLWFJDWV5hVSZZTrlBUKjS1FZiUrDeWVDasaQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iaFlmZWZSmZRaKyGQzKYQaDYIGSmDSzdKyDSGcQ0KrAk5nTxtDpdAi3e5PWCvdgfKiGSLMVhjbh-BCCaw0WaQrZqdGLAr6RAZGSSWzCZSiIEKNFi5TY5q4y4E658dgPACuGC+NNi4wSQgqM3hNFEwNEGlEYthENELJkCjWplMFpsGlRUo8rVlNz4LAe7DV0Vpvy1jKtCkkVuZMjsDpNsOEwjKUIkcxNqWhomd5za3jJH2IZEomEzb0+9BGfs1oESEOtkmM0K0ahMplkZqOZUrYrRlgUJrTMoL5Pekj7HwAklcegEgl0BuFi99S-SA4ZoWUdWsTSLlsozZlzNkZPrG0a5F2e66hwPz6OCcgSK8+lAALZgO7+ABuj045BImB9PzL-CMeFW2qTQhU0ZMzVRKR0RoVJ6nyJQNFPC5z0HLN3ivbobzvIJH2fAJ3lQB5sAAL24dhv1-ed4nLQDoUkVRrX1CF8ihGQzXECFymNLJIU4-U8mQjN0LQwtMOIbhYEVEg8H8QjiLIu5v38CBsCk3MwCojUF1ohArCNCwNDSDFENMI51h5OEzGUCwKgtbJRDMtEhNE-tXJHMciEk6TZPfL1sC-TAVLUiiKE02d1TpGiAL05ZgztK0RVBYxNHYyzw1qcprCODQY2EZYjRc1DL087yHhk3B-AAQQAIW8fwAA0tKihkrC7JExEMGxOPhNJYXhAy7ShRY4NMWoGlcU5pTPESSoJLzcCk8rZNq+qAE1mv9XS2u2aw7GUOR9mFA7YU5YN8rstlHEdIrZvQ8SFqWir-DIKAuk2-8K2RaQlBA+QxWEUx+otXV1zyg1AbG27C3cjDSsWnzKq6fB2CLalfW06KvvEGYVANDE0TSfZ+sOc7gVRQHHSUKFobcubukexH-CYR4WdwVTyCVTASCeVT1LCj6dJiqwrVmW0eoyI5VC3dKxURWMqjG-LHVy1NJpxGaYfpiSEeWyr5NI8jv0wPQXpwKAhgijGWsXZZ+S7TQu3SI0aBWM0kpDO1QPysQVgmpoXRQu6xPhp7fI-ALjdN79sAtwWsbo8wcitQGkrMEUzQO7YTTGzFqiqWpaY+WGHrK57YFwEgmH8dhUEa+PWs4qRlnERYFDG6ozH6vcQzkeZ5n1RxwyLi97tDpmK6rmu642q2-yFr60QsIVOJjHHjBhdKupmI0h8cpc5hyEeS-HvX-DAABHJVlORqBUYb23G3KbfAayPIJG5Iol3RJETFg-VjqmGPtrRmZ8mA82NtQOe1FG75AsOGFY+xGwU03l-RsMwTCcUOLaTIag1YB3TLDE+80y6yQeGAB8qB3z+HIKQu4sAH7bWsMGQ41gDRWlRCacQZolBlEsMCfcOROQHWAWPAkAAlMAggwB8BCEqe4jDhZ1CkGiVQ+kdQGlhCCGyjk14misGZbIoiQ5yivtgauAAZPAYBp6oB-NAzGrV246JKKCW0CZ1CwiUNnTIe05CJghMY-s4lrhmOrqFGAdxsDKR5uQaeijEj7WDK3A0WD-rKObJYSQYFVDpMdKCY+VUADuMkCJEUNkpIKfNQqUH8HgAAZqgAgEBuBgHaLgV8qBXiSBgOwQQBtFIUUwIIBpqAEmIGMoifJGQxpmCVtw9KqJzBdlDFg2wENCklI4HJcpgzlLVI0nU3AjSCCPAeERSQTBubsEaQ8B8PS-D9N2UbYZozxlWX1NWLqqI0TGitGNfqrd+TZFdrBOorFDCbNKW+COgVgr81qaM5prT2mdO6b0wQflPzfhGccsZDiba6SJvyNEih7BQkMIKfqVopCOHfnYUo+UoXbKxZHKpIVDlIrORcq5JAblEXuRi1lgVcWNPecSyQix5D7zEJoalRlqzHVSK3ECShmUBFWo1I5JyWm4DaXgNFbSMUkAAEawEEHwUV+L0bzwTlZSE2SDqwXDLsCyRR4QHWrBkVEoJZAHULuraaQcYbFOhZqhq2qmncoeJc65tzBWPNNeay1byCVbRikTGYQj1B1FMPqLQ-UzI2TqNaCQG9oRAMDYHYSIatkarqv4NakbkV6tRV0o1iazWCD0Fa8VWDpAaMcvCHGxpTquyzYrC0XD83quqg2ptXKHjnJjby-ldyHl9KTd23tabPoTNXvAle+V5jpEhKdSEiJXbaJdaSmQs7XpdGbbq-VHT20bsEA+qRqabUwIDJmlIEJwWOTtIYYGBoBTogtOaE0UJhD3vwI+xdy7Y18vje+z9O6f2OL-Zxc62Q2pWDUGZT+EyRSAjRPIOC+oASztvqjJ9KKDVvoxXR94mGSzYaJZxbYyczAoP3PkEm7VXaMpUEuKwtHnx33eM26NKG10Jr6ax9jc5OMZv7cYEw+i1i5TSu60E-J5jMIRGIGMBpZ0sweGzDmXMebwpqTYpFz623oseZZ6zAVbMPEEAcsKKnIrpsSXbAUXDrAHTinIfq1RtjrzmMKdxch-ZTWrUQ0N2z3PPhs9zXmHKwqyaXTyuNAr30ZfZp57LPncuUH89bQLEzgtexROFtYkX0p5WkLGOLwH35JY1sGtyVi9W2MwOOPoU44jiv3EnQjRxMhHDyAs91Y1qxewxMwxid6q2ENQoNmxtc7GEjCTXV6+FolBVifE3dC8JkxkRPsMwsF1CKxlkUJWSIcaOCyCCY+u3huSGHLgDgBBJsqElSk2bCI5BA0smsMoaR24PZxoDNIP3rF-YB0DykWHCUZvyPyC0VoL2EeBNGQGswVgSHWcCWC+DkvbZEr9-bmBJC4AFd+Ubk4QgTau3a6wmVZDqClsyA0L3ECghmMuVWcsoKQq272BnaOmeSAAHJ1wAAqoDwOwWABAqoQAgIECiXpmaa7uOK+YiI80GkckZRYoJYTzCTmYV+mQc0KFR0NpXqv-Aa61zr0gYV7HY7q3pTQu5ZBAoxFoE0brEC8TFtg0oVoVjHwx+wYHPPWr8NmF2LIKhqNR7NGkKQcwhTLFBcsJ0cvNZuTT8DqkHGccVmYpKvNea5iyFgl1IvZlslOzmECbqkpq-9eLgAFRO1EmJDw4m1w5-0bnwe91wgOuYYESg7TolsPCIvqJJUXv3JYY03YR81rchP-Ap3p+z6aaEpU5j-CM9QHY83jpf6KEchaE6lkEQ2RjOGOoLIV2UvY+JUdmOudSL0c8AAeVwBbRfUNUkCqm8DH0EDAJaUEEgPYBgMtiX2uz0hKGDDyFqCckdDqDUFhCTAFGwTUFylbhKDg1P1hn8FZ38HqRIEoB6HGxUjAHYK5gCF5T1XeSZC4iOARCRy0ChBsFEAd2WH3zSByEhiFBsBHhYLrnYM4N8AnAX0GHeSFC+R0wkDmWyEOFhAdAgxKDsCUAGhXhcjIGwAfD5VaGnmZm5j1XgJczaXsMcPuEEFrkEA0PCjwLtQqGAldxBSWEyFkOSSsBKFSHbmhHUDsMBx8OcNrlcLIG6C0LGy510Mzz-Xyi+TkCh0YmhEoK0GrFWDsFghWBqGSIcKcPwBcJkRCmcNJELA8KY26W8L5SkX8PaP7HeSzngX9SqH2EUL00QFwwYihFbhBCe1yndxOFZwwHgCiD6ygEbxD0EEWDKFyEUHz3UC0EWyEEWElT3ByE03UCyCQiYPxDAC2OX0EFBCIKhFmU0BNFyi8TFHKE5Hu1iMsErQIXl0LEePwPmJz0o3zzyELxhwYjtDAnZBXm+yYOKjEQeNUybyMAyGbjGlylsA+MBljz0kpTu1RGMj9jz16yDTP2LjSzKQUheXs05TxTBLtSlmDEdAxEUFjBqFSEBWSGdhgkd3yFl2BJrzpLrRhX8jhV80RVZMxJD3MhJVUEsGUVX1A3SjkBZFSFRDlnmFCNnXDUjTZKcSfg5AMRUGqIxELU4h2GGjUVyWOFRJEnpLnXWhNMVOXzEPxw4Vwz3DHWFF7jsjECqHzngzekcwVIC29Pbm2BWFgiESqDyEbGBjUElXhwqFUDLzqEkxRhk1GVNMXHbhZBxOtzFEbH7hJlB0BkpUcDeONBTxdNrWhVKyyzszlKjMaSLO2i7D-xBAtHAhsD9n6jJWyTrP3h1BRPFNHwHCfzsR7IzXsGXkIKNDDLWEoNUBDFREPnX0dGnLpxBIG0V2f2ZzT0XMSX2D4XGi7BsHUA3Jh2SEhBWHyQezmCBMPIlLnJPLsRZzZ0wAvImRsCzTXj1EWFkEmIQHj2HMpST2tFkA9z21PJV3V1N210ArhGNGbl4TBBMDSDdksjLxDHbDRCOGULFFT0B3YAwuMEcgYmNCMhxI3270skrFbHMkA07BPxnNpIHAv0iU4Gv2ngwuPHlmPStNz1tG3DyBDDWAWEow3Fpw2KIXQIgKN2wPQlgJoptF+N3lSVC2hyKCNBsgH1sGP2GnM2bP7DULYI4IxJjPwKZBKAYhNAEyMgM35MsjoJDHYRBBqBWEbHqNSKaPSMEPstq29JWAFCFHbwROJ1kJshMGuOFGOLUEYJ4skB6M4BCrrhaKkjaPQhouW19mFCyAjE4lsCixZAHhLOZGtCtBcBcCAA */
|
||||
id: 'Modeling',
|
||||
|
||||
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
||||
@ -135,11 +146,14 @@ export const modelingMachine = createMachine(
|
||||
otherSelections: [],
|
||||
codeBasedSelections: [],
|
||||
} as Selections,
|
||||
selectionRangeTypeMap: {} as SelectionRangeTypeMap,
|
||||
sketchPathToNode: null as PathToNode | null, // maybe too specific, and we should have a generic pathToNode, but being specific seems less risky when I'm not sure
|
||||
sketchEnginePathId: '' as string,
|
||||
sketchDetails: {
|
||||
sketchPathToNode: [],
|
||||
zAxis: [0, 0, 1],
|
||||
yAxis: [0, 1, 0],
|
||||
origin: [0, 0, 0],
|
||||
} as null | SketchDetails,
|
||||
sketchPlaneId: '' as string,
|
||||
sketchNormalBackUp: null as null | [number, number, number],
|
||||
sketchEnginePathId: '' as string,
|
||||
moveDescs: [] as MoveDesc[],
|
||||
},
|
||||
|
||||
@ -160,7 +174,6 @@ export const modelingMachine = createMachine(
|
||||
{
|
||||
target: 'animating to existing sketch',
|
||||
cond: 'Selection is on face',
|
||||
actions: ['set sketch metadata'],
|
||||
},
|
||||
'Sketch no face',
|
||||
],
|
||||
@ -504,6 +517,11 @@ export const modelingMachine = createMachine(
|
||||
target: 'animating to plane',
|
||||
actions: ['reset sketch metadata'],
|
||||
},
|
||||
|
||||
'Set selection': {
|
||||
target: 'Sketch no face',
|
||||
internal: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -532,15 +550,15 @@ export const modelingMachine = createMachine(
|
||||
{
|
||||
src: 'animate-to-sketch',
|
||||
id: 'animate-to-sketch',
|
||||
onDone: 'Sketch',
|
||||
onDone: {
|
||||
target: 'Sketch',
|
||||
actions: 'set new sketch metadata',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
entry: 'clientToEngine cam sync direction',
|
||||
},
|
||||
|
||||
'animating to plane (copy)': {},
|
||||
'animating to plane (copy) (copy)': {},
|
||||
},
|
||||
|
||||
initial: 'idle',
|
||||
@ -562,13 +580,13 @@ export const modelingMachine = createMachine(
|
||||
},
|
||||
{
|
||||
guards: {
|
||||
'is editing existing sketch': ({ sketchPathToNode }) => {
|
||||
'is editing existing sketch': ({ sketchDetails }) => {
|
||||
// should check that the variable declaration is a pipeExpression
|
||||
// and that the pipeExpression contains a "startProfileAt" callExpression
|
||||
if (!sketchPathToNode) return false
|
||||
if (!sketchDetails?.sketchPathToNode) return false
|
||||
const variableDeclaration = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
sketchPathToNode,
|
||||
sketchDetails.sketchPathToNode,
|
||||
'VariableDeclarator'
|
||||
).node
|
||||
if (variableDeclaration.type !== 'VariableDeclarator') return false
|
||||
@ -621,128 +639,154 @@ export const modelingMachine = createMachine(
|
||||
},
|
||||
// end guards
|
||||
actions: {
|
||||
'set sketchMetadata from pathToNode': assign(({ sketchPathToNode }) => {
|
||||
if (!sketchPathToNode) return {}
|
||||
return getSketchMetadataFromPathToNode(sketchPathToNode)
|
||||
'set sketchMetadata from pathToNode': assign(({ sketchDetails }) => {
|
||||
if (!sketchDetails?.sketchPathToNode || !sketchDetails) return {}
|
||||
return {
|
||||
sketchDetails: {
|
||||
...sketchDetails,
|
||||
sketchPathToNode: sketchDetails.sketchPathToNode,
|
||||
},
|
||||
}
|
||||
}),
|
||||
'hide default planes': () => {
|
||||
sceneInfra.removeDefaultPlanes()
|
||||
kclManager.hidePlanes()
|
||||
},
|
||||
'reset sketch metadata': assign({
|
||||
sketchPathToNode: null,
|
||||
sketchDetails: null,
|
||||
sketchEnginePathId: '',
|
||||
sketchPlaneId: '',
|
||||
}),
|
||||
'set sketch metadata': assign(({ selectionRanges }) => {
|
||||
const sourceRange = selectionRanges.codeBasedSelections[0].range
|
||||
const sketchPathToNode = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
sourceRange
|
||||
)
|
||||
return getSketchMetadataFromPathToNode(
|
||||
sketchPathToNode,
|
||||
selectionRanges
|
||||
)
|
||||
}),
|
||||
'set new sketch metadata': assign((_, { data }) => data),
|
||||
'set new sketch metadata': assign((_, { data }) => ({
|
||||
sketchDetails: data,
|
||||
})),
|
||||
// TODO implement source ranges for all of these constraints
|
||||
// need to make the async like the modal constraints
|
||||
'Make selection horizontal': ({ selectionRanges, sketchPathToNode }) => {
|
||||
'Make selection horizontal': ({ selectionRanges, sketchDetails }) => {
|
||||
const { modifiedAst } = applyConstraintHorzVert(
|
||||
selectionRanges,
|
||||
'horizontal',
|
||||
kclManager.ast,
|
||||
kclManager.programMemory
|
||||
)
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
sketchPathToNode || [],
|
||||
modifiedAst
|
||||
sketchDetails.sketchPathToNode,
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
},
|
||||
'Make selection vertical': ({ selectionRanges, sketchPathToNode }) => {
|
||||
'Make selection vertical': ({ selectionRanges, sketchDetails }) => {
|
||||
const { modifiedAst } = applyConstraintHorzVert(
|
||||
selectionRanges,
|
||||
'vertical',
|
||||
kclManager.ast,
|
||||
kclManager.programMemory
|
||||
)
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
sketchPathToNode || [],
|
||||
modifiedAst
|
||||
sketchDetails.sketchPathToNode || [],
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
},
|
||||
'Constrain horizontally align': ({
|
||||
selectionRanges,
|
||||
sketchPathToNode,
|
||||
}) => {
|
||||
'Constrain horizontally align': ({ selectionRanges, sketchDetails }) => {
|
||||
const { modifiedAst } = applyConstraintHorzVertAlign({
|
||||
selectionRanges,
|
||||
constraint: 'setVertDistance',
|
||||
})
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
sketchPathToNode || [],
|
||||
modifiedAst
|
||||
sketchDetails?.sketchPathToNode || [],
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
},
|
||||
'Constrain vertically align': ({ selectionRanges, sketchPathToNode }) => {
|
||||
'Constrain vertically align': ({ selectionRanges, sketchDetails }) => {
|
||||
const { modifiedAst } = applyConstraintHorzVertAlign({
|
||||
selectionRanges,
|
||||
constraint: 'setHorzDistance',
|
||||
})
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
sketchPathToNode || [],
|
||||
modifiedAst
|
||||
sketchDetails?.sketchPathToNode || [],
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
},
|
||||
'Constrain snap to X': ({ selectionRanges, sketchPathToNode }) => {
|
||||
'Constrain snap to X': ({ selectionRanges, sketchDetails }) => {
|
||||
const { modifiedAst } = applyConstraintAxisAlign({
|
||||
selectionRanges,
|
||||
constraint: 'snapToXAxis',
|
||||
})
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
sketchPathToNode || [],
|
||||
modifiedAst
|
||||
sketchDetails?.sketchPathToNode || [],
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
},
|
||||
'Constrain snap to Y': ({ selectionRanges, sketchPathToNode }) => {
|
||||
'Constrain snap to Y': ({ selectionRanges, sketchDetails }) => {
|
||||
const { modifiedAst } = applyConstraintAxisAlign({
|
||||
selectionRanges,
|
||||
constraint: 'snapToYAxis',
|
||||
})
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
sketchPathToNode || [],
|
||||
modifiedAst
|
||||
sketchDetails?.sketchPathToNode || [],
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
},
|
||||
'Constrain equal length': ({ selectionRanges, sketchPathToNode }) => {
|
||||
'Constrain equal length': ({ selectionRanges, sketchDetails }) => {
|
||||
const { modifiedAst } = applyConstraintEqualLength({
|
||||
selectionRanges,
|
||||
})
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
sketchPathToNode || [],
|
||||
modifiedAst
|
||||
sketchDetails?.sketchPathToNode || [],
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
},
|
||||
'Constrain parallel': ({ selectionRanges, sketchPathToNode }) => {
|
||||
'Constrain parallel': ({ selectionRanges, sketchDetails }) => {
|
||||
const { modifiedAst } = applyConstraintEqualAngle({
|
||||
selectionRanges,
|
||||
})
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
sketchPathToNode || [],
|
||||
modifiedAst
|
||||
sketchDetails?.sketchPathToNode || [],
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
},
|
||||
'Constrain remove constraints': ({
|
||||
selectionRanges,
|
||||
sketchPathToNode,
|
||||
}) => {
|
||||
'Constrain remove constraints': ({ selectionRanges, sketchDetails }) => {
|
||||
const { modifiedAst } = applyRemoveConstrainingValues({
|
||||
selectionRanges,
|
||||
})
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
sketchPathToNode || [],
|
||||
modifiedAst
|
||||
sketchDetails?.sketchPathToNode || [],
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
},
|
||||
'AST extrude': (_, event) => {
|
||||
@ -754,7 +798,6 @@ export const modelingMachine = createMachine(
|
||||
distance.variableName &&
|
||||
distance.insertIndex !== undefined
|
||||
) {
|
||||
console.log('adding variable!', distance)
|
||||
const newBody = [...ast.body]
|
||||
newBody.splice(
|
||||
distance.insertIndex,
|
||||
@ -785,18 +828,25 @@ export const modelingMachine = createMachine(
|
||||
sceneInfra.modelingSend('Equip Line tool')
|
||||
}
|
||||
},
|
||||
'setup client side sketch segments': ({ sketchPathToNode }, { type }) => {
|
||||
'setup client side sketch segments': ({ sketchDetails }) => {
|
||||
if (!sketchDetails) return
|
||||
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
|
||||
sceneEntitiesManager
|
||||
.tearDownSketch({ removeAxis: false })
|
||||
.then(() => {
|
||||
sceneEntitiesManager.setupSketch({
|
||||
sketchPathToNode: sketchPathToNode || [],
|
||||
sketchPathToNode: sketchDetails?.sketchPathToNode || [],
|
||||
forward: sketchDetails.zAxis,
|
||||
up: sketchDetails.yAxis,
|
||||
position: sketchDetails.origin,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
sceneEntitiesManager.setupSketch({
|
||||
sketchPathToNode: sketchPathToNode || [],
|
||||
sketchPathToNode: sketchDetails?.sketchPathToNode || [],
|
||||
forward: sketchDetails.zAxis,
|
||||
up: sketchDetails.yAxis,
|
||||
position: sketchDetails.origin,
|
||||
})
|
||||
}
|
||||
},
|
||||
@ -809,43 +859,60 @@ export const modelingMachine = createMachine(
|
||||
}
|
||||
},
|
||||
'remove sketch grid': () => sceneEntitiesManager.removeSketchGrid(),
|
||||
'set up draft line': ({ sketchPathToNode }) => {
|
||||
sceneEntitiesManager.setUpDraftLine(sketchPathToNode || [])
|
||||
'set up draft line': ({ sketchDetails }) => {
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.setUpDraftLine(
|
||||
sketchDetails.sketchPathToNode,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis
|
||||
)
|
||||
},
|
||||
'set up draft arc': ({ sketchPathToNode }) => {
|
||||
sceneEntitiesManager.setUpDraftArc(sketchPathToNode || [])
|
||||
'set up draft arc': ({ sketchDetails }) => {
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.setUpDraftArc(
|
||||
sketchDetails.sketchPathToNode,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis
|
||||
)
|
||||
},
|
||||
'set up draft line without teardown': ({ sketchPathToNode }) =>
|
||||
'set up draft line without teardown': ({ sketchDetails }) =>
|
||||
sceneEntitiesManager.setupSketch({
|
||||
sketchPathToNode: sketchPathToNode || [],
|
||||
sketchPathToNode: sketchDetails?.sketchPathToNode || [],
|
||||
draftSegment: 'line',
|
||||
forward: sketchDetails?.zAxis || [0, 1, 0],
|
||||
up: sketchDetails?.yAxis || [0, 0, 1],
|
||||
position: sketchDetails?.origin,
|
||||
}),
|
||||
'show default planes': () => {
|
||||
sceneInfra.showDefaultPlanes()
|
||||
sceneEntitiesManager.setupDefaultPlaneHover()
|
||||
kclManager.showPlanes()
|
||||
},
|
||||
'setup noPoints onClick listener': ({ sketchPathToNode }) => {
|
||||
'setup noPoints onClick listener': ({ sketchDetails }) => {
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.createIntersectionPlane()
|
||||
const sketchGroup = sketchGroupFromPathToNode({
|
||||
pathToNode: sketchPathToNode || [],
|
||||
ast: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
const quaternion = quaternionFromSketchGroup(sketchGroup)
|
||||
const quaternion = quaternionFromUpNForward(
|
||||
new Vector3(...sketchDetails.yAxis),
|
||||
new Vector3(...sketchDetails.zAxis)
|
||||
)
|
||||
sceneEntitiesManager.intersectionPlane &&
|
||||
sceneEntitiesManager.intersectionPlane.setRotationFromQuaternion(
|
||||
quaternion
|
||||
)
|
||||
sceneEntitiesManager.intersectionPlane &&
|
||||
sceneEntitiesManager.intersectionPlane.position.copy(
|
||||
new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
|
||||
)
|
||||
sceneInfra.setCallbacks({
|
||||
onClick: async (args) => {
|
||||
if (!args) return
|
||||
if (args.mouseEvent.which !== 1) return
|
||||
const { intersectionPoint } = args
|
||||
if (!intersectionPoint?.twoD || !sketchPathToNode) return
|
||||
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode)
|
||||
return
|
||||
const { modifiedAst } = addStartProfileAt(
|
||||
kclManager.ast,
|
||||
sketchPathToNode,
|
||||
sketchDetails.sketchPathToNode,
|
||||
[intersectionPoint.twoD.x, intersectionPoint.twoD.y]
|
||||
)
|
||||
await kclManager.updateAst(modifiedAst, false)
|
||||
@ -854,8 +921,15 @@ export const modelingMachine = createMachine(
|
||||
},
|
||||
})
|
||||
},
|
||||
'add axis n grid': ({ sketchPathToNode }) =>
|
||||
sceneEntitiesManager.createSketchAxis(sketchPathToNode || []),
|
||||
'add axis n grid': ({ sketchDetails }) => {
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.createSketchAxis(
|
||||
sketchDetails.sketchPathToNode || [],
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
},
|
||||
'reset client scene mouse handlers': () => {
|
||||
// when not in sketch mode we don't need any mouse listeners
|
||||
// (note the orbit controls are always active though)
|
||||
@ -871,44 +945,3 @@ export const modelingMachine = createMachine(
|
||||
// end actions
|
||||
}
|
||||
)
|
||||
|
||||
function getSketchMetadataFromPathToNode(
|
||||
pathToNode: PathToNode,
|
||||
selectionRanges?: Selections
|
||||
) {
|
||||
const pipeExpression = getNodeFromPath<PipeExpression>(
|
||||
kclManager.ast,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
).node
|
||||
if (pipeExpression.type !== 'PipeExpression') return {}
|
||||
const sketchCallExpression = pipeExpression.body.find(
|
||||
(e) => e.type === 'CallExpression' && e.callee.name === 'startSketchOn'
|
||||
) as CallExpression
|
||||
if (!sketchCallExpression) return {}
|
||||
|
||||
let sketchEnginePathId: string
|
||||
if (selectionRanges) {
|
||||
sketchEnginePathId =
|
||||
isCursorInSketchCommandRange(
|
||||
engineCommandManager.artifactMap,
|
||||
selectionRanges
|
||||
) || ''
|
||||
} else {
|
||||
const _selectionRanges: Selections = {
|
||||
otherSelections: [],
|
||||
codeBasedSelections: [
|
||||
{ range: [pipeExpression.start, pipeExpression.end], type: 'default' },
|
||||
],
|
||||
}
|
||||
sketchEnginePathId =
|
||||
isCursorInSketchCommandRange(
|
||||
engineCommandManager.artifactMap,
|
||||
_selectionRanges
|
||||
) || ''
|
||||
}
|
||||
return {
|
||||
sketchPathToNode: pathToNode,
|
||||
sketchEnginePathId,
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,8 @@ export interface StoreState {
|
||||
path: string
|
||||
}[]
|
||||
setHomeMenuItems: (items: { name: string; path: string }[]) => void
|
||||
lastCodeMirrorSelectionUpdatedFromScene: number
|
||||
setLastCodeMirrorSelectionUpdatedFromScene: (time: number) => void
|
||||
}
|
||||
|
||||
export const useStore = create<StoreState>()(
|
||||
@ -156,6 +158,9 @@ export const useStore = create<StoreState>()(
|
||||
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
|
||||
homeMenuItems: [],
|
||||
setHomeMenuItems: (homeMenuItems) => set({ homeMenuItems }),
|
||||
lastCodeMirrorSelectionUpdatedFromScene: Date.now(),
|
||||
setLastCodeMirrorSelectionUpdatedFromScene: (time) =>
|
||||
set({ lastCodeMirrorSelectionUpdatedFromScene: time }),
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -242,6 +242,8 @@ pub struct Face {
|
||||
pub y_axis: Point3d,
|
||||
/// The z-axis (normal).
|
||||
pub z_axis: Point3d,
|
||||
/// the face id the sketch is on
|
||||
pub face_id: uuid::Uuid,
|
||||
#[serde(rename = "__meta")]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
use anyhow::Result;
|
||||
use derive_docs::stdlib;
|
||||
use schemars::JsonSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
@ -113,8 +114,9 @@ async fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: Args)
|
||||
|
||||
// Create a hashmap for quick id lookup
|
||||
let mut face_id_map = std::collections::HashMap::new();
|
||||
let mut start_cap_id = None;
|
||||
let mut end_cap_id = None;
|
||||
// creating fake ids for start and end caps is to make extrudes mock-execute safe
|
||||
let mut start_cap_id = Some(Uuid::new_v4());
|
||||
let mut end_cap_id = Some(Uuid::new_v4());
|
||||
|
||||
for face_info in face_infos {
|
||||
match face_info.cap {
|
||||
@ -160,6 +162,18 @@ async fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: Args)
|
||||
new_value.push(extrude_surface);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_value.push(ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
|
||||
position: sketch_group.position, // TODO should be for the extrude surface
|
||||
rotation: sketch_group.rotation, // TODO should be for the extrude surface
|
||||
// pushing this values with a fake face_id to make extrudes mock-execute safe
|
||||
face_id: Uuid::new_v4(),
|
||||
name: path.get_base().name.clone(),
|
||||
geo_meta: GeoMeta {
|
||||
id: path.get_base().geo_meta.id,
|
||||
metadata: path.get_base().geo_meta.metadata.clone(),
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -955,6 +955,7 @@ async fn start_sketch_on_face(
|
||||
y_axis: extrude_group.y_axis,
|
||||
z_axis: extrude_group.z_axis,
|
||||
meta: vec![args.source_range.into()],
|
||||
face_id: extrude_plane_id,
|
||||
}))
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user