both edit and move in one PR (#566)
* get the data for where lines are Signed-off-by: Jess Frazelle <github@jessfraz.com> * make pretty Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * new shit Signed-off-by: Jess Frazelle <github@jessfraz.com> * beginning of stufff Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * add new fns Signed-off-by: Jess Frazelle <github@jessfraz.com> * basic function Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix ups to keep order Signed-off-by: Jess Frazelle <github@jessfraz.com> * further Signed-off-by: Jess Frazelle <github@jessfraz.com> * failing test Signed-off-by: Jess Frazelle <github@jessfraz.com> * do it in rust Signed-off-by: Jess Frazelle <github@jessfraz.com> * trait Signed-off-by: Jess Frazelle <github@jessfraz.com> * start of ui integration Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * weird shit Signed-off-by: Jess Frazelle <github@jessfraz.com> * generate close on close Signed-off-by: Jess Frazelle <github@jessfraz.com> * start of constraint functions Signed-off-by: Jess Frazelle <github@jessfraz.com> * helper functions Signed-off-by: Jess Frazelle <github@jessfraz.com> * make work Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * constraints w ranges Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * skip Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * comment Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * throw Signed-off-by: Jess Frazelle <github@jessfraz.com> * make close a bit less sensitive in move scenario Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup shit we didnt end up using Signed-off-by: Jess Frazelle <github@jessfraz.com> * make it less hard to close Signed-off-by: Jess Frazelle <github@jessfraz.com> * Fix edit after sketch * Move to plane for sketch * Fix pathToNode for ast mods * Fix exit sketch mode with escape * Fix fmt since my editor did it wrong * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix link Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Adam Sunderland <adam@kittycad.io>
This commit is contained in:
2
.github/workflows/cargo-test.yml
vendored
2
.github/workflows/cargo-test.yml
vendored
@ -55,7 +55,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |-
|
run: |-
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
cargo test --all
|
cargo nextest run --workspace --no-fail-fast -P ci
|
||||||
env:
|
env:
|
||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||||
|
|
||||||
|
14
src/App.tsx
14
src/App.tsx
@ -47,6 +47,7 @@ export function App() {
|
|||||||
streamDimensions,
|
streamDimensions,
|
||||||
guiMode,
|
guiMode,
|
||||||
setGuiMode,
|
setGuiMode,
|
||||||
|
executeAst,
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
guiMode: s.guiMode,
|
guiMode: s.guiMode,
|
||||||
setGuiMode: s.setGuiMode,
|
setGuiMode: s.setGuiMode,
|
||||||
@ -57,6 +58,7 @@ export function App() {
|
|||||||
setOpenPanes: s.setOpenPanes,
|
setOpenPanes: s.setOpenPanes,
|
||||||
didDragInStream: s.didDragInStream,
|
didDragInStream: s.didDragInStream,
|
||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
|
executeAst: s.executeAst,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -87,12 +89,23 @@ export function App() {
|
|||||||
if (guiMode.mode === 'sketch') {
|
if (guiMode.mode === 'sketch') {
|
||||||
if (guiMode.sketchMode === 'selectFace') return
|
if (guiMode.sketchMode === 'selectFace') return
|
||||||
if (guiMode.sketchMode === 'sketchEdit') {
|
if (guiMode.sketchMode === 'sketchEdit') {
|
||||||
|
// TODO: share this with Toolbar's "Exit sketch" button
|
||||||
|
// exiting sketch should be done consistently across all exits
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager?.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: { type: 'edit_mode_exit' },
|
cmd: { type: 'edit_mode_exit' },
|
||||||
})
|
})
|
||||||
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||||
|
})
|
||||||
setGuiMode({ mode: 'default' })
|
setGuiMode({ mode: 'default' })
|
||||||
|
// this is necessary to get the UI back into a consistent
|
||||||
|
// state right now, hopefully won't need to rerender
|
||||||
|
// when exiting sketch mode in the future
|
||||||
|
executeAst()
|
||||||
} else {
|
} else {
|
||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager?.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
@ -108,6 +121,7 @@ export function App() {
|
|||||||
rotation: guiMode.rotation,
|
rotation: guiMode.rotation,
|
||||||
position: guiMode.position,
|
position: guiMode.position,
|
||||||
pathToNode: guiMode.pathToNode,
|
pathToNode: guiMode.pathToNode,
|
||||||
|
pathId: guiMode.pathId,
|
||||||
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -109,21 +109,17 @@ export const Toolbar = () => {
|
|||||||
{guiMode.mode === 'canEditSketch' && (
|
{guiMode.mode === 'canEditSketch' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log('guiMode.pathId', guiMode.pathId)
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
engineCommandManager?.sendSceneCommand({
|
ast,
|
||||||
type: 'modeling_cmd_req',
|
selectionRanges.codeBasedSelections[0].range
|
||||||
cmd_id: uuidv4(),
|
)
|
||||||
cmd: {
|
|
||||||
type: 'edit_mode_enter',
|
|
||||||
target: guiMode.pathId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
mode: 'sketch',
|
mode: 'sketch',
|
||||||
sketchMode: 'sketchEdit',
|
sketchMode: 'enterSketchEdit',
|
||||||
pathToNode: guiMode.pathToNode,
|
pathToNode: pathToNode,
|
||||||
rotation: guiMode.rotation,
|
rotation: [0, 0, 0, 1],
|
||||||
position: guiMode.position,
|
position: [0, 0, 0],
|
||||||
|
pathId: guiMode.pathId,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
className="group"
|
className="group"
|
||||||
@ -240,6 +236,7 @@ export const Toolbar = () => {
|
|||||||
sketchMode: sketchFnName,
|
sketchMode: sketchFnName,
|
||||||
waitingFirstClick: true,
|
waitingFirstClick: true,
|
||||||
isTooltip: true,
|
isTooltip: true,
|
||||||
|
pathId: guiMode.pathId,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
@ -40,12 +40,12 @@ const DownloadAppBanner = () => {
|
|||||||
</code>
|
</code>
|
||||||
, and isn't backed up anywhere! Visit{' '}
|
, and isn't backed up anywhere! Visit{' '}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/KittyCAD/modeling-app/releases"
|
href="https://kittycad.io/modeling-app/download"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
|
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
|
||||||
>
|
>
|
||||||
our GitHub repository
|
our website
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
to download the app for the best experience.
|
to download the app for the best experience.
|
||||||
</p>
|
</p>
|
||||||
|
@ -21,6 +21,10 @@ import {
|
|||||||
} from 'lang/std/sketch'
|
} from 'lang/std/sketch'
|
||||||
import { getNodeFromPath } from 'lang/queryAst'
|
import { getNodeFromPath } from 'lang/queryAst'
|
||||||
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
|
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
|
||||||
|
import { modify_ast_for_sketch } from '../wasm-lib/pkg/wasm_lib'
|
||||||
|
import { KCLError } from 'lang/errors'
|
||||||
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
|
import { rangeTypeFix } from 'lang/abstractSyntaxTree'
|
||||||
|
|
||||||
export const Stream = ({ className = '' }) => {
|
export const Stream = ({ className = '' }) => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
@ -211,14 +215,9 @@ export const Stream = ({ className = '' }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
engineCommandManager?.sendSceneCommand(command).then(async (resp) => {
|
engineCommandManager?.sendSceneCommand(command).then(async (resp) => {
|
||||||
if (command?.cmd?.type !== 'mouse_click' || !ast) return
|
if (!(guiMode.mode === 'sketch')) return
|
||||||
if (
|
|
||||||
!(
|
if (guiMode.sketchMode === 'selectFace') return
|
||||||
guiMode.mode === 'sketch' &&
|
|
||||||
guiMode.sketchMode === ('sketch_line' as any as 'line')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
// Check if the sketch group already exists.
|
// Check if the sketch group already exists.
|
||||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
@ -230,6 +229,56 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const sketchGroup = programMemory.root[variableName]
|
const sketchGroup = programMemory.root[variableName]
|
||||||
const isEditingExistingSketch =
|
const isEditingExistingSketch =
|
||||||
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
|
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
|
||||||
|
let sketchGroupId = ''
|
||||||
|
if (sketchGroup && sketchGroup.type === 'SketchGroup') {
|
||||||
|
sketchGroupId = sketchGroup.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
guiMode.sketchMode === ('move' as any as 'line') &&
|
||||||
|
command.cmd.type === 'handle_mouse_drag_end'
|
||||||
|
) {
|
||||||
|
// Let's get the updated ast.
|
||||||
|
if (sketchGroupId === '') return
|
||||||
|
|
||||||
|
console.log('guiMode.pathId', guiMode.pathId)
|
||||||
|
|
||||||
|
// We have a problem if we do not have an id for the sketch group.
|
||||||
|
if (
|
||||||
|
guiMode.pathId === undefined ||
|
||||||
|
guiMode.pathId === null ||
|
||||||
|
guiMode.pathId === ''
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
let engineId = guiMode.pathId
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedAst: Program = await modify_ast_for_sketch(
|
||||||
|
engineCommandManager,
|
||||||
|
JSON.stringify(ast),
|
||||||
|
variableName,
|
||||||
|
engineId
|
||||||
|
)
|
||||||
|
|
||||||
|
updateAst(updatedAst, false)
|
||||||
|
} catch (e: any) {
|
||||||
|
const parsed: RustKclError = JSON.parse(e.toString())
|
||||||
|
const kclError = new KCLError(
|
||||||
|
parsed.kind,
|
||||||
|
parsed.msg,
|
||||||
|
rangeTypeFix(parsed.sourceRanges)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(kclError)
|
||||||
|
throw kclError
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command?.cmd?.type !== 'mouse_click' || !ast) return
|
||||||
|
|
||||||
|
if (!(guiMode.sketchMode === ('sketch_line' as any as 'line'))) return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
resp?.data?.data?.entities_modified?.length &&
|
resp?.data?.data?.entities_modified?.length &&
|
||||||
@ -257,6 +306,16 @@ export const Stream = ({ className = '' }) => {
|
|||||||
const _modifiedAst = _addStartSketch.modifiedAst
|
const _modifiedAst = _addStartSketch.modifiedAst
|
||||||
const _pathToNode = _addStartSketch.pathToNode
|
const _pathToNode = _addStartSketch.pathToNode
|
||||||
|
|
||||||
|
// We need to update the guiMode with the right pathId so that we can
|
||||||
|
// move lines later and send the right sketch id to the engine.
|
||||||
|
for (const [id, artifact] of Object.entries(
|
||||||
|
engineCommandManager.artifactMap
|
||||||
|
)) {
|
||||||
|
if (artifact.commandType === 'start_path') {
|
||||||
|
guiMode.pathId = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setGuiMode({
|
setGuiMode({
|
||||||
...guiMode,
|
...guiMode,
|
||||||
pathToNode: _pathToNode,
|
pathToNode: _pathToNode,
|
||||||
@ -315,9 +374,12 @@ export const Stream = ({ className = '' }) => {
|
|||||||
engineCommandManager?.sendSceneCommand({
|
engineCommandManager?.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: { type: 'edit_mode_exit' },
|
||||||
type: 'sketch_mode_disable',
|
})
|
||||||
},
|
engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||||
})
|
})
|
||||||
updateAst(_modifiedAst, true)
|
updateAst(_modifiedAst, true)
|
||||||
}
|
}
|
||||||
|
@ -37,27 +37,62 @@ export function useAppMode() {
|
|||||||
guiMode.sketchMode === 'selectFace' &&
|
guiMode.sketchMode === 'selectFace' &&
|
||||||
engineCommandManager
|
engineCommandManager
|
||||||
) {
|
) {
|
||||||
|
const createAndShowPlanes = async () => {
|
||||||
|
let localDefaultPlanes: DefaultPlanes
|
||||||
if (!defaultPlanes) {
|
if (!defaultPlanes) {
|
||||||
const xy = createPlane(engineCommandManager, {
|
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
|
||||||
x_axis: { x: 1, y: 0, z: 0 },
|
setDefaultPlanes(localDefaultPlanes)
|
||||||
y_axis: { x: 0, y: 1, z: 0 },
|
|
||||||
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
|
||||||
})
|
|
||||||
// TODO re-enable
|
|
||||||
// const yz = createPlane(engineCommandManager, {
|
|
||||||
// x_axis: { x: 0, y: 1, z: 0 },
|
|
||||||
// y_axis: { x: 0, y: 0, z: 1 },
|
|
||||||
// color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
|
||||||
// })
|
|
||||||
// const xz = createPlane(engineCommandManager, {
|
|
||||||
// x_axis: { x: 1, y: 0, z: 0 },
|
|
||||||
// y_axis: { x: 0, y: 0, z: 1 },
|
|
||||||
// color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
|
||||||
// })
|
|
||||||
setDefaultPlanes({ xy })
|
|
||||||
} else {
|
} else {
|
||||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, false)
|
localDefaultPlanes = defaultPlanes
|
||||||
}
|
}
|
||||||
|
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, false)
|
||||||
|
}
|
||||||
|
createAndShowPlanes()
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
guiMode.mode === 'sketch' &&
|
||||||
|
guiMode.sketchMode === 'enterSketchEdit' &&
|
||||||
|
engineCommandManager
|
||||||
|
) {
|
||||||
|
const enableSketchMode = async () => {
|
||||||
|
let localDefaultPlanes: DefaultPlanes
|
||||||
|
if (!defaultPlanes) {
|
||||||
|
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
|
||||||
|
setDefaultPlanes(localDefaultPlanes)
|
||||||
|
} else {
|
||||||
|
localDefaultPlanes = defaultPlanes
|
||||||
|
}
|
||||||
|
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, true)
|
||||||
|
// TODO figure out the plane to use based on the sketch
|
||||||
|
// maybe it's easier to make a new plane than rely on the defaults
|
||||||
|
await engineCommandManager?.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'sketch_mode_enable',
|
||||||
|
plane_id: localDefaultPlanes.xy,
|
||||||
|
ortho: true,
|
||||||
|
animated: !isReducedMotion(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const proms: any[] = []
|
||||||
|
proms.push(
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'edit_mode_enter',
|
||||||
|
target: guiMode.pathId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await Promise.all(proms)
|
||||||
|
}
|
||||||
|
enableSketchMode()
|
||||||
|
setGuiMode({
|
||||||
|
...guiMode,
|
||||||
|
sketchMode: 'sketchEdit',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (guiMode.mode !== 'sketch' && defaultPlanes) {
|
if (guiMode.mode !== 'sketch' && defaultPlanes) {
|
||||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
||||||
@ -151,6 +186,7 @@ export function useAppMode() {
|
|||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
pathToNode: [],
|
pathToNode: [],
|
||||||
|
pathId: sketchUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('sketchModeResponse', sketchModeResponse)
|
console.log('sketchModeResponse', sketchModeResponse)
|
||||||
@ -160,7 +196,7 @@ export function useAppMode() {
|
|||||||
}, [engineCommandManager, defaultPlanes])
|
}, [engineCommandManager, defaultPlanes])
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPlane(
|
async function createPlane(
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
{
|
{
|
||||||
x_axis,
|
x_axis,
|
||||||
@ -173,7 +209,7 @@ function createPlane(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const planeId = uuidv4()
|
const planeId = uuidv4()
|
||||||
engineCommandManager?.sendSceneCommand({
|
await engineCommandManager?.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'make_plane',
|
type: 'make_plane',
|
||||||
@ -185,7 +221,7 @@ function createPlane(
|
|||||||
},
|
},
|
||||||
cmd_id: planeId,
|
cmd_id: planeId,
|
||||||
})
|
})
|
||||||
engineCommandManager?.sendSceneCommand({
|
await engineCommandManager?.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'plane_set_color',
|
type: 'plane_set_color',
|
||||||
@ -215,6 +251,28 @@ function setDefaultPlanesHidden(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function initDefaultPlanes(
|
||||||
|
engineCommandManager: EngineCommandManager
|
||||||
|
): Promise<DefaultPlanes> {
|
||||||
|
const xy = await createPlane(engineCommandManager, {
|
||||||
|
x_axis: { x: 1, y: 0, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 1, z: 0 },
|
||||||
|
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
||||||
|
})
|
||||||
|
// TODO re-enable
|
||||||
|
// const yz = createPlane(engineCommandManager, {
|
||||||
|
// x_axis: { x: 0, y: 1, z: 0 },
|
||||||
|
// y_axis: { x: 0, y: 0, z: 1 },
|
||||||
|
// color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
||||||
|
// })
|
||||||
|
// const xz = createPlane(engineCommandManager, {
|
||||||
|
// x_axis: { x: 1, y: 0, z: 0 },
|
||||||
|
// y_axis: { x: 0, y: 0, z: 1 },
|
||||||
|
// color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
||||||
|
// })
|
||||||
|
return { xy }
|
||||||
|
}
|
||||||
|
|
||||||
function isCursorInSketchCommandRange(
|
function isCursorInSketchCommandRange(
|
||||||
artifactMap: ArtifactMap,
|
artifactMap: ArtifactMap,
|
||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
|
@ -15,9 +15,13 @@ interface CommandInfo {
|
|||||||
range: SourceRange
|
range: SourceRange
|
||||||
parentId?: string
|
parentId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
||||||
|
|
||||||
interface ResultCommand extends CommandInfo {
|
interface ResultCommand extends CommandInfo {
|
||||||
type: 'result'
|
type: 'result'
|
||||||
data: any
|
data: any
|
||||||
|
raw: WebSocketResponse
|
||||||
}
|
}
|
||||||
interface PendingCommand extends CommandInfo {
|
interface PendingCommand extends CommandInfo {
|
||||||
type: 'pending'
|
type: 'pending'
|
||||||
@ -37,8 +41,6 @@ interface NewTrackArgs {
|
|||||||
mediaStream: MediaStream
|
mediaStream: MediaStream
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
|
||||||
|
|
||||||
type ClientMetrics = Models['ClientMetrics_type']
|
type ClientMetrics = Models['ClientMetrics_type']
|
||||||
|
|
||||||
// EngineConnection encapsulates the connection(s) to the Engine
|
// EngineConnection encapsulates the connection(s) to the Engine
|
||||||
@ -652,12 +654,14 @@ export class EngineCommandManager {
|
|||||||
commandType: command.commandType,
|
commandType: command.commandType,
|
||||||
parentId: command.parentId ? command.parentId : undefined,
|
parentId: command.parentId ? command.parentId : undefined,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
|
raw: message,
|
||||||
}
|
}
|
||||||
resolve({
|
resolve({
|
||||||
id,
|
id,
|
||||||
commandType: command.commandType,
|
commandType: command.commandType,
|
||||||
range: command.range,
|
range: command.range,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
|
raw: message,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.artifactMap[id] = {
|
this.artifactMap[id] = {
|
||||||
@ -665,6 +669,7 @@ export class EngineCommandManager {
|
|||||||
commandType: command?.commandType,
|
commandType: command?.commandType,
|
||||||
range: command?.range,
|
range: command?.range,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
|
raw: message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -873,7 +878,10 @@ export class EngineCommandManager {
|
|||||||
}
|
}
|
||||||
const range: SourceRange = JSON.parse(rangeStr)
|
const range: SourceRange = JSON.parse(rangeStr)
|
||||||
|
|
||||||
return this.sendModelingCommand({ id, range, command: commandStr })
|
// We only care about the modeling command response.
|
||||||
|
return this.sendModelingCommand({ id, range, command: commandStr }).then(
|
||||||
|
({ raw }) => JSON.stringify(raw)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
commandResult(id: string): Promise<any> {
|
commandResult(id: string): Promise<any> {
|
||||||
const command = this.artifactMap[id]
|
const command = this.artifactMap[id]
|
||||||
@ -943,7 +951,6 @@ export class EngineCommandManager {
|
|||||||
pathInfos.forEach(({ originalId, segments }) => {
|
pathInfos.forEach(({ originalId, segments }) => {
|
||||||
const originalArtifact = this.artifactMap[originalId]
|
const originalArtifact = this.artifactMap[originalId]
|
||||||
if (!originalArtifact || originalArtifact.type === 'pending') {
|
if (!originalArtifact || originalArtifact.type === 'pending') {
|
||||||
console.log('problem')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const pipeExpPath = getNodePathFromSourceRange(
|
const pipeExpPath = getNodePathFromSourceRange(
|
||||||
@ -956,23 +963,20 @@ export class EngineCommandManager {
|
|||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
).node
|
).node
|
||||||
if (pipeExp.type !== 'VariableDeclarator') {
|
if (pipeExp.type !== 'VariableDeclarator') {
|
||||||
console.log('problem', pipeExp, pipeExpPath, ast)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const variableName = pipeExp.id.name
|
const variableName = pipeExp.id.name
|
||||||
const memoryItem = programMemory.root[variableName]
|
const memoryItem = programMemory.root[variableName]
|
||||||
if (!memoryItem) {
|
if (!memoryItem) {
|
||||||
console.log('problem', variableName, programMemory)
|
|
||||||
return
|
return
|
||||||
} else if (memoryItem.type !== 'SketchGroup') {
|
} else if (memoryItem.type !== 'SketchGroup') {
|
||||||
console.log('problem', memoryItem, programMemory)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const relevantSegments = segments.filter(
|
const relevantSegments = segments.filter(
|
||||||
({ command_id }: { command_id: string | null }) => command_id
|
({ command_id }: { command_id: string | null }) => command_id
|
||||||
)
|
)
|
||||||
if (memoryItem.value.length !== relevantSegments.length) {
|
if (memoryItem.value.length !== relevantSegments.length) {
|
||||||
console.log('problem', memoryItem.value, relevantSegments)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (let i = 0; i < relevantSegments.length; i++) {
|
for (let i = 0; i < relevantSegments.length; i++) {
|
||||||
@ -982,9 +986,11 @@ export class EngineCommandManager {
|
|||||||
const artifact = this.artifactMap[oldId]
|
const artifact = this.artifactMap[oldId]
|
||||||
delete this.artifactMap[oldId]
|
delete this.artifactMap[oldId]
|
||||||
delete this.sourceRangeMap[oldId]
|
delete this.sourceRangeMap[oldId]
|
||||||
|
if (artifact) {
|
||||||
this.artifactMap[engineSegment.command_id] = artifact
|
this.artifactMap[engineSegment.command_id] = artifact
|
||||||
this.sourceRangeMap[engineSegment.command_id] = artifact.range
|
this.sourceRangeMap[engineSegment.command_id] = artifact.range
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,7 @@ show(mySketch001)
|
|||||||
{
|
{
|
||||||
mode: 'sketch',
|
mode: 'sketch',
|
||||||
sketchMode: 'sketchEdit',
|
sketchMode: 'sketchEdit',
|
||||||
|
pathId: '',
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
pathToNode: [
|
pathToNode: [
|
||||||
|
@ -71,7 +71,7 @@ export type GuiModes =
|
|||||||
waitingFirstClick: boolean
|
waitingFirstClick: boolean
|
||||||
rotation: Rotation
|
rotation: Rotation
|
||||||
position: Position
|
position: Position
|
||||||
id?: string
|
pathId: string
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@ -80,6 +80,15 @@ export type GuiModes =
|
|||||||
rotation: Rotation
|
rotation: Rotation
|
||||||
position: Position
|
position: Position
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
|
pathId: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
mode: 'sketch'
|
||||||
|
sketchMode: 'enterSketchEdit'
|
||||||
|
rotation: Rotation
|
||||||
|
position: Position
|
||||||
|
pathToNode: PathToNode
|
||||||
|
pathId: string
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
mode: 'sketch'
|
mode: 'sketch'
|
||||||
|
22
src/wasm-lib/.config/nextest.toml
Normal file
22
src/wasm-lib/.config/nextest.toml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Each test can have at most 4 threads, but if its name contains "serial_test_", then it
|
||||||
|
# also requires 4 threads.
|
||||||
|
# This means such tests run one at a time, with 4 threads.
|
||||||
|
|
||||||
|
[test-groups]
|
||||||
|
serial-integration = { max-threads = 4 }
|
||||||
|
|
||||||
|
[profile.default]
|
||||||
|
slow-timeout = { period = "10s", terminate-after = 1 }
|
||||||
|
|
||||||
|
[profile.ci]
|
||||||
|
slow-timeout = { period = "60s", terminate-after = 10 }
|
||||||
|
|
||||||
|
[[profile.default.overrides]]
|
||||||
|
filter = "test(serial_test_)"
|
||||||
|
test-group = "serial-integration"
|
||||||
|
threads-required = 4
|
||||||
|
|
||||||
|
[[profile.ci.overrides]]
|
||||||
|
filter = "test(serial_test_)"
|
||||||
|
test-group = "serial-integration"
|
||||||
|
threads-required = 4
|
5
src/wasm-lib/Cargo.lock
generated
5
src/wasm-lib/Cargo.lock
generated
@ -1310,9 +1310,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.1.28"
|
version = "0.1.29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
"bson",
|
"bson",
|
||||||
"clap",
|
"clap",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@ -1338,6 +1339,7 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3441,6 +3443,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"kcl-lib",
|
"kcl-lib",
|
||||||
"kittycad",
|
"kittycad",
|
||||||
|
"pretty_assertions",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -13,6 +13,7 @@ gloo-utils = "0.2.0"
|
|||||||
kcl-lib = { path = "kcl" }
|
kcl-lib = { path = "kcl" }
|
||||||
kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
|
kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
|
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
||||||
wasm-bindgen = "0.2.87"
|
wasm-bindgen = "0.2.87"
|
||||||
wasm-bindgen-futures = "0.4.37"
|
wasm-bindgen-futures = "0.4.37"
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ wasm-bindgen-futures = "0.4.37"
|
|||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
image = "0.24.7"
|
image = "0.24.7"
|
||||||
kittycad = "0.2.25"
|
kittycad = "0.2.25"
|
||||||
|
pretty_assertions = "1.4.0"
|
||||||
reqwest = { version = "0.11.20", default-features = false }
|
reqwest = { version = "0.11.20", default-features = false }
|
||||||
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
|
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||||
twenty-twenty = "0.6.1"
|
twenty-twenty = "0.6.1"
|
||||||
@ -50,3 +52,11 @@ members = [
|
|||||||
"derive-docs",
|
"derive-docs",
|
||||||
"kcl",
|
"kcl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "executor"
|
||||||
|
path = "tests/executor/main.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "modify"
|
||||||
|
path = "tests/modify/main.rs"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language"
|
description = "KittyCAD Language"
|
||||||
version = "0.1.28"
|
version = "0.1.29"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ license = "MIT"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.75", features = ["backtrace"] }
|
anyhow = { version = "1.0.75", features = ["backtrace"] }
|
||||||
|
async-trait = "0.1.73"
|
||||||
clap = { version = "4.4.3", features = ["cargo", "derive", "env", "unicode"] }
|
clap = { version = "4.4.3", features = ["cargo", "derive", "env", "unicode"] }
|
||||||
dashmap = "5.5.3"
|
dashmap = "5.5.3"
|
||||||
derive-docs = { version = "0.1.3" }
|
derive-docs = { version = "0.1.3" }
|
||||||
@ -29,6 +30,7 @@ js-sys = { version = "0.3.64" }
|
|||||||
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
|
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
|
||||||
wasm-bindgen = "0.2.87"
|
wasm-bindgen = "0.2.87"
|
||||||
wasm-bindgen-futures = "0.4.37"
|
wasm-bindgen-futures = "0.4.37"
|
||||||
|
web-sys = { version = "0.3.64", features = ["console"] }
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
||||||
|
2
src/wasm-lib/kcl/src/ast/mod.rs
Normal file
2
src/wasm-lib/kcl/src/ast/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod modify;
|
||||||
|
pub mod types;
|
293
src/wasm-lib/kcl/src/ast/modify.rs
Normal file
293
src/wasm-lib/kcl/src/ast/modify.rs
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
use kittycad::types::{ModelingCmd, Point3D};
|
||||||
|
|
||||||
|
use super::types::ConstraintLevel;
|
||||||
|
use crate::{
|
||||||
|
ast::types::{
|
||||||
|
ArrayExpression, CallExpression, FormatOptions, Literal, PipeExpression, PipeSubstitution, Program,
|
||||||
|
VariableDeclarator,
|
||||||
|
},
|
||||||
|
engine::{EngineConnection, EngineManager},
|
||||||
|
errors::{KclError, KclErrorDetails},
|
||||||
|
executor::{Point2d, SourceRange},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// The control point data for a curve or line.
|
||||||
|
pub struct ControlPointData {
|
||||||
|
/// The control points for the curve or line.
|
||||||
|
pub points: Vec<kittycad::types::Point3D>,
|
||||||
|
/// The command that created this curve or line.
|
||||||
|
pub command: kittycad::types::PathCommand,
|
||||||
|
/// The id of the curve or line.
|
||||||
|
pub id: uuid::Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
const EPSILON: f64 = 0.015625; // or 2^-6
|
||||||
|
|
||||||
|
/// Update the AST to reflect the new state of the program after something like
|
||||||
|
/// a move or a new line.
|
||||||
|
pub async fn modify_ast_for_sketch(
|
||||||
|
engine: &mut EngineConnection,
|
||||||
|
program: &mut Program,
|
||||||
|
// The name of the sketch.
|
||||||
|
sketch_name: &str,
|
||||||
|
// The ID of the parent sketch.
|
||||||
|
sketch_id: uuid::Uuid,
|
||||||
|
) -> Result<String, KclError> {
|
||||||
|
// First we need to check if this sketch is constrained (even partially).
|
||||||
|
// If it is, we cannot modify it.
|
||||||
|
|
||||||
|
// Get the information about the sketch.
|
||||||
|
if let Some(ast_sketch) = program.get_variable(sketch_name) {
|
||||||
|
let constraint_level = ast_sketch.get_constraint_level();
|
||||||
|
match &constraint_level {
|
||||||
|
ConstraintLevel::None { source_ranges: _ } => {}
|
||||||
|
ConstraintLevel::Ignore { source_ranges: _ } => {}
|
||||||
|
ConstraintLevel::Partial {
|
||||||
|
source_ranges: _,
|
||||||
|
levels,
|
||||||
|
} => {
|
||||||
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"Sketch {} is constrained `{}` and cannot be modified",
|
||||||
|
sketch_name, constraint_level
|
||||||
|
),
|
||||||
|
source_ranges: levels.get_all_partial_or_full_source_ranges(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
ConstraintLevel::Full { source_ranges } => {
|
||||||
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"Sketch {} is constrained `{}` and cannot be modified",
|
||||||
|
sketch_name, constraint_level
|
||||||
|
),
|
||||||
|
source_ranges: source_ranges.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's start by getting the path info.
|
||||||
|
|
||||||
|
// Let's get the path info.
|
||||||
|
let resp = engine
|
||||||
|
.send_modeling_cmd_get_response(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::PathGetInfo { path_id: sketch_id },
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||||
|
modeling_response: kittycad::types::OkModelingCmdResponse::PathGetInfo { data: path_info },
|
||||||
|
} = &resp
|
||||||
|
else {
|
||||||
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Get path info response was not as expected: {:?}", resp),
|
||||||
|
source_ranges: vec![SourceRange::default()],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/* // Let's try to get the children of the sketch.
|
||||||
|
let resp = engine
|
||||||
|
.send_modeling_cmd_get_response(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::EntityGetAllChildUuids { entity_id: sketch_id },
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||||
|
modeling_response: kittycad::types::OkModelingCmdResponse::EntityGetAllChildUuids { data: children_info },
|
||||||
|
} = &resp
|
||||||
|
else {
|
||||||
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Get child info response was not as expected: {:?}", resp),
|
||||||
|
source_ranges: vec![SourceRange::default()],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("children_info: {:#?}", children_info);
|
||||||
|
|
||||||
|
// Let's try to get the parent id.
|
||||||
|
let resp = engine
|
||||||
|
.send_modeling_cmd_get_response(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::EntityGetParentId { entity_id: sketch_id },
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||||
|
modeling_response: kittycad::types::OkModelingCmdResponse::EntityGetParentId { data: parent_info },
|
||||||
|
} = &resp
|
||||||
|
else {
|
||||||
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Get parent id response was not as expected: {:?}", resp),
|
||||||
|
source_ranges: vec![SourceRange::default()],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("parent_info: {:#?}", parent_info);*/
|
||||||
|
|
||||||
|
// Now let's get the control points for all the segments.
|
||||||
|
// TODO: We should probably await all these at once so we aren't going one by one.
|
||||||
|
// But I guess this is fine for now.
|
||||||
|
// We absolutely have to preserve the order of the control points.
|
||||||
|
let mut control_points = Vec::new();
|
||||||
|
for segment in &path_info.segments {
|
||||||
|
if let Some(command_id) = &segment.command_id {
|
||||||
|
let h = engine.send_modeling_cmd_get_response(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::CurveGetControlPoints { curve_id: *command_id },
|
||||||
|
);
|
||||||
|
|
||||||
|
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||||
|
modeling_response: kittycad::types::OkModelingCmdResponse::CurveGetControlPoints { data },
|
||||||
|
} = h.await?
|
||||||
|
else {
|
||||||
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Curve get control points response was not as expected: {:?}", resp),
|
||||||
|
source_ranges: vec![SourceRange::default()],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
control_points.push(ControlPointData {
|
||||||
|
points: data.control_points.clone(),
|
||||||
|
command: segment.command.clone(),
|
||||||
|
id: *command_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if control_points.is_empty() {
|
||||||
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("No control points found for sketch {}", sketch_name),
|
||||||
|
source_ranges: vec![SourceRange::default()],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_control_points = control_points.first().ok_or_else(|| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("No control points found for sketch {}", sketch_name),
|
||||||
|
source_ranges: vec![SourceRange::default()],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut additional_lines = Vec::new();
|
||||||
|
let mut last_point = first_control_points.points[1].clone();
|
||||||
|
for control_point in control_points[1..].iter() {
|
||||||
|
additional_lines.push([
|
||||||
|
(control_point.points[1].x - last_point.x),
|
||||||
|
(control_point.points[1].y - last_point.y),
|
||||||
|
]);
|
||||||
|
last_point = Point3D {
|
||||||
|
x: control_point.points[1].x,
|
||||||
|
y: control_point.points[1].y,
|
||||||
|
z: control_point.points[1].z,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay now let's recalculate the sketch from the control points.
|
||||||
|
let start_sketch_at_end = Point3D {
|
||||||
|
x: (first_control_points.points[1].x - first_control_points.points[0].x),
|
||||||
|
y: (first_control_points.points[1].y - first_control_points.points[0].y),
|
||||||
|
z: (first_control_points.points[1].z - first_control_points.points[0].z),
|
||||||
|
};
|
||||||
|
let sketch = create_start_sketch_at(
|
||||||
|
sketch_name,
|
||||||
|
[first_control_points.points[0].x, first_control_points.points[0].y],
|
||||||
|
[start_sketch_at_end.x, start_sketch_at_end.y],
|
||||||
|
additional_lines,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Add the sketch back to the program.
|
||||||
|
program.replace_variable(sketch_name, sketch);
|
||||||
|
|
||||||
|
let recasted = program.recast(&FormatOptions::default(), 0);
|
||||||
|
|
||||||
|
// Re-parse the ast so we get the correct source ranges.
|
||||||
|
let tokens = crate::tokeniser::lexer(&recasted);
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
*program = parser.ast()?;
|
||||||
|
|
||||||
|
Ok(recasted)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a pipe expression that starts a sketch at the given point and draws a line to the given point.
|
||||||
|
fn create_start_sketch_at(
|
||||||
|
name: &str,
|
||||||
|
start: [f64; 2],
|
||||||
|
end: [f64; 2],
|
||||||
|
additional_lines: Vec<[f64; 2]>,
|
||||||
|
) -> Result<VariableDeclarator, KclError> {
|
||||||
|
let start_sketch_at = CallExpression::new(
|
||||||
|
"startSketchAt",
|
||||||
|
vec![ArrayExpression::new(vec![
|
||||||
|
Literal::new(round_before_recast(start[0]).into()).into(),
|
||||||
|
Literal::new(round_before_recast(start[1]).into()).into(),
|
||||||
|
])
|
||||||
|
.into()],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Keep track of where we are so we can close the sketch if we need to.
|
||||||
|
let mut current_position = Point2d {
|
||||||
|
x: start[0],
|
||||||
|
y: start[1],
|
||||||
|
};
|
||||||
|
current_position.x += end[0];
|
||||||
|
current_position.y += end[1];
|
||||||
|
|
||||||
|
let initial_line = CallExpression::new(
|
||||||
|
"line",
|
||||||
|
vec![
|
||||||
|
ArrayExpression::new(vec![
|
||||||
|
Literal::new(round_before_recast(end[0]).into()).into(),
|
||||||
|
Literal::new(round_before_recast(end[1]).into()).into(),
|
||||||
|
])
|
||||||
|
.into(),
|
||||||
|
PipeSubstitution::new().into(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut pipe_body = vec![start_sketch_at.into(), initial_line.into()];
|
||||||
|
|
||||||
|
for (index, line) in additional_lines.iter().enumerate() {
|
||||||
|
current_position.x += line[0];
|
||||||
|
current_position.y += line[1];
|
||||||
|
|
||||||
|
// If we are on the last line, check if we have to close the sketch.
|
||||||
|
if index == additional_lines.len() - 1 {
|
||||||
|
let diff_x = (current_position.x - start[0]).abs();
|
||||||
|
let diff_y = (current_position.y - start[1]).abs();
|
||||||
|
// Compare the end of the last line to the start of the first line.
|
||||||
|
// This is a bit more lenient if you look at the value of epsilon.
|
||||||
|
if diff_x <= EPSILON && diff_y <= EPSILON {
|
||||||
|
// We have to close the sketch.
|
||||||
|
let close = CallExpression::new("close", vec![PipeSubstitution::new().into()])?;
|
||||||
|
pipe_body.push(close.into());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: we should check if we should close the sketch.
|
||||||
|
let line = CallExpression::new(
|
||||||
|
"line",
|
||||||
|
vec![
|
||||||
|
ArrayExpression::new(vec![
|
||||||
|
Literal::new(round_before_recast(line[0]).into()).into(),
|
||||||
|
Literal::new(round_before_recast(line[1]).into()).into(),
|
||||||
|
])
|
||||||
|
.into(),
|
||||||
|
PipeSubstitution::new().into(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
pipe_body.push(line.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(VariableDeclarator::new(name, PipeExpression::new(pipe_body).into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn round_before_recast(num: f64) -> f64 {
|
||||||
|
(num * 100000.0).round() / 100000.0
|
||||||
|
}
|
@ -237,6 +237,47 @@ impl Program {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace a variable declaration with the given name with a new one.
|
||||||
|
pub fn replace_variable(&mut self, name: &str, declarator: VariableDeclarator) {
|
||||||
|
for item in &mut self.body {
|
||||||
|
match item {
|
||||||
|
BodyItem::ExpressionStatement(_expression_statement) => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BodyItem::VariableDeclaration(ref mut variable_declaration) => {
|
||||||
|
for declaration in &mut variable_declaration.declarations {
|
||||||
|
if declaration.id.name == name {
|
||||||
|
*declaration = declarator;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BodyItem::ReturnStatement(_return_statement) => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the variable declaration with the given name.
|
||||||
|
pub fn get_variable(&self, name: &str) -> Option<&VariableDeclarator> {
|
||||||
|
for item in &self.body {
|
||||||
|
match item {
|
||||||
|
BodyItem::ExpressionStatement(_expression_statement) => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BodyItem::VariableDeclaration(variable_declaration) => {
|
||||||
|
for declaration in &variable_declaration.declarations {
|
||||||
|
if declaration.id.name == name {
|
||||||
|
return Some(declaration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BodyItem::ReturnStatement(_return_statement) => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ValueMeta {
|
pub trait ValueMeta {
|
||||||
@ -247,7 +288,7 @@ pub trait ValueMeta {
|
|||||||
|
|
||||||
macro_rules! impl_value_meta {
|
macro_rules! impl_value_meta {
|
||||||
{$name:ident} => {
|
{$name:ident} => {
|
||||||
impl crate::abstract_syntax_tree_types::ValueMeta for $name {
|
impl crate::ast::types::ValueMeta for $name {
|
||||||
fn start(&self) -> usize {
|
fn start(&self) -> usize {
|
||||||
self.start
|
self.start
|
||||||
}
|
}
|
||||||
@ -426,6 +467,26 @@ impl Value {
|
|||||||
Value::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
|
Value::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the constraint level for a value type.
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
match self {
|
||||||
|
Value::Literal(literal) => literal.get_constraint_level(),
|
||||||
|
Value::Identifier(identifier) => identifier.get_constraint_level(),
|
||||||
|
Value::BinaryExpression(binary_expression) => binary_expression.get_constraint_level(),
|
||||||
|
|
||||||
|
Value::FunctionExpression(function_identifier) => function_identifier.get_constraint_level(),
|
||||||
|
Value::CallExpression(call_expression) => call_expression.get_constraint_level(),
|
||||||
|
Value::PipeExpression(pipe_expression) => pipe_expression.get_constraint_level(),
|
||||||
|
Value::PipeSubstitution(pipe_substitution) => ConstraintLevel::Ignore {
|
||||||
|
source_ranges: vec![pipe_substitution.into()],
|
||||||
|
},
|
||||||
|
Value::ArrayExpression(array_expression) => array_expression.get_constraint_level(),
|
||||||
|
Value::ObjectExpression(object_expression) => object_expression.get_constraint_level(),
|
||||||
|
Value::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
||||||
|
Value::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Value> for crate::executor::SourceRange {
|
impl From<Value> for crate::executor::SourceRange {
|
||||||
@ -465,6 +526,18 @@ impl From<&BinaryPart> for crate::executor::SourceRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BinaryPart {
|
impl BinaryPart {
|
||||||
|
/// Get the constraint level.
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
match self {
|
||||||
|
BinaryPart::Literal(literal) => literal.get_constraint_level(),
|
||||||
|
BinaryPart::Identifier(identifier) => identifier.get_constraint_level(),
|
||||||
|
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_constraint_level(),
|
||||||
|
BinaryPart::CallExpression(call_expression) => call_expression.get_constraint_level(),
|
||||||
|
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
||||||
|
BinaryPart::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||||
match &self {
|
match &self {
|
||||||
BinaryPart::Literal(literal) => literal.recast(),
|
BinaryPart::Literal(literal) => literal.recast(),
|
||||||
@ -639,7 +712,7 @@ pub enum NoneCodeValue {
|
|||||||
NewLine,
|
NewLine,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NoneCodeMeta {
|
pub struct NoneCodeMeta {
|
||||||
@ -698,7 +771,33 @@ pub struct CallExpression {
|
|||||||
|
|
||||||
impl_value_meta!(CallExpression);
|
impl_value_meta!(CallExpression);
|
||||||
|
|
||||||
|
impl From<CallExpression> for Value {
|
||||||
|
fn from(call_expression: CallExpression) -> Self {
|
||||||
|
Value::CallExpression(Box::new(call_expression))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CallExpression {
|
impl CallExpression {
|
||||||
|
pub fn new(name: &str, arguments: Vec<Value>) -> Result<Self, KclError> {
|
||||||
|
// Create our stdlib.
|
||||||
|
let stdlib = crate::std::StdLib::new();
|
||||||
|
let func = stdlib.get(name).ok_or_else(|| {
|
||||||
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: format!("Function {} is not defined", name),
|
||||||
|
source_ranges: vec![],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
callee: Identifier::new(name),
|
||||||
|
arguments,
|
||||||
|
optional: false,
|
||||||
|
function: Function::StdLib { func },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
|
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{}({})",
|
"{}({})",
|
||||||
@ -839,6 +938,23 @@ impl CallExpression {
|
|||||||
arg.rename_identifiers(old_name, new_name);
|
arg.rename_identifiers(old_name, new_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the constraint level for this call expression.
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
if self.arguments.is_empty() {
|
||||||
|
return ConstraintLevel::Ignore {
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over the arguments and get the constraint level for each one.
|
||||||
|
let mut constraint_levels = ConstraintLevels::new();
|
||||||
|
for arg in &self.arguments {
|
||||||
|
constraint_levels.push(arg.get_constraint_level());
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint_levels.get_constraint_level(self.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A function declaration.
|
/// A function declaration.
|
||||||
@ -879,6 +995,15 @@ pub struct VariableDeclaration {
|
|||||||
impl_value_meta!(VariableDeclaration);
|
impl_value_meta!(VariableDeclaration);
|
||||||
|
|
||||||
impl VariableDeclaration {
|
impl VariableDeclaration {
|
||||||
|
pub fn new(declarations: Vec<VariableDeclarator>, kind: VariableKind) -> Self {
|
||||||
|
Self {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
declarations,
|
||||||
|
kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a value that includes the given character position.
|
/// Returns a value that includes the given character position.
|
||||||
pub fn get_value_for_position(&self, pos: usize) -> Option<&Value> {
|
pub fn get_value_for_position(&self, pos: usize) -> Option<&Value> {
|
||||||
for declaration in &self.declarations {
|
for declaration in &self.declarations {
|
||||||
@ -1059,6 +1184,21 @@ pub struct VariableDeclarator {
|
|||||||
|
|
||||||
impl_value_meta!(VariableDeclarator);
|
impl_value_meta!(VariableDeclarator);
|
||||||
|
|
||||||
|
impl VariableDeclarator {
|
||||||
|
pub fn new(name: &str, init: Value) -> Self {
|
||||||
|
Self {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
id: Identifier::new(name),
|
||||||
|
init,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
self.init.get_constraint_level()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
@ -1071,7 +1211,30 @@ pub struct Literal {
|
|||||||
|
|
||||||
impl_value_meta!(Literal);
|
impl_value_meta!(Literal);
|
||||||
|
|
||||||
|
impl From<Literal> for Value {
|
||||||
|
fn from(literal: Literal) -> Self {
|
||||||
|
Value::Literal(Box::new(literal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Literal {
|
impl Literal {
|
||||||
|
pub fn new(value: serde_json::Value) -> Self {
|
||||||
|
Self {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
raw: value.to_string(),
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the constraint level for this literal.
|
||||||
|
/// Literals are always not constrained.
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
ConstraintLevel::None {
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn recast(&self) -> String {
|
fn recast(&self) -> String {
|
||||||
if let serde_json::Value::String(value) = &self.value {
|
if let serde_json::Value::String(value) = &self.value {
|
||||||
let quote = if self.raw.trim().starts_with('"') { '"' } else { '\'' };
|
let quote = if self.raw.trim().starts_with('"') { '"' } else { '\'' };
|
||||||
@ -1116,6 +1279,22 @@ pub struct Identifier {
|
|||||||
impl_value_meta!(Identifier);
|
impl_value_meta!(Identifier);
|
||||||
|
|
||||||
impl Identifier {
|
impl Identifier {
|
||||||
|
pub fn new(name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
name: name.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the constraint level for this identifier.
|
||||||
|
/// Identifier are always fully constrained.
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
ConstraintLevel::Full {
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
fn rename(&mut self, old_name: &str, new_name: &str) {
|
fn rename(&mut self, old_name: &str, new_name: &str) {
|
||||||
if self.name == old_name {
|
if self.name == old_name {
|
||||||
@ -1134,6 +1313,24 @@ pub struct PipeSubstitution {
|
|||||||
|
|
||||||
impl_value_meta!(PipeSubstitution);
|
impl_value_meta!(PipeSubstitution);
|
||||||
|
|
||||||
|
impl PipeSubstitution {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { start: 0, end: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PipeSubstitution {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PipeSubstitution> for Value {
|
||||||
|
fn from(pipe_substitution: PipeSubstitution) -> Self {
|
||||||
|
Value::PipeSubstitution(Box::new(pipe_substitution))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
@ -1145,7 +1342,36 @@ pub struct ArrayExpression {
|
|||||||
|
|
||||||
impl_value_meta!(ArrayExpression);
|
impl_value_meta!(ArrayExpression);
|
||||||
|
|
||||||
|
impl From<ArrayExpression> for Value {
|
||||||
|
fn from(array_expression: ArrayExpression) -> Self {
|
||||||
|
Value::ArrayExpression(Box::new(array_expression))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ArrayExpression {
|
impl ArrayExpression {
|
||||||
|
pub fn new(elements: Vec<Value>) -> Self {
|
||||||
|
Self {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
elements,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
if self.elements.is_empty() {
|
||||||
|
return ConstraintLevel::Ignore {
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut constraint_levels = ConstraintLevels::new();
|
||||||
|
for element in &self.elements {
|
||||||
|
constraint_levels.push(element.get_constraint_level());
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint_levels.get_constraint_level(self.into())
|
||||||
|
}
|
||||||
|
|
||||||
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
|
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
|
||||||
let flat_recast = format!(
|
let flat_recast = format!(
|
||||||
"[{}]",
|
"[{}]",
|
||||||
@ -1268,6 +1494,29 @@ pub struct ObjectExpression {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectExpression {
|
impl ObjectExpression {
|
||||||
|
pub fn new(properties: Vec<ObjectProperty>) -> Self {
|
||||||
|
Self {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
properties,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
if self.properties.is_empty() {
|
||||||
|
return ConstraintLevel::Ignore {
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut constraint_levels = ConstraintLevels::new();
|
||||||
|
for property in &self.properties {
|
||||||
|
constraint_levels.push(property.value.get_constraint_level());
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint_levels.get_constraint_level(self.into())
|
||||||
|
}
|
||||||
|
|
||||||
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
|
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
|
||||||
let flat_recast = format!(
|
let flat_recast = format!(
|
||||||
"{{ {} }}",
|
"{{ {} }}",
|
||||||
@ -1523,6 +1772,14 @@ pub struct MemberExpression {
|
|||||||
impl_value_meta!(MemberExpression);
|
impl_value_meta!(MemberExpression);
|
||||||
|
|
||||||
impl MemberExpression {
|
impl MemberExpression {
|
||||||
|
/// Get the constraint level for a member expression.
|
||||||
|
/// This is always fully constrained.
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
ConstraintLevel::Full {
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn recast(&self) -> String {
|
fn recast(&self) -> String {
|
||||||
let key_str = match &self.property {
|
let key_str = match &self.property {
|
||||||
LiteralIdentifier::Identifier(identifier) => {
|
LiteralIdentifier::Identifier(identifier) => {
|
||||||
@ -1672,6 +1929,26 @@ pub struct BinaryExpression {
|
|||||||
impl_value_meta!(BinaryExpression);
|
impl_value_meta!(BinaryExpression);
|
||||||
|
|
||||||
impl BinaryExpression {
|
impl BinaryExpression {
|
||||||
|
pub fn new(operator: BinaryOperator, left: BinaryPart, right: BinaryPart) -> Self {
|
||||||
|
Self {
|
||||||
|
start: left.start(),
|
||||||
|
end: right.end(),
|
||||||
|
operator,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
let left_constraint_level = self.left.get_constraint_level();
|
||||||
|
let right_constraint_level = self.right.get_constraint_level();
|
||||||
|
|
||||||
|
let mut constraint_levels = ConstraintLevels::new();
|
||||||
|
constraint_levels.push(left_constraint_level);
|
||||||
|
constraint_levels.push(right_constraint_level);
|
||||||
|
constraint_levels.get_constraint_level(self.into())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn precedence(&self) -> u8 {
|
pub fn precedence(&self) -> u8 {
|
||||||
self.operator.precedence()
|
self.operator.precedence()
|
||||||
}
|
}
|
||||||
@ -1870,6 +2147,19 @@ pub struct UnaryExpression {
|
|||||||
impl_value_meta!(UnaryExpression);
|
impl_value_meta!(UnaryExpression);
|
||||||
|
|
||||||
impl UnaryExpression {
|
impl UnaryExpression {
|
||||||
|
pub fn new(operator: UnaryOperator, argument: BinaryPart) -> Self {
|
||||||
|
Self {
|
||||||
|
start: 0,
|
||||||
|
end: argument.end(),
|
||||||
|
operator,
|
||||||
|
argument,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
self.argument.get_constraint_level()
|
||||||
|
}
|
||||||
|
|
||||||
fn recast(&self, options: &FormatOptions) -> String {
|
fn recast(&self, options: &FormatOptions) -> String {
|
||||||
format!("{}{}", &self.operator, self.argument.recast(options, 0))
|
format!("{}{}", &self.operator, self.argument.recast(options, 0))
|
||||||
}
|
}
|
||||||
@ -1944,7 +2234,38 @@ pub struct PipeExpression {
|
|||||||
|
|
||||||
impl_value_meta!(PipeExpression);
|
impl_value_meta!(PipeExpression);
|
||||||
|
|
||||||
|
impl From<PipeExpression> for Value {
|
||||||
|
fn from(pipe_expression: PipeExpression) -> Self {
|
||||||
|
Value::PipeExpression(Box::new(pipe_expression))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PipeExpression {
|
impl PipeExpression {
|
||||||
|
pub fn new(body: Vec<Value>) -> Self {
|
||||||
|
Self {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
body,
|
||||||
|
non_code_meta: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
if self.body.is_empty() {
|
||||||
|
return ConstraintLevel::Ignore {
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over all body expressions.
|
||||||
|
let mut constraint_levels = ConstraintLevels::new();
|
||||||
|
for expression in &self.body {
|
||||||
|
constraint_levels.push(expression.get_constraint_level());
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint_levels.get_constraint_level(self.into())
|
||||||
|
}
|
||||||
|
|
||||||
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||||
self.body
|
self.body
|
||||||
.iter()
|
.iter()
|
||||||
@ -2063,6 +2384,13 @@ pub struct FunctionExpression {
|
|||||||
impl_value_meta!(FunctionExpression);
|
impl_value_meta!(FunctionExpression);
|
||||||
|
|
||||||
impl FunctionExpression {
|
impl FunctionExpression {
|
||||||
|
/// Function expressions don't really apply.
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
ConstraintLevel::Ignore {
|
||||||
|
source_ranges: vec![self.into()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||||
// We don't want to end with a new line inside nested functions.
|
// We don't want to end with a new line inside nested functions.
|
||||||
let mut new_options = options.clone();
|
let mut new_options = options.clone();
|
||||||
@ -2167,11 +2495,150 @@ impl FormatOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The constraint level.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS, JsonSchema, Display)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[display(style = "snake_case")]
|
||||||
|
pub enum ConstraintLevel {
|
||||||
|
/// Ignore constraints.
|
||||||
|
/// This is useful for stuff like pipe substitutions where we don't want it to
|
||||||
|
/// factor into the overall constraint level.
|
||||||
|
/// Like empty arrays or objects, etc.
|
||||||
|
#[display("ignore")]
|
||||||
|
Ignore { source_ranges: Vec<SourceRange> },
|
||||||
|
/// No constraints.
|
||||||
|
#[display("none")]
|
||||||
|
None { source_ranges: Vec<SourceRange> },
|
||||||
|
/// Partially constrained.
|
||||||
|
#[display("partial")]
|
||||||
|
Partial {
|
||||||
|
source_ranges: Vec<SourceRange>,
|
||||||
|
levels: ConstraintLevels,
|
||||||
|
},
|
||||||
|
/// Fully constrained.
|
||||||
|
#[display("full")]
|
||||||
|
Full { source_ranges: Vec<SourceRange> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ConstraintLevel> for Vec<SourceRange> {
|
||||||
|
fn from(constraint_level: ConstraintLevel) -> Self {
|
||||||
|
match constraint_level {
|
||||||
|
ConstraintLevel::Ignore { source_ranges } => source_ranges,
|
||||||
|
ConstraintLevel::None { source_ranges } => source_ranges,
|
||||||
|
ConstraintLevel::Partial {
|
||||||
|
source_ranges,
|
||||||
|
levels: _,
|
||||||
|
} => source_ranges,
|
||||||
|
ConstraintLevel::Full { source_ranges } => source_ranges,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ConstraintLevel {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
// Just check the variant.
|
||||||
|
std::mem::discriminant(self) == std::mem::discriminant(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConstraintLevel {
|
||||||
|
pub fn update_source_ranges(&self, source_range: SourceRange) -> Self {
|
||||||
|
match self {
|
||||||
|
ConstraintLevel::Ignore { source_ranges: _ } => ConstraintLevel::Ignore {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
},
|
||||||
|
ConstraintLevel::None { source_ranges: _ } => ConstraintLevel::None {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
},
|
||||||
|
ConstraintLevel::Partial {
|
||||||
|
source_ranges: _,
|
||||||
|
levels,
|
||||||
|
} => ConstraintLevel::Partial {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
levels: levels.clone(),
|
||||||
|
},
|
||||||
|
ConstraintLevel::Full { source_ranges: _ } => ConstraintLevel::Full {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A vector of constraint levels.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct ConstraintLevels(pub Vec<ConstraintLevel>);
|
||||||
|
|
||||||
|
impl Default for ConstraintLevels {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConstraintLevels {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, constraint_level: ConstraintLevel) {
|
||||||
|
self.0.push(constraint_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the overall constraint level.
|
||||||
|
pub fn get_constraint_level(&self, source_range: SourceRange) -> ConstraintLevel {
|
||||||
|
if self.0.is_empty() {
|
||||||
|
return ConstraintLevel::Ignore {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all the constraint levels are the same.
|
||||||
|
if self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.all(|level| *level == self.0[0] || matches!(level, ConstraintLevel::Ignore { .. }))
|
||||||
|
{
|
||||||
|
self.0[0].clone()
|
||||||
|
} else {
|
||||||
|
ConstraintLevel::Partial {
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
levels: self.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_all_partial_or_full_source_ranges(&self) -> Vec<SourceRange> {
|
||||||
|
let mut source_ranges = Vec::new();
|
||||||
|
// Add to our source ranges anything that is not none or ignore.
|
||||||
|
for level in &self.0 {
|
||||||
|
match level {
|
||||||
|
ConstraintLevel::None { source_ranges: _ } => {}
|
||||||
|
ConstraintLevel::Ignore { source_ranges: _ } => {}
|
||||||
|
ConstraintLevel::Partial {
|
||||||
|
source_ranges: _,
|
||||||
|
levels,
|
||||||
|
} => {
|
||||||
|
source_ranges.extend(levels.get_all_partial_or_full_source_ranges());
|
||||||
|
}
|
||||||
|
ConstraintLevel::Full {
|
||||||
|
source_ranges: full_source_ranges,
|
||||||
|
} => {
|
||||||
|
source_ranges.extend(full_source_ranges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source_ranges
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
// We have this as a test so we can ensure it never panics with an unwrap in the server.
|
// We have this as a test so we can ensure it never panics with an unwrap in the server.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_variable_kind_to_completion() {
|
fn test_variable_kind_to_completion() {
|
||||||
@ -2205,8 +2672,7 @@ show(part001)"#;
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_recast_with_std_and_non_stdlib() {
|
fn test_recast_with_std_and_non_stdlib() {
|
||||||
let some_program_string = r#"{"body":[{"type":"VariableDeclaration","start":0,"end":0,"declarations":[{"type":"VariableDeclarator","start":0,"end":0,"id":{"type":"Identifier","start":0,"end":0,"name":"part001"},"init":{"type":"PipeExpression","start":0,"end":0,"body":[{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"startSketchAt"},"function":{"type":"StdLib","func":{"name":"startSketchAt","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"ry"},"function":{"type":"InMemory"},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":90,"raw":"90"},{"type":"PipeSubstitution","start":0,"end":0}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"line"},"function":{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"},{"type":"PipeSubstitution","start":0,"end":0}]}],"nonCodeMeta":{"noneCodeNodes":{},"start":null}}}],"kind":"const"},{"type":"ExpressionStatement","start":0,"end":0,"expression":{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"show"},"function":{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Identifier","start":0,"end":0,"name":"part001"}]}}],"start":0,"end":0,"nonCodeMeta":{"noneCodeNodes":{},"start":null}}"#;
|
let some_program_string = r#"{"body":[{"type":"VariableDeclaration","start":0,"end":0,"declarations":[{"type":"VariableDeclarator","start":0,"end":0,"id":{"type":"Identifier","start":0,"end":0,"name":"part001"},"init":{"type":"PipeExpression","start":0,"end":0,"body":[{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"startSketchAt"},"function":{"type":"StdLib","func":{"name":"startSketchAt","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"ry"},"function":{"type":"InMemory"},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":90,"raw":"90"},{"type":"PipeSubstitution","start":0,"end":0}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"line"},"function":{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"},{"type":"PipeSubstitution","start":0,"end":0}]}],"nonCodeMeta":{"noneCodeNodes":{},"start":null}}}],"kind":"const"},{"type":"ExpressionStatement","start":0,"end":0,"expression":{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"show"},"function":{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Identifier","start":0,"end":0,"name":"part001"}]}}],"start":0,"end":0,"nonCodeMeta":{"noneCodeNodes":{},"start":null}}"#;
|
||||||
let some_program: crate::abstract_syntax_tree_types::Program =
|
let some_program: crate::ast::types::Program = serde_json::from_str(some_program_string).unwrap();
|
||||||
serde_json::from_str(some_program_string).unwrap();
|
|
||||||
|
|
||||||
let recasted = some_program.recast(&Default::default(), 0);
|
let recasted = some_program.recast(&Default::default(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
@ -486,7 +486,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialize_function() {
|
fn test_serialize_function() {
|
||||||
let some_function = crate::abstract_syntax_tree_types::Function::StdLib {
|
let some_function = crate::ast::types::Function::StdLib {
|
||||||
func: Box::new(crate::std::sketch::Line),
|
func: Box::new(crate::std::sketch::Line),
|
||||||
};
|
};
|
||||||
let serialized = serde_json::to_string(&some_function).unwrap();
|
let serialized = serde_json::to_string(&some_function).unwrap();
|
||||||
@ -496,12 +496,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_function() {
|
fn test_deserialize_function() {
|
||||||
let some_function_string = r#"{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}}"#;
|
let some_function_string = r#"{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}}"#;
|
||||||
let some_function: crate::abstract_syntax_tree_types::Function =
|
let some_function: crate::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
|
||||||
serde_json::from_str(some_function_string).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
some_function,
|
some_function,
|
||||||
crate::abstract_syntax_tree_types::Function::StdLib {
|
crate::ast::types::Function::StdLib {
|
||||||
func: Box::new(crate::std::sketch::Line),
|
func: Box::new(crate::std::sketch::Line),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -510,12 +509,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_function_show() {
|
fn test_deserialize_function_show() {
|
||||||
let some_function_string = r#"{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}}"#;
|
let some_function_string = r#"{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}}"#;
|
||||||
let some_function: crate::abstract_syntax_tree_types::Function =
|
let some_function: crate::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
|
||||||
serde_json::from_str(some_function_string).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
some_function,
|
some_function,
|
||||||
crate::abstract_syntax_tree_types::Function::StdLib {
|
crate::ast::types::Function::StdLib {
|
||||||
func: Box::new(crate::std::Show),
|
func: Box::new(crate::std::Show),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -9,7 +9,10 @@ use futures::{SinkExt, StreamExt};
|
|||||||
use kittycad::types::{OkWebSocketResponseData, WebSocketRequest, WebSocketResponse};
|
use kittycad::types::{OkWebSocketResponseData, WebSocketRequest, WebSocketResponse};
|
||||||
use tokio_tungstenite::tungstenite::Message as WsMsg;
|
use tokio_tungstenite::tungstenite::Message as WsMsg;
|
||||||
|
|
||||||
use crate::errors::{KclError, KclErrorDetails};
|
use crate::{
|
||||||
|
engine::EngineManager,
|
||||||
|
errors::{KclError, KclErrorDetails},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EngineConnection {
|
pub struct EngineConnection {
|
||||||
@ -70,7 +73,7 @@ impl EngineConnection {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("got ws error: {:?}", e);
|
println!("got ws error: {:?}", e);
|
||||||
continue;
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,10 +92,13 @@ impl EngineConnection {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl EngineManager for EngineConnection {
|
||||||
/// Send a modeling command.
|
/// Send a modeling command.
|
||||||
/// Do not wait for the response message.
|
/// Do not wait for the response message.
|
||||||
pub fn send_modeling_cmd(
|
fn send_modeling_cmd(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: uuid::Uuid,
|
id: uuid::Uuid,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
@ -110,13 +116,20 @@ impl EngineConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send a modeling command and wait for the response message.
|
/// Send a modeling command and wait for the response message.
|
||||||
pub fn send_modeling_cmd_get_response(
|
async fn send_modeling_cmd_get_response(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: uuid::Uuid,
|
id: uuid::Uuid,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
cmd: kittycad::types::ModelingCmd,
|
cmd: kittycad::types::ModelingCmd,
|
||||||
) -> Result<OkWebSocketResponseData, KclError> {
|
) -> Result<OkWebSocketResponseData, KclError> {
|
||||||
self.send_modeling_cmd(id, source_range, cmd)?;
|
self.tcp_send(WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id })
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Failed to send modeling command: {}", e),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
// Wait for the response.
|
// Wait for the response.
|
||||||
loop {
|
loop {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
//! engine.
|
//! engine.
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use kittycad::types::OkWebSocketResponseData;
|
||||||
|
|
||||||
use crate::errors::KclError;
|
use crate::errors::KclError;
|
||||||
|
|
||||||
@ -12,8 +13,11 @@ impl EngineConnection {
|
|||||||
pub async fn new() -> Result<EngineConnection> {
|
pub async fn new() -> Result<EngineConnection> {
|
||||||
Ok(EngineConnection {})
|
Ok(EngineConnection {})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn send_modeling_cmd(
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl crate::engine::EngineManager for EngineConnection {
|
||||||
|
fn send_modeling_cmd(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: uuid::Uuid,
|
_id: uuid::Uuid,
|
||||||
_source_range: crate::executor::SourceRange,
|
_source_range: crate::executor::SourceRange,
|
||||||
@ -21,4 +25,13 @@ impl EngineConnection {
|
|||||||
) -> Result<(), KclError> {
|
) -> Result<(), KclError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn send_modeling_cmd_get_response(
|
||||||
|
&mut self,
|
||||||
|
_id: uuid::Uuid,
|
||||||
|
_source_range: crate::executor::SourceRange,
|
||||||
|
_cmd: kittycad::types::ModelingCmd,
|
||||||
|
) -> Result<OkWebSocketResponseData, KclError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,11 @@ impl EngineConnection {
|
|||||||
pub async fn new(manager: EngineCommandManager) -> Result<EngineConnection, JsValue> {
|
pub async fn new(manager: EngineCommandManager) -> Result<EngineConnection, JsValue> {
|
||||||
Ok(EngineConnection { manager })
|
Ok(EngineConnection { manager })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn send_modeling_cmd(
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl crate::engine::EngineManager for EngineConnection {
|
||||||
|
fn send_modeling_cmd(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: uuid::Uuid,
|
id: uuid::Uuid,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
@ -55,4 +58,53 @@ impl EngineConnection {
|
|||||||
.sendModelingCommandFromWasm(id.to_string(), source_range_str, cmd_str);
|
.sendModelingCommandFromWasm(id.to_string(), source_range_str, cmd_str);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn send_modeling_cmd_get_response(
|
||||||
|
&mut self,
|
||||||
|
id: uuid::Uuid,
|
||||||
|
source_range: crate::executor::SourceRange,
|
||||||
|
cmd: kittycad::types::ModelingCmd,
|
||||||
|
) -> Result<kittycad::types::OkWebSocketResponseData, KclError> {
|
||||||
|
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Failed to serialize source range: {:?}", e),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
let ws_msg = WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id };
|
||||||
|
let cmd_str = serde_json::to_string(&ws_msg).map_err(|e| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Failed to serialize modeling command: {:?}", e),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let promise = self
|
||||||
|
.manager
|
||||||
|
.sendModelingCommandFromWasm(id.to_string(), source_range_str, cmd_str);
|
||||||
|
|
||||||
|
let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Failed to wait for promise from engine: {:?}", e),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Parse the value as a string.
|
||||||
|
let s = value.as_string().ok_or_else(|| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Failed to get string from response from engine: `{:?}`", value),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let modeling_result: kittycad::types::OkWebSocketResponseData = serde_json::from_str(&s).map_err(|e| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Failed to deserialize response from engine: {:?}", e),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(modeling_result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
//! Functions for managing engine communications.
|
//! Functions for managing engine communications.
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
#[cfg(not(test))]
|
|
||||||
#[cfg(feature = "engine")]
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
#[cfg(feature = "engine")]
|
#[cfg(feature = "engine")]
|
||||||
@ -31,37 +26,27 @@ pub use conn_mock::EngineConnection;
|
|||||||
#[cfg(not(feature = "engine"))]
|
#[cfg(not(feature = "engine"))]
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
pub mod conn_mock;
|
pub mod conn_mock;
|
||||||
|
use anyhow::Result;
|
||||||
#[cfg(not(feature = "engine"))]
|
#[cfg(not(feature = "engine"))]
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
pub use conn_mock::EngineConnection;
|
pub use conn_mock::EngineConnection;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[async_trait::async_trait(?Send)]
|
||||||
#[cfg(not(test))]
|
pub trait EngineManager {
|
||||||
#[derive(Debug)]
|
/// Send a modeling command.
|
||||||
#[wasm_bindgen]
|
/// Do not wait for the response message.
|
||||||
pub struct EngineManager {
|
fn send_modeling_cmd(
|
||||||
connection: EngineConnection,
|
&mut self,
|
||||||
}
|
id: uuid::Uuid,
|
||||||
#[cfg(target_arch = "wasm32")]
|
source_range: crate::executor::SourceRange,
|
||||||
#[cfg(not(test))]
|
cmd: kittycad::types::ModelingCmd,
|
||||||
#[cfg(feature = "engine")]
|
) -> Result<(), crate::errors::KclError>;
|
||||||
#[wasm_bindgen]
|
|
||||||
impl EngineManager {
|
|
||||||
#[wasm_bindgen(constructor)]
|
|
||||||
pub async fn new(manager: conn_wasm::EngineCommandManager) -> EngineManager {
|
|
||||||
EngineManager {
|
|
||||||
// This unwrap is safe because the connection is always created.
|
|
||||||
connection: EngineConnection::new(manager).await.unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_modeling_cmd(&mut self, id_str: &str, cmd_str: &str) -> Result<(), String> {
|
/// Send a modeling command and wait for the response message.
|
||||||
let id = uuid::Uuid::parse_str(id_str).map_err(|e| e.to_string())?;
|
async fn send_modeling_cmd_get_response(
|
||||||
let cmd = serde_json::from_str(cmd_str).map_err(|e| e.to_string())?;
|
&mut self,
|
||||||
self.connection
|
id: uuid::Uuid,
|
||||||
.send_modeling_cmd(id, crate::executor::SourceRange::default(), cmd)
|
source_range: crate::executor::SourceRange,
|
||||||
.map_err(String::from)?;
|
cmd: kittycad::types::ModelingCmd,
|
||||||
|
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError>;
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
abstract_syntax_tree_types::{BodyItem, Function, FunctionExpression, Value},
|
ast::types::{BodyItem, Function, FunctionExpression, Value},
|
||||||
engine::EngineConnection,
|
engine::EngineConnection,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
};
|
};
|
||||||
@ -578,7 +578,7 @@ impl Default for PipeInfo {
|
|||||||
|
|
||||||
/// Execute a AST's program.
|
/// Execute a AST's program.
|
||||||
pub fn execute(
|
pub fn execute(
|
||||||
program: crate::abstract_syntax_tree_types::Program,
|
program: crate::ast::types::Program,
|
||||||
memory: &mut ProgramMemory,
|
memory: &mut ProgramMemory,
|
||||||
options: BodyType,
|
options: BodyType,
|
||||||
engine: &mut EngineConnection,
|
engine: &mut EngineConnection,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
pub mod abstract_syntax_tree_types;
|
pub mod ast;
|
||||||
pub mod docs;
|
pub mod docs;
|
||||||
pub mod engine;
|
pub mod engine;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
abstract_syntax_tree_types::{
|
ast::types::{
|
||||||
BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, MemberExpression,
|
BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, MemberExpression,
|
||||||
UnaryExpression, ValueMeta,
|
UnaryExpression, ValueMeta,
|
||||||
},
|
},
|
||||||
@ -41,7 +41,7 @@ pub struct ParenthesisToken {
|
|||||||
pub end: usize,
|
pub end: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::abstract_syntax_tree_types::impl_value_meta!(ParenthesisToken);
|
crate::ast::types::impl_value_meta!(ParenthesisToken);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -56,7 +56,7 @@ pub struct ExtendedBinaryExpression {
|
|||||||
pub end_extended: Option<usize>,
|
pub end_extended: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::abstract_syntax_tree_types::impl_value_meta!(ExtendedBinaryExpression);
|
crate::ast::types::impl_value_meta!(ExtendedBinaryExpression);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -70,7 +70,7 @@ pub struct ExtendedLiteral {
|
|||||||
pub end_extended: Option<usize>,
|
pub end_extended: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::abstract_syntax_tree_types::impl_value_meta!(ExtendedLiteral);
|
crate::ast::types::impl_value_meta!(ExtendedLiteral);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -444,7 +444,7 @@ impl ReversePolishNotation {
|
|||||||
let expression = UnaryExpression {
|
let expression = UnaryExpression {
|
||||||
start: current_token.start,
|
start: current_token.start,
|
||||||
end: current_token.end,
|
end: current_token.end,
|
||||||
operator: crate::abstract_syntax_tree_types::UnaryOperator::Neg,
|
operator: crate::ast::types::UnaryOperator::Neg,
|
||||||
argument: BinaryPart::Identifier(Box::new(Identifier {
|
argument: BinaryPart::Identifier(Box::new(Identifier {
|
||||||
name: current_token.value.trim_start_matches('-').to_string(),
|
name: current_token.value.trim_start_matches('-').to_string(),
|
||||||
start: current_token.start + 1,
|
start: current_token.start + 1,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::{collections::HashMap, str::FromStr};
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
abstract_syntax_tree_types::{
|
ast::types::{
|
||||||
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ExpressionStatement,
|
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ExpressionStatement,
|
||||||
FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject, NoneCodeMeta,
|
FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject, NoneCodeMeta,
|
||||||
NoneCodeNode, NoneCodeValue, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression, PipeSubstitution,
|
NoneCodeNode, NoneCodeValue, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression, PipeSubstitution,
|
||||||
@ -1251,9 +1251,9 @@ impl Parser {
|
|||||||
let closing_brace_token = self.get_token(closing_brace_index)?;
|
let closing_brace_token = self.get_token(closing_brace_index)?;
|
||||||
let args = self.make_arguments(brace_token.index, vec![])?;
|
let args = self.make_arguments(brace_token.index, vec![])?;
|
||||||
let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) {
|
let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) {
|
||||||
crate::abstract_syntax_tree_types::Function::StdLib { func: stdlib_fn }
|
crate::ast::types::Function::StdLib { func: stdlib_fn }
|
||||||
} else {
|
} else {
|
||||||
crate::abstract_syntax_tree_types::Function::InMemory
|
crate::ast::types::Function::InMemory
|
||||||
};
|
};
|
||||||
Ok(CallExpressionResult {
|
Ok(CallExpressionResult {
|
||||||
expression: CallExpression {
|
expression: CallExpression {
|
||||||
@ -1790,7 +1790,7 @@ mod tests {
|
|||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::abstract_syntax_tree_types::BinaryOperator;
|
use crate::ast::types::BinaryOperator;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_make_identifier() {
|
fn test_make_identifier() {
|
||||||
|
@ -7,7 +7,7 @@ use clap::Parser;
|
|||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use tower_lsp::{jsonrpc::Result as RpcResult, lsp_types::*, Client, LanguageServer};
|
use tower_lsp::{jsonrpc::Result as RpcResult, lsp_types::*, Client, LanguageServer};
|
||||||
|
|
||||||
use crate::{abstract_syntax_tree_types::VariableKind, executor::SourceRange, parser::PIPE_OPERATOR};
|
use crate::{ast::types::VariableKind, executor::SourceRange, parser::PIPE_OPERATOR};
|
||||||
|
|
||||||
/// A subcommand for running the server.
|
/// A subcommand for running the server.
|
||||||
#[derive(Parser, Clone, Debug)]
|
#[derive(Parser, Clone, Debug)]
|
||||||
@ -34,7 +34,7 @@ pub struct Backend {
|
|||||||
/// Token maps.
|
/// Token maps.
|
||||||
pub token_map: DashMap<String, Vec<crate::tokeniser::Token>>,
|
pub token_map: DashMap<String, Vec<crate::tokeniser::Token>>,
|
||||||
/// AST maps.
|
/// AST maps.
|
||||||
pub ast_map: DashMap<String, crate::abstract_syntax_tree_types::Program>,
|
pub ast_map: DashMap<String, crate::ast::types::Program>,
|
||||||
/// Current code.
|
/// Current code.
|
||||||
pub current_code_map: DashMap<String, String>,
|
pub current_code_map: DashMap<String, String>,
|
||||||
/// Diagnostics.
|
/// Diagnostics.
|
||||||
@ -171,19 +171,19 @@ impl Backend {
|
|||||||
|
|
||||||
for item in &ast.body {
|
for item in &ast.body {
|
||||||
match item {
|
match item {
|
||||||
crate::abstract_syntax_tree_types::BodyItem::ExpressionStatement(_) => continue,
|
crate::ast::types::BodyItem::ExpressionStatement(_) => continue,
|
||||||
crate::abstract_syntax_tree_types::BodyItem::ReturnStatement(_) => continue,
|
crate::ast::types::BodyItem::ReturnStatement(_) => continue,
|
||||||
crate::abstract_syntax_tree_types::BodyItem::VariableDeclaration(variable) => {
|
crate::ast::types::BodyItem::VariableDeclaration(variable) => {
|
||||||
// We only want to complete variables.
|
// We only want to complete variables.
|
||||||
for declaration in &variable.declarations {
|
for declaration in &variable.declarations {
|
||||||
completions.push(CompletionItem {
|
completions.push(CompletionItem {
|
||||||
label: declaration.id.name.to_string(),
|
label: declaration.id.name.to_string(),
|
||||||
label_details: None,
|
label_details: None,
|
||||||
kind: Some(match variable.kind {
|
kind: Some(match variable.kind {
|
||||||
crate::abstract_syntax_tree_types::VariableKind::Let => CompletionItemKind::VARIABLE,
|
crate::ast::types::VariableKind::Let => CompletionItemKind::VARIABLE,
|
||||||
crate::abstract_syntax_tree_types::VariableKind::Const => CompletionItemKind::CONSTANT,
|
crate::ast::types::VariableKind::Const => CompletionItemKind::CONSTANT,
|
||||||
crate::abstract_syntax_tree_types::VariableKind::Var => CompletionItemKind::VARIABLE,
|
crate::ast::types::VariableKind::Var => CompletionItemKind::VARIABLE,
|
||||||
crate::abstract_syntax_tree_types::VariableKind::Fn => CompletionItemKind::FUNCTION,
|
crate::ast::types::VariableKind::Fn => CompletionItemKind::FUNCTION,
|
||||||
}),
|
}),
|
||||||
detail: Some(variable.kind.to_string()),
|
detail: Some(variable.kind.to_string()),
|
||||||
documentation: None,
|
documentation: None,
|
||||||
@ -368,7 +368,7 @@ impl LanguageServer for Backend {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match hover {
|
match hover {
|
||||||
crate::abstract_syntax_tree_types::Hover::Function { name, range } => {
|
crate::ast::types::Hover::Function { name, range } => {
|
||||||
// Get the docs for this function.
|
// Get the docs for this function.
|
||||||
let Some(completion) = self.stdlib_completions.get(&name) else {
|
let Some(completion) = self.stdlib_completions.get(&name) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@ -399,7 +399,7 @@ impl LanguageServer for Backend {
|
|||||||
range: Some(range),
|
range: Some(range),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
crate::abstract_syntax_tree_types::Hover::Signature { .. } => Ok(None),
|
crate::ast::types::Hover::Signature { .. } => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,7 +482,7 @@ impl LanguageServer for Backend {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match hover {
|
match hover {
|
||||||
crate::abstract_syntax_tree_types::Hover::Function { name, range: _ } => {
|
crate::ast::types::Hover::Function { name, range: _ } => {
|
||||||
// Get the docs for this function.
|
// Get the docs for this function.
|
||||||
let Some(signature) = self.stdlib_signatures.get(&name) else {
|
let Some(signature) = self.stdlib_signatures.get(&name) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@ -490,7 +490,7 @@ impl LanguageServer for Backend {
|
|||||||
|
|
||||||
Ok(Some(signature.clone()))
|
Ok(Some(signature.clone()))
|
||||||
}
|
}
|
||||||
crate::abstract_syntax_tree_types::Hover::Signature {
|
crate::ast::types::Hover::Signature {
|
||||||
name,
|
name,
|
||||||
parameter_index,
|
parameter_index,
|
||||||
range: _,
|
range: _,
|
||||||
@ -554,7 +554,7 @@ impl LanguageServer for Backend {
|
|||||||
};
|
};
|
||||||
// Now recast it.
|
// Now recast it.
|
||||||
let recast = ast.recast(
|
let recast = ast.recast(
|
||||||
&crate::abstract_syntax_tree_types::FormatOptions {
|
&crate::ast::types::FormatOptions {
|
||||||
tab_size: params.options.tab_size as usize,
|
tab_size: params.options.tab_size as usize,
|
||||||
insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
|
insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
|
||||||
use_tabs: !params.options.insert_spaces,
|
use_tabs: !params.options.insert_spaces,
|
||||||
|
@ -15,8 +15,8 @@ use schemars::JsonSchema;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
abstract_syntax_tree_types::parse_json_number_as_f64,
|
ast::types::parse_json_number_as_f64,
|
||||||
engine::EngineConnection,
|
engine::{EngineConnection, EngineManager},
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{ExtrudeGroup, MemoryItem, Metadata, SketchGroup, SourceRange},
|
executor::{ExtrudeGroup, MemoryItem, Metadata, SketchGroup, SourceRange},
|
||||||
};
|
};
|
||||||
@ -518,9 +518,10 @@ pub enum Primitive {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::std::StdLib;
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::std::StdLib;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generate_stdlib_markdown_docs() {
|
fn test_generate_stdlib_markdown_docs() {
|
||||||
let stdlib = StdLib::new();
|
let stdlib = StdLib::new();
|
||||||
|
@ -6,6 +6,7 @@ use kittycad::types::{ModelingCmd, Point3D};
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::utils::Angle;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{BasePath, GeoMeta, MemoryItem, Path, Point2d, Position, Rotation, SketchGroup},
|
executor::{BasePath, GeoMeta, MemoryItem, Path, Point2d, Position, Rotation, SketchGroup},
|
||||||
@ -15,8 +16,6 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::utils::Angle;
|
|
||||||
|
|
||||||
/// Data to draw a line to a point.
|
/// Data to draw a line to a point.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
|
@ -47,6 +47,7 @@ impl Angle {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use std::f64::consts::PI;
|
/// use std::f64::consts::PI;
|
||||||
|
///
|
||||||
/// use kcl_lib::std::utils::Angle;
|
/// use kcl_lib::std::utils::Angle;
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
|
@ -18,8 +18,7 @@ pub async fn execute_wasm(
|
|||||||
manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
||||||
) -> Result<JsValue, String> {
|
) -> Result<JsValue, String> {
|
||||||
// deserialize the ast from a stringified json
|
// deserialize the ast from a stringified json
|
||||||
let program: kcl_lib::abstract_syntax_tree_types::Program =
|
let program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
||||||
serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
|
||||||
let mut mem: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
|
let mut mem: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let mut engine = kcl_lib::engine::EngineConnection::new(manager)
|
let mut engine = kcl_lib::engine::EngineConnection::new(manager)
|
||||||
@ -33,6 +32,36 @@ pub async fn execute_wasm(
|
|||||||
JsValue::from_serde(&memory).map_err(|e| e.to_string())
|
JsValue::from_serde(&memory).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wasm_bindgen wrapper for execute
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn modify_ast_for_sketch(
|
||||||
|
manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
||||||
|
program_str: &str,
|
||||||
|
sketch_name: &str,
|
||||||
|
sketch_id: &str,
|
||||||
|
) -> Result<JsValue, String> {
|
||||||
|
// deserialize the ast from a stringified json
|
||||||
|
let mut program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let mut engine = kcl_lib::engine::EngineConnection::new(manager)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
|
|
||||||
|
let _ = kcl_lib::ast::modify::modify_ast_for_sketch(
|
||||||
|
&mut engine,
|
||||||
|
&mut program,
|
||||||
|
sketch_name,
|
||||||
|
uuid::Uuid::parse_str(sketch_id).map_err(|e| e.to_string())?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(String::from)?;
|
||||||
|
|
||||||
|
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
||||||
|
// gloo-serialize crate instead.
|
||||||
|
JsValue::from_serde(&program).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn deserialize_files(data: &[u8]) -> Result<JsValue, JsError> {
|
pub fn deserialize_files(data: &[u8]) -> Result<JsValue, JsError> {
|
||||||
let ws_resp: kittycad::types::WebSocketResponse = bson::from_slice(data)?;
|
let ws_resp: kittycad::types::WebSocketResponse = bson::from_slice(data)?;
|
||||||
@ -73,8 +102,7 @@ pub fn parse_js(js: &str) -> Result<JsValue, String> {
|
|||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn recast_wasm(json_str: &str) -> Result<JsValue, JsError> {
|
pub fn recast_wasm(json_str: &str) -> Result<JsValue, JsError> {
|
||||||
// deserialize the ast from a stringified json
|
// deserialize the ast from a stringified json
|
||||||
let program: kcl_lib::abstract_syntax_tree_types::Program =
|
let program: kcl_lib::ast::types::Program = serde_json::from_str(json_str).map_err(JsError::from)?;
|
||||||
serde_json::from_str(json_str).map_err(JsError::from)?;
|
|
||||||
|
|
||||||
// Use the default options until we integrate into the UI the ability to change them.
|
// Use the default options until we integrate into the UI the ability to change them.
|
||||||
let result = program.recast(&Default::default(), 0);
|
let result = program.recast(&Default::default(), 0);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use kcl_lib::engine::EngineManager;
|
||||||
|
|
||||||
/// Executes a kcl program and takes a snapshot of the result.
|
/// Executes a kcl program and takes a snapshot of the result.
|
||||||
/// This returns the bytes of the snapshot.
|
/// This returns the bytes of the snapshot.
|
||||||
@ -38,13 +39,15 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
|
|||||||
let _ = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &mut engine)?;
|
let _ = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &mut engine)?;
|
||||||
|
|
||||||
// Send a snapshot request to the engine.
|
// Send a snapshot request to the engine.
|
||||||
let resp = engine.send_modeling_cmd_get_response(
|
let resp = engine
|
||||||
|
.send_modeling_cmd_get_response(
|
||||||
uuid::Uuid::new_v4(),
|
uuid::Uuid::new_v4(),
|
||||||
kcl_lib::executor::SourceRange::default(),
|
kcl_lib::executor::SourceRange::default(),
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
kittycad::types::ModelingCmd::TakeSnapshot {
|
||||||
format: kittycad::types::ImageFormat::Png,
|
format: kittycad::types::ImageFormat::Png,
|
||||||
},
|
},
|
||||||
)?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
||||||
@ -62,7 +65,7 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_execute_with_function_sketch() {
|
async fn serial_test_execute_with_function_sketch() {
|
||||||
let code = r#"fn box = (h, l, w) => {
|
let code = r#"fn box = (h, l, w) => {
|
||||||
const myBox = startSketchAt([0,0])
|
const myBox = startSketchAt([0,0])
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
@ -83,7 +86,7 @@ show(fnBox)"#;
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_execute_with_angled_line() {
|
async fn serial_test_execute_with_angled_line() {
|
||||||
let code = r#"const part001 = startSketchAt([4.83, 12.56])
|
let code = r#"const part001 = startSketchAt([4.83, 12.56])
|
||||||
|> line([15.1, 2.48], %)
|
|> line([15.1, 2.48], %)
|
||||||
|> line({ to: [3.15, -9.85], tag: 'seg01' }, %)
|
|> line({ to: [3.15, -9.85], tag: 'seg01' }, %)
|
||||||
@ -100,7 +103,7 @@ show(part001)"#;
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_execute_parametric_example() {
|
async fn serial_test_execute_parametric_example() {
|
||||||
let code = r#"const sigmaAllow = 35000 // psi
|
let code = r#"const sigmaAllow = 35000 // psi
|
||||||
const width = 9 // inch
|
const width = 9 // inch
|
||||||
const p = 150 // Force on shelf - lbs
|
const p = 150 // Force on shelf - lbs
|
||||||
|
257
src/wasm-lib/tests/modify/main.rs
Normal file
257
src/wasm-lib/tests/modify/main.rs
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use kcl_lib::{
|
||||||
|
ast::{modify::modify_ast_for_sketch, types::Program},
|
||||||
|
engine::{EngineConnection, EngineManager},
|
||||||
|
executor::{MemoryItem, SourceRange},
|
||||||
|
};
|
||||||
|
use kittycad::types::{ModelingCmd, Point3D};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
/// Setup the engine and parse code for an ast.
|
||||||
|
async fn setup(code: &str, name: &str) -> Result<(EngineConnection, Program, uuid::Uuid)> {
|
||||||
|
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
||||||
|
let http_client = reqwest::Client::builder()
|
||||||
|
.user_agent(user_agent)
|
||||||
|
// For file conversions we need this to be long.
|
||||||
|
.timeout(std::time::Duration::from_secs(600))
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(60));
|
||||||
|
let ws_client = reqwest::Client::builder()
|
||||||
|
.user_agent(user_agent)
|
||||||
|
// For file conversions we need this to be long.
|
||||||
|
.timeout(std::time::Duration::from_secs(600))
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(60))
|
||||||
|
.tcp_keepalive(std::time::Duration::from_secs(600))
|
||||||
|
.http1_only();
|
||||||
|
|
||||||
|
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
||||||
|
|
||||||
|
// Create the client.
|
||||||
|
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
||||||
|
|
||||||
|
let ws = client
|
||||||
|
.modeling()
|
||||||
|
.commands_ws(None, None, None, None, Some(false))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let tokens = kcl_lib::tokeniser::lexer(code);
|
||||||
|
let parser = kcl_lib::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast()?;
|
||||||
|
let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
|
||||||
|
let mut engine = kcl_lib::engine::EngineConnection::new(ws).await?;
|
||||||
|
let memory = kcl_lib::executor::execute(
|
||||||
|
program.clone(),
|
||||||
|
&mut mem,
|
||||||
|
kcl_lib::executor::BodyType::Root,
|
||||||
|
&mut engine,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// We need to get the sketch ID.
|
||||||
|
// Get the sketch group ID from memory.
|
||||||
|
let MemoryItem::SketchGroup(sketch_group) = memory.root.get(name).unwrap() else {
|
||||||
|
anyhow::bail!("part001 not found in memory: {:?}", memory);
|
||||||
|
};
|
||||||
|
let sketch_id = sketch_group.id;
|
||||||
|
|
||||||
|
let plane_id = uuid::Uuid::new_v4();
|
||||||
|
engine.send_modeling_cmd(
|
||||||
|
plane_id,
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::MakePlane {
|
||||||
|
clobber: false,
|
||||||
|
origin: Point3D { x: 0.0, y: 0.0, z: 0.0 },
|
||||||
|
size: 60.0,
|
||||||
|
x_axis: Point3D { x: 1.0, y: 0.0, z: 0.0 },
|
||||||
|
y_axis: Point3D { x: 0.0, y: 1.0, z: 0.0 },
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Enter sketch mode.
|
||||||
|
// We can't get control points without being in sketch mode.
|
||||||
|
// You can however get path info without sketch mode.
|
||||||
|
engine.send_modeling_cmd(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::SketchModeEnable {
|
||||||
|
animated: false,
|
||||||
|
ortho: true,
|
||||||
|
plane_id,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Enter edit mode.
|
||||||
|
// We can't get control points of an existing sketch without being in edit mode.
|
||||||
|
engine.send_modeling_cmd(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::EditModeEnter { target: sketch_id },
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok((engine, program, sketch_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_modify_sketch_part001() {
|
||||||
|
let name = "part001";
|
||||||
|
let code = format!(
|
||||||
|
r#"const {} = startSketchAt([8.41, 5.78])
|
||||||
|
|> line([7.37, -11.0], %)
|
||||||
|
|> line([-8.69, -3.75], %)
|
||||||
|
|> line([-5.0, 4.25], %)
|
||||||
|
"#,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
|
||||||
|
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
|
||||||
|
let mut new_program = program.clone();
|
||||||
|
let new_code = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Make sure the code is the same.
|
||||||
|
assert_eq!(code, new_code);
|
||||||
|
// Make sure the program is the same.
|
||||||
|
assert_eq!(new_program, program);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_modify_sketch_part002() {
|
||||||
|
let name = "part002";
|
||||||
|
let code = format!(
|
||||||
|
r#"const {} = startSketchAt([8.41, 5.78])
|
||||||
|
|> line([7.42, -8.62], %)
|
||||||
|
|> line([-6.38, -3.51], %)
|
||||||
|
|> line([-3.77, 3.56], %)
|
||||||
|
"#,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
|
||||||
|
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
|
||||||
|
let mut new_program = program.clone();
|
||||||
|
let new_code = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Make sure the code is the same.
|
||||||
|
assert_eq!(code, new_code);
|
||||||
|
// Make sure the program is the same.
|
||||||
|
assert_eq!(new_program, program);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
#[ignore] // until KittyCAD/engine#1434 is fixed.
|
||||||
|
async fn serial_test_modify_close_sketch() {
|
||||||
|
let name = "part002";
|
||||||
|
let code = format!(
|
||||||
|
r#"const {} = startSketchAt([7.91, 3.89])
|
||||||
|
|> line([7.42, -8.62], %)
|
||||||
|
|> line([-6.38, -3.51], %)
|
||||||
|
|> line([-3.77, 3.56], %)
|
||||||
|
|> close(%)
|
||||||
|
"#,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
|
||||||
|
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
|
||||||
|
let mut new_program = program.clone();
|
||||||
|
let new_code = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Make sure the code is the same.
|
||||||
|
assert_eq!(code, new_code);
|
||||||
|
// Make sure the program is the same.
|
||||||
|
assert_eq!(new_program, program);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_modify_line_to_close_sketch() {
|
||||||
|
let name = "part002";
|
||||||
|
let code = format!(
|
||||||
|
r#"const {} = startSketchAt([7.91, 3.89])
|
||||||
|
|> line([7.42, -8.62], %)
|
||||||
|
|> line([-6.38, -3.51], %)
|
||||||
|
|> line([-3.77, 3.56], %)
|
||||||
|
|> lineTo([7.91, 3.89], %)
|
||||||
|
"#,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
|
||||||
|
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
|
||||||
|
let mut new_program = program.clone();
|
||||||
|
let new_code = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Make sure the code is the same.
|
||||||
|
assert_eq!(
|
||||||
|
new_code,
|
||||||
|
format!(
|
||||||
|
r#"const {} = startSketchAt([7.91, 3.89])
|
||||||
|
|> line([7.42, -8.62], %)
|
||||||
|
|> line([-6.38, -3.51], %)
|
||||||
|
|> line([-3.77, 3.56], %)
|
||||||
|
|> close(%)
|
||||||
|
"#,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_modify_with_constraint() {
|
||||||
|
let name = "part002";
|
||||||
|
let code = format!(
|
||||||
|
r#"const thing = 12
|
||||||
|
const {} = startSketchAt([7.91, 3.89])
|
||||||
|
|> line([7.42, -8.62], %)
|
||||||
|
|> line([-6.38, -3.51], %)
|
||||||
|
|> line([-3.77, 3.56], %)
|
||||||
|
|> lineTo([thing, 3.89], %)
|
||||||
|
"#,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
|
||||||
|
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
|
||||||
|
let mut new_program = program.clone();
|
||||||
|
let result = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id).await;
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
r#"engine: KclErrorDetails { source_ranges: [SourceRange([159, 164])], message: "Sketch part002 is constrained `partial` and cannot be modified" }"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_modify_line_should_close_sketch() {
|
||||||
|
let name = "part003";
|
||||||
|
let code = format!(
|
||||||
|
r#"const {} = startSketchAt([13.69, 3.8])
|
||||||
|
|> line([4.23, -11.79], %)
|
||||||
|
|> line([-10.7, -1.16], %)
|
||||||
|
|> line([-3.72, 8.69], %)
|
||||||
|
|> line([10.19, 4.26], %)
|
||||||
|
"#,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
|
||||||
|
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
|
||||||
|
let mut new_program = program.clone();
|
||||||
|
let new_code = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Make sure the code is the same.
|
||||||
|
assert_eq!(
|
||||||
|
new_code,
|
||||||
|
format!(
|
||||||
|
r#"const {} = startSketchAt([13.69, 3.8])
|
||||||
|
|> line([4.23, -11.79], %)
|
||||||
|
|> line([-10.7, -1.16], %)
|
||||||
|
|> line([-3.72, 8.69], %)
|
||||||
|
|> close(%)
|
||||||
|
"#,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user