Merge branch 'main' into franknoirot/4088/create-file-url
This commit is contained in:
2
.github/ci-cd-scripts/playwright-electron.sh
vendored
2
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -21,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
retry=1
|
retry=1
|
||||||
max_retrys=4
|
max_retrys=5
|
||||||
|
|
||||||
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
||||||
while [[ $retry -le $max_retrys ]]; do
|
while [[ $retry -le $max_retrys ]]; do
|
||||||
|
|||||||
@ -491,721 +491,8 @@ sketch002 = startSketchOn(extrude001, seg03)
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
`,
|
`,
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
)
|
)
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test(`Verify axis, origin, and horizontal snapping`, async ({
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
editor,
|
|
||||||
toolbar,
|
|
||||||
scene,
|
|
||||||
}) => {
|
|
||||||
const viewPortSize = { width: 1200, height: 500 }
|
|
||||||
|
|
||||||
await page.setBodyDimensions(viewPortSize)
|
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
|
|
||||||
// Constants and locators
|
|
||||||
// These are mappings from screenspace to KCL coordinates,
|
|
||||||
// until we merge in our coordinate system helpers
|
|
||||||
const xzPlane = [
|
|
||||||
viewPortSize.width * 0.65,
|
|
||||||
viewPortSize.height * 0.3,
|
|
||||||
] as const
|
|
||||||
const originSloppy = {
|
|
||||||
screen: [
|
|
||||||
viewPortSize.width / 2 + 3, // 3px off the center of the screen
|
|
||||||
viewPortSize.height / 2,
|
|
||||||
],
|
|
||||||
kcl: [0, 0],
|
|
||||||
} as const
|
|
||||||
const xAxisSloppy = {
|
|
||||||
screen: [
|
|
||||||
viewPortSize.width * 0.75,
|
|
||||||
viewPortSize.height / 2 - 3, // 3px off the X-axis
|
|
||||||
],
|
|
||||||
kcl: [20.34, 0],
|
|
||||||
} as const
|
|
||||||
const offYAxis = {
|
|
||||||
screen: [
|
|
||||||
viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range
|
|
||||||
viewPortSize.height * 0.3,
|
|
||||||
],
|
|
||||||
kcl: [8.14, 6.78],
|
|
||||||
} as const
|
|
||||||
const yAxisSloppy = {
|
|
||||||
screen: [
|
|
||||||
viewPortSize.width / 2 + 5, // 5px off the Y-axis
|
|
||||||
viewPortSize.height * 0.3,
|
|
||||||
],
|
|
||||||
kcl: [0, 6.78],
|
|
||||||
} as const
|
|
||||||
const [clickOnXzPlane, moveToXzPlane] = scene.makeMouseHelpers(...xzPlane)
|
|
||||||
const [clickOriginSloppy] = scene.makeMouseHelpers(...originSloppy.screen)
|
|
||||||
const [clickXAxisSloppy, moveXAxisSloppy] = scene.makeMouseHelpers(
|
|
||||||
...xAxisSloppy.screen
|
|
||||||
)
|
|
||||||
const [dragToOffYAxis, dragFromOffAxis] = scene.makeDragHelpers(
|
|
||||||
...offYAxis.screen
|
|
||||||
)
|
|
||||||
|
|
||||||
const expectedCodeSnippets = {
|
|
||||||
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
|
|
||||||
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`,
|
|
||||||
segmentOnXAxis: `xLine(${xAxisSloppy.kcl[0]}, %)`,
|
|
||||||
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`,
|
|
||||||
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
|
|
||||||
}
|
|
||||||
|
|
||||||
await test.step(`Start a sketch on the XZ plane`, async () => {
|
|
||||||
await editor.closePane()
|
|
||||||
await toolbar.startSketchPlaneSelection()
|
|
||||||
await moveToXzPlane()
|
|
||||||
await clickOnXzPlane()
|
|
||||||
// timeout wait for engine animation is unavoidable
|
|
||||||
await page.waitForTimeout(600)
|
|
||||||
await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane)
|
|
||||||
})
|
|
||||||
await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => {
|
|
||||||
await clickOriginSloppy()
|
|
||||||
await editor.expectEditor.toContain(expectedCodeSnippets.pointAtOrigin)
|
|
||||||
})
|
|
||||||
await test.step(`Add a segment on x-axis after moving the mouse a bit, verify it snaps`, async () => {
|
|
||||||
await moveXAxisSloppy()
|
|
||||||
await clickXAxisSloppy()
|
|
||||||
await editor.expectEditor.toContain(expectedCodeSnippets.segmentOnXAxis)
|
|
||||||
})
|
|
||||||
await test.step(`Unequip line tool`, async () => {
|
|
||||||
await toolbar.lineBtn.click()
|
|
||||||
await expect(toolbar.lineBtn).not.toHaveAttribute('aria-pressed', 'true')
|
|
||||||
})
|
|
||||||
await test.step(`Drag the origin point up and to the right, verify it's past snapping`, async () => {
|
|
||||||
await dragToOffYAxis({
|
|
||||||
fromPoint: { x: originSloppy.screen[0], y: originSloppy.screen[1] },
|
|
||||||
})
|
|
||||||
await editor.expectEditor.toContain(
|
|
||||||
expectedCodeSnippets.afterSegmentDraggedOffYAxis
|
|
||||||
)
|
|
||||||
})
|
|
||||||
await test.step(`Drag the origin point left to the y-axis, verify it snaps back`, async () => {
|
|
||||||
await dragFromOffAxis({
|
|
||||||
toPoint: { x: yAxisSloppy.screen[0], y: yAxisSloppy.screen[1] },
|
|
||||||
})
|
|
||||||
await editor.expectEditor.toContain(
|
|
||||||
expectedCodeSnippets.afterSegmentDraggedOnYAxis
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test(`Verify user can double-click to edit a sketch`, async ({
|
|
||||||
context,
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
editor,
|
|
||||||
toolbar,
|
|
||||||
scene,
|
|
||||||
}) => {
|
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
const initialCode = `closedSketch = startSketchOn('XZ')
|
|
||||||
|> circle({ center = [8, 5], radius = 2 }, %)
|
|
||||||
openSketch = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-5, 0], %)
|
|
||||||
|> lineTo([0, 5], %)
|
|
||||||
|> xLine(5, %)
|
|
||||||
|> tangentialArcTo([10, 0], %)
|
|
||||||
`
|
|
||||||
const viewPortSize = { width: 1000, height: 500 }
|
|
||||||
await page.setBodyDimensions(viewPortSize)
|
|
||||||
|
|
||||||
await context.addInitScript((code) => {
|
|
||||||
localStorage.setItem('persistCode', code)
|
|
||||||
}, initialCode)
|
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
await u.waitForPageLoad()
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
|
|
||||||
const pointInsideCircle = {
|
|
||||||
x: viewPortSize.width * 0.63,
|
|
||||||
y: viewPortSize.height * 0.5,
|
|
||||||
}
|
|
||||||
const pointOnPathAfterSketching = {
|
|
||||||
x: viewPortSize.width * 0.65,
|
|
||||||
y: viewPortSize.height * 0.5,
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const [_clickOpenPath, moveToOpenPath, dblClickOpenPath] =
|
|
||||||
scene.makeMouseHelpers(
|
|
||||||
pointOnPathAfterSketching.x,
|
|
||||||
pointOnPathAfterSketching.y
|
|
||||||
)
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const [_clickCircle, moveToCircle, dblClickCircle] = scene.makeMouseHelpers(
|
|
||||||
pointInsideCircle.x,
|
|
||||||
pointInsideCircle.y
|
|
||||||
)
|
|
||||||
|
|
||||||
const exitSketch = async () => {
|
|
||||||
await test.step(`Exit sketch mode`, async () => {
|
|
||||||
await toolbar.exitSketchBtn.click()
|
|
||||||
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
|
||||||
await expect(toolbar.startSketchBtn).toBeEnabled()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await test.step(`Double-click on the closed sketch`, async () => {
|
|
||||||
await moveToCircle()
|
|
||||||
await dblClickCircle()
|
|
||||||
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
|
||||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
|
||||||
await editor.expectState({
|
|
||||||
activeLines: [`|>circle({center=[8,5],radius=2},%)`],
|
|
||||||
highlightedCode: 'circle({center=[8,5],radius=2},%)',
|
|
||||||
diagnostics: [],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
|
|
||||||
await exitSketch()
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
|
|
||||||
// Drag the sketch line out of the axis view which blocks the click
|
|
||||||
await page.dragAndDrop('#stream', '#stream', {
|
|
||||||
sourcePosition: {
|
|
||||||
x: viewPortSize.width * 0.7,
|
|
||||||
y: viewPortSize.height * 0.5,
|
|
||||||
},
|
|
||||||
targetPosition: {
|
|
||||||
x: viewPortSize.width * 0.7,
|
|
||||||
y: viewPortSize.height * 0.4,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
|
|
||||||
await test.step(`Double-click on the open sketch`, async () => {
|
|
||||||
await moveToOpenPath()
|
|
||||||
await scene.expectPixelColor([250, 250, 250], pointOnPathAfterSketching, 15)
|
|
||||||
// There is a full execution after exiting sketch that clears the scene.
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await dblClickOpenPath()
|
|
||||||
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
|
||||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
|
||||||
// Wait for enter sketch mode to complete
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await editor.expectState({
|
|
||||||
activeLines: [`|>tangentialArcTo([10,0],%)`],
|
|
||||||
highlightedCode: 'tangentialArcTo([10,0],%)',
|
|
||||||
diagnostics: [],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test(`Offset plane point-and-click`, async ({
|
|
||||||
context,
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
scene,
|
|
||||||
editor,
|
|
||||||
toolbar,
|
|
||||||
cmdBar,
|
|
||||||
}) => {
|
|
||||||
// One dumb hardcoded screen pixel value
|
|
||||||
const testPoint = { x: 700, y: 150 }
|
|
||||||
const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
|
||||||
const expectedOutput = `plane001 = offsetPlane('XZ', 5)`
|
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
|
|
||||||
await test.step(`Look for the blue of the XZ plane`, async () => {
|
|
||||||
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
|
||||||
})
|
|
||||||
await test.step(`Go through the command bar flow`, async () => {
|
|
||||||
await toolbar.offsetPlaneButton.click()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'arguments',
|
|
||||||
currentArgKey: 'plane',
|
|
||||||
currentArgValue: '',
|
|
||||||
headerArguments: { Plane: '', Distance: '' },
|
|
||||||
highlightedHeaderArg: 'plane',
|
|
||||||
commandName: 'Offset plane',
|
|
||||||
})
|
|
||||||
await clickOnXzPlane()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'arguments',
|
|
||||||
currentArgKey: 'distance',
|
|
||||||
currentArgValue: '5',
|
|
||||||
headerArguments: { Plane: '1 plane', Distance: '' },
|
|
||||||
highlightedHeaderArg: 'distance',
|
|
||||||
commandName: 'Offset plane',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
|
||||||
await editor.expectEditor.toContain(expectedOutput)
|
|
||||||
await editor.expectState({
|
|
||||||
diagnostics: [],
|
|
||||||
activeLines: [expectedOutput],
|
|
||||||
highlightedCode: '',
|
|
||||||
})
|
|
||||||
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Delete offset plane via feature tree selection', async () => {
|
|
||||||
await editor.closePane()
|
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
|
||||||
'Offset Plane',
|
|
||||||
0
|
|
||||||
)
|
|
||||||
await operationButton.click({ button: 'left' })
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const loftPointAndClickCases = [
|
|
||||||
{ shouldPreselect: true },
|
|
||||||
{ shouldPreselect: false },
|
|
||||||
]
|
|
||||||
loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
|
||||||
test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({
|
|
||||||
context,
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
scene,
|
|
||||||
editor,
|
|
||||||
toolbar,
|
|
||||||
cmdBar,
|
|
||||||
}) => {
|
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
|
||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|
||||||
plane001 = offsetPlane('XZ', 50)
|
|
||||||
sketch002 = startSketchOn(plane001)
|
|
||||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
|
||||||
`
|
|
||||||
await context.addInitScript((initialCode) => {
|
|
||||||
localStorage.setItem('persistCode', initialCode)
|
|
||||||
}, initialCode)
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
|
||||||
const testPoint = { x: 575, y: 200 }
|
|
||||||
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
|
||||||
const [clickOnSketch2] = scene.makeMouseHelpers(
|
|
||||||
testPoint.x,
|
|
||||||
testPoint.y + 80
|
|
||||||
)
|
|
||||||
const loftDeclaration = 'loft001 = loft([sketch001, sketch002])'
|
|
||||||
|
|
||||||
await test.step(`Look for the white of the sketch001 shape`, async () => {
|
|
||||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
async function selectSketches() {
|
|
||||||
await clickOnSketch1()
|
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await clickOnSketch2()
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await page.keyboard.up('Shift')
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
if (!shouldPreselect) {
|
|
||||||
await test.step(`Go through the command bar flow without preselected sketches`, async () => {
|
|
||||||
await toolbar.loftButton.click()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'arguments',
|
|
||||||
currentArgKey: 'selection',
|
|
||||||
currentArgValue: '',
|
|
||||||
headerArguments: { Selection: '' },
|
|
||||||
highlightedHeaderArg: 'selection',
|
|
||||||
commandName: 'Loft',
|
|
||||||
})
|
|
||||||
await selectSketches()
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'review',
|
|
||||||
headerArguments: { Selection: '2 faces' },
|
|
||||||
commandName: 'Loft',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await test.step(`Preselect the two sketches`, async () => {
|
|
||||||
await selectSketches()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
|
|
||||||
await toolbar.loftButton.click()
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'review',
|
|
||||||
headerArguments: { Selection: '2 faces' },
|
|
||||||
commandName: 'Loft',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
|
||||||
await editor.expectEditor.toContain(loftDeclaration)
|
|
||||||
await editor.expectState({
|
|
||||||
diagnostics: [],
|
|
||||||
activeLines: [loftDeclaration],
|
|
||||||
highlightedCode: '',
|
|
||||||
})
|
|
||||||
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Delete loft via feature tree selection', async () => {
|
|
||||||
await editor.closePane()
|
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0)
|
|
||||||
await operationButton.click({ button: 'left' })
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: merge with above test. Right now we're not able to delete a loft
|
|
||||||
// right after creation via selection for some reason, so we go with a new instance
|
|
||||||
test('Loft and offset plane deletion via selection', async ({
|
|
||||||
context,
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
scene,
|
|
||||||
}) => {
|
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
|
||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|
||||||
plane001 = offsetPlane('XZ', 50)
|
|
||||||
sketch002 = startSketchOn(plane001)
|
|
||||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
|
||||||
loft001 = loft([sketch001, sketch002])
|
|
||||||
`
|
|
||||||
await context.addInitScript((initialCode) => {
|
|
||||||
localStorage.setItem('persistCode', initialCode)
|
|
||||||
}, initialCode)
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
|
||||||
const testPoint = { x: 575, y: 200 }
|
|
||||||
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
|
||||||
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 80)
|
|
||||||
|
|
||||||
await test.step(`Delete loft`, async () => {
|
|
||||||
// Check for loft
|
|
||||||
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
|
||||||
await clickOnSketch1()
|
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
|
||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|
||||||
`)
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
// Check for sketch 1
|
|
||||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Delete sketch002', async () => {
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
await clickOnSketch2()
|
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
|
||||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
|
||||||
`)
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
// Check for plane001
|
|
||||||
await scene.expectPixelColor([228, 228, 228], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Delete plane001', async () => {
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
await clickOnSketch2()
|
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
|
||||||
plane001 = offsetPlane('XZ', 50)
|
|
||||||
`)
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
// Check for sketch 1
|
|
||||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const shellPointAndClickCapCases = [
|
|
||||||
{ shouldPreselect: true },
|
|
||||||
{ shouldPreselect: false },
|
|
||||||
]
|
|
||||||
shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
|
||||||
test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({
|
|
||||||
context,
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
scene,
|
|
||||||
editor,
|
|
||||||
toolbar,
|
|
||||||
cmdBar,
|
|
||||||
}) => {
|
|
||||||
// TODO: fix this test on windows after the electron migration
|
|
||||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
|
||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|
||||||
extrude001 = extrude(30, sketch001)
|
|
||||||
`
|
|
||||||
await context.addInitScript((initialCode) => {
|
|
||||||
localStorage.setItem('persistCode', initialCode)
|
|
||||||
}, initialCode)
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
|
||||||
const testPoint = { x: 575, y: 200 }
|
|
||||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
|
||||||
const shellDeclaration =
|
|
||||||
"shell001 = shell({ faces = ['end'], thickness = 5 }, extrude001)"
|
|
||||||
|
|
||||||
await test.step(`Look for the grey of the shape`, async () => {
|
|
||||||
await scene.expectPixelColor([127, 127, 127], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!shouldPreselect) {
|
|
||||||
await test.step(`Go through the command bar flow without preselected faces`, async () => {
|
|
||||||
await toolbar.shellButton.click()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'arguments',
|
|
||||||
currentArgKey: 'selection',
|
|
||||||
currentArgValue: '',
|
|
||||||
headerArguments: {
|
|
||||||
Selection: '',
|
|
||||||
Thickness: '',
|
|
||||||
},
|
|
||||||
highlightedHeaderArg: 'selection',
|
|
||||||
commandName: 'Shell',
|
|
||||||
})
|
|
||||||
await clickOnCap()
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'review',
|
|
||||||
headerArguments: {
|
|
||||||
Selection: '1 cap',
|
|
||||||
Thickness: '5',
|
|
||||||
},
|
|
||||||
commandName: 'Shell',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await test.step(`Preselect the cap`, async () => {
|
|
||||||
await clickOnCap()
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
|
||||||
await toolbar.shellButton.click()
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'review',
|
|
||||||
headerArguments: {
|
|
||||||
Selection: '1 cap',
|
|
||||||
Thickness: '5',
|
|
||||||
},
|
|
||||||
commandName: 'Shell',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
|
||||||
await editor.expectEditor.toContain(shellDeclaration)
|
|
||||||
await editor.expectState({
|
|
||||||
diagnostics: [],
|
|
||||||
activeLines: [shellDeclaration],
|
|
||||||
highlightedCode: '',
|
|
||||||
})
|
|
||||||
await scene.expectPixelColor([146, 146, 146], testPoint, 15)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Shell point-and-click wall', async ({
|
|
||||||
context,
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
scene,
|
|
||||||
editor,
|
|
||||||
toolbar,
|
|
||||||
cmdBar,
|
|
||||||
}) => {
|
|
||||||
const initialCode = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-20, 20], %)
|
|
||||||
|> xLine(40, %)
|
|
||||||
|> yLine(-60, %)
|
|
||||||
|> xLine(-40, %)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
|> close(%)
|
|
||||||
extrude001 = extrude(40, sketch001)
|
|
||||||
`
|
|
||||||
await context.addInitScript((initialCode) => {
|
|
||||||
localStorage.setItem('persistCode', initialCode)
|
|
||||||
}, initialCode)
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
|
||||||
const testPoint = { x: 580, y: 180 }
|
|
||||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
|
||||||
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70)
|
|
||||||
const mutatedCode = 'xLine(-40, %, $seg01)'
|
|
||||||
const shellDeclaration =
|
|
||||||
"shell001 = shell({ faces = ['end', seg01], thickness = 5}, extrude001)"
|
|
||||||
const formattedOutLastLine = '}, extrude001)'
|
|
||||||
|
|
||||||
await test.step(`Look for the grey of the shape`, async () => {
|
|
||||||
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Go through the command bar flow, selecting a wall and keeping default thickness`, async () => {
|
|
||||||
await toolbar.shellButton.click()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'arguments',
|
|
||||||
currentArgKey: 'selection',
|
|
||||||
currentArgValue: '',
|
|
||||||
headerArguments: {
|
|
||||||
Selection: '',
|
|
||||||
Thickness: '',
|
|
||||||
},
|
|
||||||
highlightedHeaderArg: 'selection',
|
|
||||||
commandName: 'Shell',
|
|
||||||
})
|
|
||||||
await clickOnCap()
|
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await clickOnWall()
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await page.keyboard.up('Shift')
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'review',
|
|
||||||
headerArguments: {
|
|
||||||
Selection: '1 cap, 1 face',
|
|
||||||
Thickness: '5',
|
|
||||||
},
|
|
||||||
commandName: 'Shell',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
|
||||||
await editor.expectEditor.toContain(mutatedCode)
|
|
||||||
await editor.expectEditor.toContain(shellDeclaration)
|
|
||||||
await editor.expectState({
|
|
||||||
diagnostics: [],
|
|
||||||
activeLines: [formattedOutLastLine],
|
|
||||||
highlightedCode: '',
|
|
||||||
})
|
|
||||||
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Delete shell via feature tree selection', async () => {
|
|
||||||
await editor.closePane()
|
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0)
|
|
||||||
await operationButton.click({ button: 'left' })
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const shellSketchOnFacesCases = [
|
|
||||||
`sketch001 = startSketchOn('XZ')
|
|
||||||
|> circle({ center = [0, 0], radius = 100 }, %)
|
|
||||||
|> extrude(100, %)
|
|
||||||
|
|
||||||
sketch002 = startSketchOn(sketch001, 'END')
|
|
||||||
|> circle({ center = [0, 0], radius = 50 }, %)
|
|
||||||
|> extrude(50, %)
|
|
||||||
`,
|
|
||||||
`sketch001 = startSketchOn('XZ')
|
|
||||||
|> circle({ center = [0, 0], radius = 100 }, %)
|
|
||||||
extrude001 = extrude(100, sketch001)
|
|
||||||
|
|
||||||
sketch002 = startSketchOn(extrude001, 'END')
|
|
||||||
|> circle({ center = [0, 0], radius = 50 }, %)
|
|
||||||
extrude002 = extrude(50, sketch002)
|
|
||||||
`,
|
|
||||||
]
|
|
||||||
shellSketchOnFacesCases.forEach((initialCode, index) => {
|
|
||||||
const hasExtrudesInPipe = index === 0
|
|
||||||
test(`Shell point-and-click sketch on face (extrudes in pipes: ${hasExtrudesInPipe})`, async ({
|
|
||||||
context,
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
scene,
|
|
||||||
editor,
|
|
||||||
toolbar,
|
|
||||||
cmdBar,
|
|
||||||
}) => {
|
|
||||||
await context.addInitScript((initialCode) => {
|
|
||||||
localStorage.setItem('persistCode', initialCode)
|
|
||||||
}, initialCode)
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
await scene.waitForExecutionDone()
|
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
|
||||||
const testPoint = { x: 550, y: 295 }
|
|
||||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
|
||||||
const shellDeclaration = `shell001 = shell({ faces = ['end'], thickness = 5 }, ${
|
|
||||||
hasExtrudesInPipe ? 'sketch002' : 'extrude002'
|
|
||||||
})`
|
|
||||||
|
|
||||||
await test.step(`Look for the grey of the shape`, async () => {
|
|
||||||
await toolbar.closePane('code')
|
|
||||||
await scene.expectPixelColor([128, 128, 128], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Go through the command bar flow, selecting a cap and keeping default thickness`, async () => {
|
|
||||||
await toolbar.shellButton.click()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'arguments',
|
|
||||||
currentArgKey: 'selection',
|
|
||||||
currentArgValue: '',
|
|
||||||
headerArguments: {
|
|
||||||
Selection: '',
|
|
||||||
Thickness: '',
|
|
||||||
},
|
|
||||||
highlightedHeaderArg: 'selection',
|
|
||||||
commandName: 'Shell',
|
|
||||||
})
|
|
||||||
await clickOnCap()
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'review',
|
|
||||||
headerArguments: {
|
|
||||||
Selection: '1 cap',
|
|
||||||
Thickness: '5',
|
|
||||||
},
|
|
||||||
commandName: 'Shell',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
|
||||||
await toolbar.openPane('code')
|
|
||||||
await editor.expectEditor.toContain(shellDeclaration)
|
|
||||||
await editor.expectState({
|
|
||||||
diagnostics: [],
|
|
||||||
activeLines: [shellDeclaration],
|
|
||||||
highlightedCode: '',
|
|
||||||
})
|
|
||||||
await toolbar.closePane('code')
|
|
||||||
await scene.expectPixelColor([73, 73, 73], testPoint, 15)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -32,10 +32,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'Google Chrome',
|
name: 'chromium',
|
||||||
use: {
|
use: {
|
||||||
...devices['Desktop Chrome'],
|
...devices['Desktop Chrome'],
|
||||||
channel: 'chrome',
|
|
||||||
contextOptions: {
|
contextOptions: {
|
||||||
/* Chromium is the only one with these permission types */
|
/* Chromium is the only one with these permission types */
|
||||||
permissions: ['clipboard-write', 'clipboard-read'],
|
permissions: ['clipboard-write', 'clipboard-read'],
|
||||||
|
|||||||
@ -47,6 +47,9 @@ export type ModelingCommandSchema = {
|
|||||||
Revolve: {
|
Revolve: {
|
||||||
selection: Selections
|
selection: Selections
|
||||||
angle: KclCommandValue
|
angle: KclCommandValue
|
||||||
|
axisOrEdge: string
|
||||||
|
axis: string
|
||||||
|
edge: Selections
|
||||||
}
|
}
|
||||||
Fillet: {
|
Fillet: {
|
||||||
// todo
|
// todo
|
||||||
@ -289,10 +292,44 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
|
Loft: {
|
||||||
|
description: 'Create a 3D body by blending between two or more sketches',
|
||||||
|
icon: 'loft',
|
||||||
|
needsReview: true,
|
||||||
|
args: {
|
||||||
|
selection: {
|
||||||
|
inputType: 'selection',
|
||||||
|
selectionTypes: ['solid2D'],
|
||||||
|
multiple: true,
|
||||||
|
required: true,
|
||||||
|
skip: false,
|
||||||
|
validation: loftValidator,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Shell: {
|
||||||
|
description: 'Hollow out a 3D solid.',
|
||||||
|
icon: 'shell',
|
||||||
|
needsReview: true,
|
||||||
|
args: {
|
||||||
|
selection: {
|
||||||
|
inputType: 'selection',
|
||||||
|
selectionTypes: ['cap', 'wall'],
|
||||||
|
multiple: true,
|
||||||
|
required: true,
|
||||||
|
skip: false,
|
||||||
|
},
|
||||||
|
thickness: {
|
||||||
|
inputType: 'kcl',
|
||||||
|
defaultValue: KCL_DEFAULT_LENGTH,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
Revolve: {
|
Revolve: {
|
||||||
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
||||||
icon: 'revolve',
|
icon: 'revolve',
|
||||||
|
status: 'development',
|
||||||
needsReview: true,
|
needsReview: true,
|
||||||
args: {
|
args: {
|
||||||
selection: {
|
selection: {
|
||||||
@ -301,6 +338,38 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
multiple: false, // TODO: multiple selection
|
multiple: false, // TODO: multiple selection
|
||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: true,
|
||||||
|
warningMessage:
|
||||||
|
'The revolve workflow is new and under tested. Please break it and report issues.',
|
||||||
|
},
|
||||||
|
axisOrEdge: {
|
||||||
|
inputType: 'options',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'Axis',
|
||||||
|
options: [
|
||||||
|
{ name: 'Axis', isCurrent: true, value: 'Axis' },
|
||||||
|
{ name: 'Edge', isCurrent: false, value: 'Edge' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
axis: {
|
||||||
|
required: (commandContext) =>
|
||||||
|
['Axis'].includes(
|
||||||
|
commandContext.argumentsToSubmit.axisOrEdge as string
|
||||||
|
),
|
||||||
|
inputType: 'options',
|
||||||
|
options: [
|
||||||
|
{ name: 'X Axis', isCurrent: true, value: 'X' },
|
||||||
|
{ name: 'Y Axis', isCurrent: false, value: 'Y' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
edge: {
|
||||||
|
required: (commandContext) =>
|
||||||
|
['Edge'].includes(
|
||||||
|
commandContext.argumentsToSubmit.axisOrEdge as string
|
||||||
|
),
|
||||||
|
inputType: 'selection',
|
||||||
|
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
||||||
|
multiple: false,
|
||||||
|
validation: revolveAxisValidator,
|
||||||
},
|
},
|
||||||
angle: {
|
angle: {
|
||||||
inputType: 'kcl',
|
inputType: 'kcl',
|
||||||
|
|||||||
@ -0,0 +1,155 @@
|
|||||||
|
import { Models } from '@kittycad/lib'
|
||||||
|
import { engineCommandManager } from 'lib/singletons'
|
||||||
|
import { uuidv4 } from 'lib/utils'
|
||||||
|
import { CommandBarContext } from 'machines/commandBarMachine'
|
||||||
|
import { Selections } from 'lib/selections'
|
||||||
|
|
||||||
|
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
||||||
|
for (let tries = 0; tries < numberOfRetries; tries++) {
|
||||||
|
try {
|
||||||
|
await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'disable_dry_run' },
|
||||||
|
})
|
||||||
|
// Exit out since the command was successful
|
||||||
|
return
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
console.error('disable_dry_run failed. This is bad!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a callback function and wraps it around enable_dry_run and disable_dry_run
|
||||||
|
export const dryRunWrapper = async (callback: () => Promise<any>) => {
|
||||||
|
// Gotcha: What about race conditions?
|
||||||
|
try {
|
||||||
|
await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'enable_dry_run' },
|
||||||
|
})
|
||||||
|
const result = await callback()
|
||||||
|
return result
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
await disableDryRunWithRetry(5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelections(selections: unknown): selections is Selections {
|
||||||
|
return (
|
||||||
|
(selections as Selections).graphSelections !== undefined &&
|
||||||
|
(selections as Selections).otherSelections !== undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const revolveAxisValidator = async ({
|
||||||
|
data,
|
||||||
|
context,
|
||||||
|
}: {
|
||||||
|
data: { [key: string]: Selections }
|
||||||
|
context: CommandBarContext
|
||||||
|
}): Promise<boolean | string> => {
|
||||||
|
if (!isSelections(context.argumentsToSubmit.selection)) {
|
||||||
|
return 'Unable to revolve, selections are missing'
|
||||||
|
}
|
||||||
|
const artifact =
|
||||||
|
context.argumentsToSubmit.selection.graphSelections[0].artifact
|
||||||
|
|
||||||
|
if (!artifact) {
|
||||||
|
return 'Unable to revolve, sketch not found'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('pathId' in artifact)) {
|
||||||
|
return 'Unable to revolve, sketch has no path'
|
||||||
|
}
|
||||||
|
|
||||||
|
const sketchSelection = artifact.pathId
|
||||||
|
let edgeSelection = data.edge.graphSelections[0].artifact?.id
|
||||||
|
|
||||||
|
if (!sketchSelection) {
|
||||||
|
return 'Unable to revolve, sketch is missing'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!edgeSelection) {
|
||||||
|
return 'Unable to revolve, edge is missing'
|
||||||
|
}
|
||||||
|
|
||||||
|
const angleInDegrees: Models['Angle_type'] = {
|
||||||
|
unit: 'degrees',
|
||||||
|
value: 360,
|
||||||
|
}
|
||||||
|
|
||||||
|
const revolveAboutEdgeCommand = async () => {
|
||||||
|
return await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'revolve_about_edge',
|
||||||
|
angle: angleInDegrees,
|
||||||
|
edge_id: edgeSelection,
|
||||||
|
target: sketchSelection,
|
||||||
|
tolerance: 0.0001,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const attemptRevolve = await dryRunWrapper(revolveAboutEdgeCommand)
|
||||||
|
if (attemptRevolve?.success) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// return error message for the toast
|
||||||
|
return 'Unable to revolve with selected edge'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loftValidator = async ({
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
data: { [key: string]: Selections }
|
||||||
|
context: CommandBarContext
|
||||||
|
}): Promise<boolean | string> => {
|
||||||
|
if (!isSelections(data.selection)) {
|
||||||
|
return 'Unable to loft, selections are missing'
|
||||||
|
}
|
||||||
|
const { selection } = data
|
||||||
|
|
||||||
|
if (selection.graphSelections.some((s) => s.artifact?.type !== 'solid2D')) {
|
||||||
|
return 'Unable to loft, some selection are not solid2Ds'
|
||||||
|
}
|
||||||
|
|
||||||
|
const sectionIds = data.selection.graphSelections.flatMap((s) =>
|
||||||
|
s.artifact?.type === 'solid2D' ? s.artifact.pathId : []
|
||||||
|
)
|
||||||
|
|
||||||
|
if (sectionIds.length < 2) {
|
||||||
|
return 'Unable to loft, selection contains less than two solid2Ds'
|
||||||
|
}
|
||||||
|
|
||||||
|
const loftCommand = async () => {
|
||||||
|
// TODO: check what to do with these
|
||||||
|
const DEFAULT_V_DEGREE = 2
|
||||||
|
const DEFAULT_TOLERANCE = 2
|
||||||
|
const DEFAULT_BEZ_APPROXIMATE_RATIONAL = false
|
||||||
|
return await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
section_ids: sectionIds,
|
||||||
|
type: 'loft',
|
||||||
|
bez_approximate_rational: DEFAULT_BEZ_APPROXIMATE_RATIONAL,
|
||||||
|
tolerance: DEFAULT_TOLERANCE,
|
||||||
|
v_degree: DEFAULT_V_DEGREE,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const attempt = await dryRunWrapper(loftCommand)
|
||||||
|
if (attempt?.success) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// return error message for the toast
|
||||||
|
return 'Unable to loft with selected sketches'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
StateMachineCommandSetSchema,
|
StateMachineCommandSetSchema,
|
||||||
} from './commandTypes'
|
} from './commandTypes'
|
||||||
import { DEV } from 'env'
|
import { DEV } from 'env'
|
||||||
|
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
|
||||||
|
|
||||||
interface CreateMachineCommandProps<
|
interface CreateMachineCommandProps<
|
||||||
T extends AnyStateMachine,
|
T extends AnyStateMachine,
|
||||||
@ -84,7 +85,7 @@ export function createMachineCommand<
|
|||||||
} else if ('status' in commandConfig) {
|
} else if ('status' in commandConfig) {
|
||||||
const { status } = commandConfig
|
const { status } = commandConfig
|
||||||
if (status === 'inactive') return null
|
if (status === 'inactive') return null
|
||||||
if (status === 'development' && !DEV) return null
|
if (status === 'development' && !(DEV || IS_NIGHTLY_OR_DEBUG)) return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const icon = ('icon' in commandConfig && commandConfig.icon) || undefined
|
const icon = ('icon' in commandConfig && commandConfig.icon) || undefined
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
modelingMachine,
|
modelingMachine,
|
||||||
pipeHasCircle,
|
pipeHasCircle,
|
||||||
} from 'machines/modelingMachine'
|
} from 'machines/modelingMachine'
|
||||||
|
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
|
||||||
import { EventFrom, StateFrom } from 'xstate'
|
import { EventFrom, StateFrom } from 'xstate'
|
||||||
|
|
||||||
export type ToolbarModeName = 'modeling' | 'sketching'
|
export type ToolbarModeName = 'modeling' | 'sketching'
|
||||||
@ -103,7 +104,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
data: { name: 'Revolve', groupId: 'modeling' },
|
data: { name: 'Revolve', groupId: 'modeling' },
|
||||||
}),
|
}),
|
||||||
icon: 'revolve',
|
icon: 'revolve',
|
||||||
status: DEV ? 'available' : 'kcl-only',
|
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
|
||||||
title: 'Revolve',
|
title: 'Revolve',
|
||||||
hotkey: 'R',
|
hotkey: 'R',
|
||||||
description:
|
description:
|
||||||
@ -161,7 +162,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
data: { name: 'Fillet', groupId: 'modeling' },
|
data: { name: 'Fillet', groupId: 'modeling' },
|
||||||
}),
|
}),
|
||||||
icon: 'fillet3d',
|
icon: 'fillet3d',
|
||||||
status: DEV ? 'available' : 'kcl-only',
|
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
|
||||||
title: 'Fillet',
|
title: 'Fillet',
|
||||||
hotkey: 'F',
|
hotkey: 'F',
|
||||||
description: 'Round the edges of a 3D solid.',
|
description: 'Round the edges of a 3D solid.',
|
||||||
|
|||||||
@ -32,6 +32,8 @@ export const PACKAGE_NAME = isDesktop()
|
|||||||
|
|
||||||
export const IS_NIGHTLY = PACKAGE_NAME.indexOf('-nightly') > -1
|
export const IS_NIGHTLY = PACKAGE_NAME.indexOf('-nightly') > -1
|
||||||
|
|
||||||
|
export const IS_NIGHTLY_OR_DEBUG = IS_NIGHTLY || APP_VERSION === '0.0.0'
|
||||||
|
|
||||||
export function getReleaseUrl(version: string = APP_VERSION) {
|
export function getReleaseUrl(version: string = APP_VERSION) {
|
||||||
return `https://github.com/KittyCAD/modeling-app/releases/tag/${
|
return `https://github.com/KittyCAD/modeling-app/releases/tag/${
|
||||||
IS_NIGHTLY ? 'nightly-' : ''
|
IS_NIGHTLY ? 'nightly-' : ''
|
||||||
|
|||||||
@ -1087,6 +1087,17 @@ async fn inner_start_sketch_on(
|
|||||||
|
|
||||||
Ok(SketchSurface::Plane(plane))
|
Ok(SketchSurface::Plane(plane))
|
||||||
}
|
}
|
||||||
|
SketchData::Plane(plane) => {
|
||||||
|
// Create artifact used only by the UI, not the engine.
|
||||||
|
let id = exec_state.next_uuid();
|
||||||
|
exec_state.add_artifact(Artifact {
|
||||||
|
id: ArtifactId::from(id),
|
||||||
|
inner: ArtifactInner::StartSketchOnPlane { plane_id: plane.id },
|
||||||
|
source_range: args.source_range,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(SketchSurface::Plane(plane))
|
||||||
|
}
|
||||||
SketchData::Plane(plane) => Ok(SketchSurface::Plane(plane)),
|
SketchData::Plane(plane) => Ok(SketchSurface::Plane(plane)),
|
||||||
SketchData::Solid(solid) => {
|
SketchData::Solid(solid) => {
|
||||||
let Some(tag) = tag else {
|
let Some(tag) = tag else {
|
||||||
|
|||||||
Reference in New Issue
Block a user