Compare commits
4 Commits
nightly-v2
...
jtran/fix-
Author | SHA1 | Date | |
---|---|---|---|
0c2f63b399 | |||
363ae10658 | |||
ac4a6c84cf | |||
c6fad2e2dc |
12
.eslintrc
12
.eslintrc
@ -5,16 +5,24 @@
|
|||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"css-modules",
|
"css-modules",
|
||||||
|
"jest",
|
||||||
|
"react",
|
||||||
"suggest-no-throw",
|
"suggest-no-throw",
|
||||||
|
"@typescript-eslint"
|
||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"react-app",
|
|
||||||
"react-app/jest",
|
|
||||||
"plugin:css-modules/recommended"
|
"plugin:css-modules/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-floating-promises": "error",
|
"@typescript-eslint/no-floating-promises": "error",
|
||||||
"@typescript-eslint/no-misused-promises": "error",
|
"@typescript-eslint/no-misused-promises": "error",
|
||||||
|
"no-restricted-globals": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"name": "isNaN",
|
||||||
|
"message": "Use Number.isNaN() instead."
|
||||||
|
}
|
||||||
|
],
|
||||||
"semi": [
|
"semi": [
|
||||||
"error",
|
"error",
|
||||||
"never"
|
"never"
|
||||||
|
@ -14,6 +14,7 @@ export class ToolbarFixture {
|
|||||||
|
|
||||||
extrudeButton!: Locator
|
extrudeButton!: Locator
|
||||||
loftButton!: Locator
|
loftButton!: Locator
|
||||||
|
sweepButton!: Locator
|
||||||
shellButton!: Locator
|
shellButton!: Locator
|
||||||
offsetPlaneButton!: Locator
|
offsetPlaneButton!: Locator
|
||||||
startSketchBtn!: Locator
|
startSketchBtn!: Locator
|
||||||
@ -40,6 +41,7 @@ export class ToolbarFixture {
|
|||||||
this.page = page
|
this.page = page
|
||||||
this.extrudeButton = page.getByTestId('extrude')
|
this.extrudeButton = page.getByTestId('extrude')
|
||||||
this.loftButton = page.getByTestId('loft')
|
this.loftButton = page.getByTestId('loft')
|
||||||
|
this.sweepButton = page.getByTestId('sweep')
|
||||||
this.shellButton = page.getByTestId('shell')
|
this.shellButton = page.getByTestId('shell')
|
||||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||||
this.startSketchBtn = page.getByTestId('sketch')
|
this.startSketchBtn = page.getByTestId('sketch')
|
||||||
|
@ -934,6 +934,104 @@ loft001 = loft([sketch001, sketch002])
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(`Sweep point-and-click`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const initialCode = `sketch001 = startSketchOn('YZ')
|
||||||
|
|> circle({
|
||||||
|
center = [0, 0],
|
||||||
|
radius = 500
|
||||||
|
}, %)
|
||||||
|
sketch002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> xLine(-500, %)
|
||||||
|
|> tangentialArcTo([-2000, 500], %)
|
||||||
|
`
|
||||||
|
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: 700, y: 250 }
|
||||||
|
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
|
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x - 50, testPoint.y)
|
||||||
|
const sweepDeclaration = 'sweep001 = sweep({ path = sketch002 }, sketch001)'
|
||||||
|
|
||||||
|
await test.step(`Look for sketch001`, async () => {
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Go through the command bar flow`, async () => {
|
||||||
|
await toolbar.sweepButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Sweep',
|
||||||
|
currentArgKey: 'profile',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Path: '',
|
||||||
|
Profile: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'profile',
|
||||||
|
stage: 'arguments',
|
||||||
|
})
|
||||||
|
await clickOnSketch1()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Sweep',
|
||||||
|
currentArgKey: 'path',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Path: '',
|
||||||
|
Profile: '1 face',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'path',
|
||||||
|
stage: 'arguments',
|
||||||
|
})
|
||||||
|
await clickOnSketch2()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Sweep',
|
||||||
|
headerArguments: {
|
||||||
|
Path: '1 face',
|
||||||
|
Profile: '1 face',
|
||||||
|
},
|
||||||
|
stage: 'review',
|
||||||
|
})
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
|
await scene.expectPixelColor([135, 64, 73], testPoint, 15)
|
||||||
|
await toolbar.openPane('code')
|
||||||
|
await editor.expectEditor.toContain(sweepDeclaration)
|
||||||
|
await editor.expectState({
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: [sweepDeclaration],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Delete sweep via feature tree selection', async () => {
|
||||||
|
await toolbar.openPane('feature-tree')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation('Sweep', 0)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await toolbar.closePane('feature-tree')
|
||||||
|
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const shellPointAndClickCapCases = [
|
const shellPointAndClickCapCases = [
|
||||||
{ shouldPreselect: true },
|
{ shouldPreselect: true },
|
||||||
{ shouldPreselect: false },
|
{ shouldPreselect: false },
|
||||||
|
10
package.json
10
package.json
@ -91,8 +91,8 @@
|
|||||||
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||||
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
|
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
|
||||||
"lint-fix": "eslint --fix src e2e packages/codemirror-lsp-client",
|
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
|
||||||
"lint": "eslint --max-warnings 0 src e2e packages/codemirror-lsp-client",
|
"lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
|
||||||
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||||
"files:set-notes": "./scripts/set-files-notes.sh",
|
"files:set-notes": "./scripts/set-files-notes.sh",
|
||||||
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
|
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
|
||||||
@ -171,8 +171,6 @@
|
|||||||
"@types/uuid": "^9.0.8",
|
"@types/uuid": "^9.0.8",
|
||||||
"@types/wicg-file-system-access": "^2023.10.5",
|
"@types/wicg-file-system-access": "^2023.10.5",
|
||||||
"@types/ws": "^8.5.13",
|
"@types/ws": "^8.5.13",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
|
||||||
"@typescript-eslint/parser": "^5.0.0",
|
|
||||||
"@vitejs/plugin-react": "^4.3.0",
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
"@vitest/web-worker": "^1.5.0",
|
"@vitest/web-worker": "^1.5.0",
|
||||||
"@xstate/cli": "^0.5.17",
|
"@xstate/cli": "^0.5.17",
|
||||||
@ -182,9 +180,10 @@
|
|||||||
"electron-builder": "24.13.3",
|
"electron-builder": "24.13.3",
|
||||||
"electron-notarize": "1.2.2",
|
"electron-notarize": "1.2.2",
|
||||||
"eslint": "^8.0.1",
|
"eslint": "^8.0.1",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
|
||||||
"eslint-plugin-css-modules": "^2.12.0",
|
"eslint-plugin-css-modules": "^2.12.0",
|
||||||
"eslint-plugin-import": "^2.30.0",
|
"eslint-plugin-import": "^2.30.0",
|
||||||
|
"eslint-plugin-jest": "^28.10.0",
|
||||||
|
"eslint-plugin-react": "^7.37.3",
|
||||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||||
"happy-dom": "^16.3.0",
|
"happy-dom": "^16.3.0",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
@ -200,6 +199,7 @@
|
|||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
|
"typescript-eslint": "^8.19.1",
|
||||||
"vite": "^5.4.6",
|
"vite": "^5.4.6",
|
||||||
"vite-plugin-package-version": "^1.1.0",
|
"vite-plugin-package-version": "^1.1.0",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
|
@ -42,7 +42,7 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
|
|||||||
// try to parse the content-length from the headers
|
// try to parse the content-length from the headers
|
||||||
const length = parseInt(match[1])
|
const length = parseInt(match[1])
|
||||||
|
|
||||||
if (isNaN(length))
|
if (Number.isNaN(length))
|
||||||
return Promise.reject(new Error('invalid content length'))
|
return Promise.reject(new Error('invalid content length'))
|
||||||
|
|
||||||
// slice the headers since we now have the content length
|
// slice the headers since we now have the content length
|
||||||
|
@ -157,39 +157,38 @@ export const ModelingMachineProvider = ({
|
|||||||
'enable copilot': () => {
|
'enable copilot': () => {
|
||||||
editorManager.setCopilotEnabled(true)
|
editorManager.setCopilotEnabled(true)
|
||||||
},
|
},
|
||||||
// tsc reports this typing as perfectly fine, but eslint is complaining.
|
'sketch exit execute': ({ context: { store } }) => {
|
||||||
// It's actually nonsensical, so I'm quieting.
|
// TODO: Remove this async callback. For some reason eslint wouldn't
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// let me disable @typescript-eslint/no-misused-promises for the line.
|
||||||
'sketch exit execute': async ({
|
;(async () => {
|
||||||
context: { store },
|
// When cancelling the sketch mode we should disable sketch mode within the engine.
|
||||||
}): Promise<void> => {
|
await engineCommandManager.sendSceneCommand({
|
||||||
// When cancelling the sketch mode we should disable sketch mode within the engine.
|
type: 'modeling_cmd_req',
|
||||||
await engineCommandManager.sendSceneCommand({
|
cmd_id: uuidv4(),
|
||||||
type: 'modeling_cmd_req',
|
cmd: { type: 'sketch_mode_disable' },
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: { type: 'sketch_mode_disable' },
|
|
||||||
})
|
|
||||||
|
|
||||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
|
||||||
|
|
||||||
if (cameraProjection.current === 'perspective') {
|
|
||||||
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
|
|
||||||
}
|
|
||||||
|
|
||||||
sceneInfra.camControls.syncDirection = 'engineToClient'
|
|
||||||
|
|
||||||
store.videoElement?.pause()
|
|
||||||
|
|
||||||
return kclManager
|
|
||||||
.executeCode()
|
|
||||||
.then(() => {
|
|
||||||
if (engineCommandManager.engineConnection?.idleMode) return
|
|
||||||
|
|
||||||
store.videoElement?.play().catch((e) => {
|
|
||||||
console.warn('Video playing was prevented', e)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.catch(reportRejection)
|
|
||||||
|
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||||
|
|
||||||
|
if (cameraProjection.current === 'perspective') {
|
||||||
|
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
|
||||||
|
}
|
||||||
|
|
||||||
|
sceneInfra.camControls.syncDirection = 'engineToClient'
|
||||||
|
|
||||||
|
store.videoElement?.pause()
|
||||||
|
|
||||||
|
return kclManager
|
||||||
|
.executeCode()
|
||||||
|
.then(() => {
|
||||||
|
if (engineCommandManager.engineConnection?.idleMode) return
|
||||||
|
|
||||||
|
store.videoElement?.play().catch((e) => {
|
||||||
|
console.warn('Video playing was prevented', e)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(reportRejection)
|
||||||
|
})().catch(reportRejection)
|
||||||
},
|
},
|
||||||
'Set mouse state': assign(({ context, event }) => {
|
'Set mouse state': assign(({ context, event }) => {
|
||||||
if (event.type !== 'Set mouse state') return {}
|
if (event.type !== 'Set mouse state') return {}
|
||||||
|
@ -374,6 +374,37 @@ export function loftSketches(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addSweep(
|
||||||
|
node: Node<Program>,
|
||||||
|
profileDeclarator: VariableDeclarator,
|
||||||
|
pathDeclarator: VariableDeclarator
|
||||||
|
): {
|
||||||
|
modifiedAst: Node<Program>
|
||||||
|
pathToNode: PathToNode
|
||||||
|
} {
|
||||||
|
const modifiedAst = structuredClone(node)
|
||||||
|
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SWEEP)
|
||||||
|
const sweep = createCallExpressionStdLib('sweep', [
|
||||||
|
createObjectExpression({ path: createIdentifier(pathDeclarator.id.name) }),
|
||||||
|
createIdentifier(profileDeclarator.id.name),
|
||||||
|
])
|
||||||
|
const declaration = createVariableDeclaration(name, sweep)
|
||||||
|
modifiedAst.body.push(declaration)
|
||||||
|
const pathToNode: PathToNode = [
|
||||||
|
['body', ''],
|
||||||
|
[modifiedAst.body.length - 1, 'index'],
|
||||||
|
['declaration', 'VariableDeclaration'],
|
||||||
|
['init', 'VariableDeclarator'],
|
||||||
|
['arguments', 'CallExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
modifiedAst,
|
||||||
|
pathToNode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function revolveSketch(
|
export function revolveSketch(
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
pathToNode: PathToNode,
|
pathToNode: PathToNode,
|
||||||
|
@ -77,7 +77,7 @@ interface SegmentArtifactRich extends BaseArtifact {
|
|||||||
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
|
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
|
||||||
interface SweepArtifact extends BaseArtifact {
|
interface SweepArtifact extends BaseArtifact {
|
||||||
type: 'sweep'
|
type: 'sweep'
|
||||||
subType: 'extrusion' | 'revolve' | 'loft'
|
subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
|
||||||
pathId: string
|
pathId: string
|
||||||
surfaceIds: Array<string>
|
surfaceIds: Array<string>
|
||||||
edgeIds: Array<string>
|
edgeIds: Array<string>
|
||||||
@ -85,7 +85,7 @@ interface SweepArtifact extends BaseArtifact {
|
|||||||
}
|
}
|
||||||
interface SweepArtifactRich extends BaseArtifact {
|
interface SweepArtifactRich extends BaseArtifact {
|
||||||
type: 'sweep'
|
type: 'sweep'
|
||||||
subType: 'extrusion' | 'revolve' | 'loft'
|
subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
|
||||||
path: PathArtifact
|
path: PathArtifact
|
||||||
surfaces: Array<WallArtifact | CapArtifact>
|
surfaces: Array<WallArtifact | CapArtifact>
|
||||||
edges: Array<SweepEdge>
|
edges: Array<SweepEdge>
|
||||||
@ -377,7 +377,11 @@ export function getArtifactsToUpdate({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return returnArr
|
return returnArr
|
||||||
} else if (cmd.type === 'extrude' || cmd.type === 'revolve') {
|
} else if (
|
||||||
|
cmd.type === 'extrude' ||
|
||||||
|
cmd.type === 'revolve' ||
|
||||||
|
cmd.type === 'sweep'
|
||||||
|
) {
|
||||||
const subType = cmd.type === 'extrude' ? 'extrusion' : cmd.type
|
const subType = cmd.type === 'extrude' ? 'extrusion' : cmd.type
|
||||||
returnArr.push({
|
returnArr.push({
|
||||||
id,
|
id,
|
||||||
|
@ -37,6 +37,10 @@ export type ModelingCommandSchema = {
|
|||||||
// result: (typeof EXTRUSION_RESULTS)[number]
|
// result: (typeof EXTRUSION_RESULTS)[number]
|
||||||
distance: KclCommandValue
|
distance: KclCommandValue
|
||||||
}
|
}
|
||||||
|
Sweep: {
|
||||||
|
path: Selections
|
||||||
|
profile: Selections
|
||||||
|
}
|
||||||
Loft: {
|
Loft: {
|
||||||
selection: Selections
|
selection: Selections
|
||||||
}
|
}
|
||||||
@ -292,6 +296,33 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Sweep: {
|
||||||
|
description:
|
||||||
|
'Create a 3D body by moving a sketch region along an arbitrary path.',
|
||||||
|
icon: 'sweep',
|
||||||
|
status: 'development',
|
||||||
|
needsReview: true,
|
||||||
|
args: {
|
||||||
|
profile: {
|
||||||
|
inputType: 'selection',
|
||||||
|
selectionTypes: ['solid2D'],
|
||||||
|
required: true,
|
||||||
|
skip: true,
|
||||||
|
multiple: false,
|
||||||
|
// TODO: add dry-run validation
|
||||||
|
warningMessage:
|
||||||
|
'The sweep workflow is new and under tested. Please break it and report issues.',
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
inputType: 'selection',
|
||||||
|
selectionTypes: ['segment', 'path'],
|
||||||
|
required: true,
|
||||||
|
skip: true,
|
||||||
|
multiple: false,
|
||||||
|
// TODO: add dry-run validation
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
Loft: {
|
Loft: {
|
||||||
description: 'Create a 3D body by blending between two or more sketches',
|
description: 'Create a 3D body by blending between two or more sketches',
|
||||||
icon: 'loft',
|
icon: 'loft',
|
||||||
|
@ -53,6 +53,7 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
|||||||
SKETCH: 'sketch',
|
SKETCH: 'sketch',
|
||||||
EXTRUDE: 'extrude',
|
EXTRUDE: 'extrude',
|
||||||
LOFT: 'loft',
|
LOFT: 'loft',
|
||||||
|
SWEEP: 'sweep',
|
||||||
SHELL: 'shell',
|
SHELL: 'shell',
|
||||||
SEGMENT: 'seg',
|
SEGMENT: 'seg',
|
||||||
REVOLVE: 'revolve',
|
REVOLVE: 'revolve',
|
||||||
|
@ -119,17 +119,21 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sweep',
|
id: 'sweep',
|
||||||
onClick: () => console.error('Sweep not yet implemented'),
|
onClick: ({ commandBarSend }) =>
|
||||||
|
commandBarSend({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: { name: 'Sweep', groupId: 'modeling' },
|
||||||
|
}),
|
||||||
icon: 'sweep',
|
icon: 'sweep',
|
||||||
status: 'unavailable',
|
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
|
||||||
title: 'Sweep',
|
title: 'Sweep',
|
||||||
hotkey: 'W',
|
hotkey: 'W',
|
||||||
description:
|
description:
|
||||||
'Create a 3D body by moving a sketch region along an arbitrary path.',
|
'Create a 3D body by moving a sketch region along an arbitrary path.',
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
label: 'GitHub discussion',
|
label: 'KCL docs',
|
||||||
url: 'https://github.com/KittyCAD/modeling-app/discussions/498',
|
url: 'https://zoo.dev/docs/kcl/sweep',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -345,7 +345,7 @@ export function onDragNumberCalculation(text: string, e: MouseEvent) {
|
|||||||
)
|
)
|
||||||
const newVal = roundOff(addition, precision)
|
const newVal = roundOff(addition, precision)
|
||||||
|
|
||||||
if (isNaN(newVal)) {
|
if (Number.isNaN(newVal)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ import {
|
|||||||
import { revolveSketch } from 'lang/modifyAst/addRevolve'
|
import { revolveSketch } from 'lang/modifyAst/addRevolve'
|
||||||
import {
|
import {
|
||||||
addOffsetPlane,
|
addOffsetPlane,
|
||||||
|
addSweep,
|
||||||
deleteFromSelection,
|
deleteFromSelection,
|
||||||
extrudeSketch,
|
extrudeSketch,
|
||||||
loftSketches,
|
loftSketches,
|
||||||
@ -266,6 +267,7 @@ export type ModelingMachineEvent =
|
|||||||
| { type: 'Export'; data: ModelingCommandSchema['Export'] }
|
| { type: 'Export'; data: ModelingCommandSchema['Export'] }
|
||||||
| { type: 'Make'; data: ModelingCommandSchema['Make'] }
|
| { type: 'Make'; data: ModelingCommandSchema['Make'] }
|
||||||
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
|
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
|
||||||
|
| { type: 'Sweep'; data?: ModelingCommandSchema['Sweep'] }
|
||||||
| { type: 'Loft'; data?: ModelingCommandSchema['Loft'] }
|
| { type: 'Loft'; data?: ModelingCommandSchema['Loft'] }
|
||||||
| { type: 'Shell'; data?: ModelingCommandSchema['Shell'] }
|
| { type: 'Shell'; data?: ModelingCommandSchema['Shell'] }
|
||||||
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
|
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
|
||||||
@ -1544,6 +1546,66 @@ export const modelingMachine = setup({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
sweepAstMod: fromPromise(
|
||||||
|
async ({
|
||||||
|
input,
|
||||||
|
}: {
|
||||||
|
input: ModelingCommandSchema['Sweep'] | undefined
|
||||||
|
}) => {
|
||||||
|
if (!input) return new Error('No input provided')
|
||||||
|
// Extract inputs
|
||||||
|
const ast = kclManager.ast
|
||||||
|
const { profile, path } = input
|
||||||
|
|
||||||
|
// Find the profile declaration
|
||||||
|
const profileNodePath = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
profile.graphSelections[0].codeRef.range
|
||||||
|
)
|
||||||
|
const profileNode = getNodeFromPath<VariableDeclarator>(
|
||||||
|
ast,
|
||||||
|
profileNodePath,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
if (err(profileNode)) {
|
||||||
|
return new Error("Couldn't parse profile selection")
|
||||||
|
}
|
||||||
|
const profileDeclarator = profileNode.node
|
||||||
|
|
||||||
|
// Find the path declaration
|
||||||
|
const pathNodePath = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
path.graphSelections[0].codeRef.range
|
||||||
|
)
|
||||||
|
const pathNode = getNodeFromPath<VariableDeclarator>(
|
||||||
|
ast,
|
||||||
|
pathNodePath,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
if (err(pathNode)) {
|
||||||
|
return new Error("Couldn't parse path selection")
|
||||||
|
}
|
||||||
|
const pathDeclarator = pathNode.node
|
||||||
|
|
||||||
|
// Perform the sweep
|
||||||
|
const sweepRes = addSweep(ast, profileDeclarator, pathDeclarator)
|
||||||
|
const updateAstResult = await kclManager.updateAst(
|
||||||
|
sweepRes.modifiedAst,
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
focusPath: [sweepRes.pathToNode],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||||
|
updateAstResult.newAst
|
||||||
|
)
|
||||||
|
|
||||||
|
if (updateAstResult?.selections) {
|
||||||
|
editorManager.selectRange(updateAstResult?.selections)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
loftAstMod: fromPromise(
|
loftAstMod: fromPromise(
|
||||||
async ({
|
async ({
|
||||||
input,
|
input,
|
||||||
@ -1739,6 +1801,11 @@ export const modelingMachine = setup({
|
|||||||
reenter: false,
|
reenter: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Sweep: {
|
||||||
|
target: 'Applying sweep',
|
||||||
|
reenter: true,
|
||||||
|
},
|
||||||
|
|
||||||
Loft: {
|
Loft: {
|
||||||
target: 'Applying loft',
|
target: 'Applying loft',
|
||||||
reenter: true,
|
reenter: true,
|
||||||
@ -2531,6 +2598,19 @@ export const modelingMachine = setup({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Applying sweep': {
|
||||||
|
invoke: {
|
||||||
|
src: 'sweepAstMod',
|
||||||
|
id: 'sweepAstMod',
|
||||||
|
input: ({ event }) => {
|
||||||
|
if (event.type !== 'Sweep') return undefined
|
||||||
|
return event.data
|
||||||
|
},
|
||||||
|
onDone: ['idle'],
|
||||||
|
onError: ['idle'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
'Applying loft': {
|
'Applying loft': {
|
||||||
invoke: {
|
invoke: {
|
||||||
src: 'loftAstMod',
|
src: 'loftAstMod',
|
||||||
|
Reference in New Issue
Block a user