Merge branch 'main' into franknoirot/4088/create-file-url
This commit is contained in:
@ -1,3 +1,3 @@
|
||||
[codespell]
|
||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall
|
||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall,ser
|
||||
skip: **/target,node_modules,build,dist,./out,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./packages/codemirror-lang-kcl/test/all.test.ts,tsconfig.tsbuildinfo
|
||||
|
44
.github/workflows/cargo-bench.yml
vendored
44
.github/workflows/cargo-bench.yml
vendored
@ -1,44 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-bench.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-bench.yml
|
||||
workflow_dispatch:
|
||||
permissions: read-all
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
name: cargo bench
|
||||
jobs:
|
||||
cargo-bench:
|
||||
name: Benchmark with iai
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cargo install cargo-criterion
|
||||
sudo apt update
|
||||
sudo apt install -y valgrind
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.6.1
|
||||
- name: Benchmark kcl library
|
||||
shell: bash
|
||||
run: |-
|
||||
cd src/wasm-lib/kcl; cargo bench --all-features -- iai
|
||||
env:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -280,7 +280,7 @@ test(
|
||||
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||
await expect(page.getByText('New Project')).toBeVisible()
|
||||
await expect(page.getByText('Create project')).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('Opening the router-template project should load', async () => {
|
||||
|
@ -136,6 +136,22 @@ export class CmdBarFixture {
|
||||
}
|
||||
}
|
||||
|
||||
get cmdSearchInput() {
|
||||
return this.page.getByTestId('cmd-bar-search')
|
||||
}
|
||||
|
||||
get argumentInput() {
|
||||
return this.page.getByTestId('cmd-bar-arg-value')
|
||||
}
|
||||
|
||||
get cmdOptions() {
|
||||
return this.page.getByTestId('cmd-bar-option')
|
||||
}
|
||||
|
||||
chooseCommand = async (commandName: string) => {
|
||||
await this.cmdOptions.getByText(commandName).click()
|
||||
}
|
||||
|
||||
/**
|
||||
* Select an option from the command bar
|
||||
*/
|
||||
|
@ -103,7 +103,7 @@ export class HomePageFixture {
|
||||
.toEqual(expectedState)
|
||||
}
|
||||
|
||||
createAndGoToProject = async (projectTitle: string) => {
|
||||
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
|
||||
await expect(this.projectSection).not.toHaveText('Loading your Projects...')
|
||||
await this.projectButtonNew.click()
|
||||
await this.projectTextName.click()
|
||||
|
@ -63,6 +63,10 @@ export class ToolbarFixture {
|
||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||
}
|
||||
|
||||
get logoLink() {
|
||||
return this.page.getByTestId('app-logo')
|
||||
}
|
||||
|
||||
startSketchPlaneSelection = async () =>
|
||||
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
||||
|
||||
|
@ -172,7 +172,7 @@ test(
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('broken-code')).toBeVisible()
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
await expect(page.getByText('New Project')).toBeVisible()
|
||||
await expect(page.getByText('Create project')).toBeVisible()
|
||||
})
|
||||
await test.step('opening broken code project should clear the scene and show the error', async () => {
|
||||
// Go back home.
|
||||
@ -253,7 +253,7 @@ test(
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('empty')).toBeVisible()
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
await expect(page.getByText('New Project')).toBeVisible()
|
||||
await expect(page.getByText('Create project')).toBeVisible()
|
||||
})
|
||||
await test.step('opening empty code project should clear the scene', async () => {
|
||||
// Go back home.
|
||||
@ -985,6 +985,126 @@ test.describe(`Project management commands`, () => {
|
||||
})
|
||||
}
|
||||
)
|
||||
test(`Create a new project with a colliding name`, async ({
|
||||
context,
|
||||
homePage,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const projectName = 'test-project'
|
||||
await test.step(`Setup`, async () => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const projectDir = path.join(dir, projectName)
|
||||
await Promise.all([fsp.mkdir(projectDir, { recursive: true })])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
path.join(projectDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
})
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
title: projectName,
|
||||
fileCount: 1,
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('Create a new project with the same name', async () => {
|
||||
await cmdBar.openCmdBar()
|
||||
await cmdBar.chooseCommand('create project')
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
commandName: 'Create project',
|
||||
currentArgKey: 'name',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Name: '',
|
||||
},
|
||||
highlightedHeaderArg: 'name',
|
||||
})
|
||||
await cmdBar.argumentInput.fill(projectName)
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Check the project was created with a non-colliding name`, async () => {
|
||||
await toolbar.logoLink.click()
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
title: projectName + '-1',
|
||||
fileCount: 1,
|
||||
},
|
||||
{
|
||||
title: projectName,
|
||||
fileCount: 1,
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('Create another project with the same name', async () => {
|
||||
await cmdBar.openCmdBar()
|
||||
await cmdBar.chooseCommand('create project')
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
commandName: 'Create project',
|
||||
currentArgKey: 'name',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Name: '',
|
||||
},
|
||||
highlightedHeaderArg: 'name',
|
||||
})
|
||||
await cmdBar.argumentInput.fill(projectName)
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Check the second project was created with a non-colliding name`, async () => {
|
||||
await toolbar.logoLink.click()
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
title: projectName + '-2',
|
||||
fileCount: 1,
|
||||
},
|
||||
{
|
||||
title: projectName + '-1',
|
||||
fileCount: 1,
|
||||
},
|
||||
{
|
||||
title: projectName,
|
||||
fileCount: 1,
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test(`Create a few projects using the default project name`, async ({
|
||||
homePage,
|
||||
toolbar,
|
||||
}) => {
|
||||
for (let i = 0; i < 12; i++) {
|
||||
await test.step(`Create project ${i}`, async () => {
|
||||
await homePage.expectState({
|
||||
projectCards: Array.from({ length: i }, (_, i) => ({
|
||||
title: `project-${i.toString().padStart(3, '0')}`,
|
||||
fileCount: 1,
|
||||
})).toReversed(),
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await homePage.createAndGoToProject()
|
||||
await toolbar.logoLink.click()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test(
|
||||
@ -1391,7 +1511,7 @@ extrude001 = extrude(200, sketch001)`)
|
||||
await page.getByTestId('app-logo').click()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'New project' })
|
||||
page.getByRole('button', { name: 'Create project' })
|
||||
).toBeVisible()
|
||||
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
@ -1465,7 +1585,7 @@ test(
|
||||
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||
await expect(page.getByText('New Project')).toBeVisible()
|
||||
await expect(page.getByText('Create project')).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('Opening the router-template project should load the stream', async () => {
|
||||
@ -1494,7 +1614,7 @@ test(
|
||||
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||
await expect(page.getByText('New Project')).toBeVisible()
|
||||
await expect(page.getByText('Create project')).toBeVisible()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -1078,7 +1078,7 @@ export async function createProject({
|
||||
returnHome?: boolean
|
||||
}) {
|
||||
await test.step(`Create project and navigate to it`, async () => {
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await page.getByRole('button', { name: 'Create project' }).click()
|
||||
await page.getByRole('textbox', { name: 'Name' }).fill(name)
|
||||
await page.getByRole('button', { name: 'Continue' }).click()
|
||||
|
||||
|
@ -154,7 +154,6 @@
|
||||
"@playwright/test": "^1.49.0",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
"@types/d3-force": "^3.0.10",
|
||||
"@types/diff": "^6.0.0",
|
||||
"@types/electron": "^1.6.10",
|
||||
"@types/isomorphic-fetch": "^0.0.39",
|
||||
@ -175,7 +174,6 @@
|
||||
"@vitest/web-worker": "^1.5.0",
|
||||
"@xstate/cli": "^0.5.17",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"d3-force": "^3.0.0",
|
||||
"electron": "32.1.2",
|
||||
"electron-builder": "24.13.3",
|
||||
"electron-notarize": "1.2.2",
|
||||
|
@ -25,13 +25,13 @@ import {
|
||||
CallExpression,
|
||||
PathToNode,
|
||||
Program,
|
||||
SourceRange,
|
||||
Expr,
|
||||
parse,
|
||||
recast,
|
||||
defaultSourceRange,
|
||||
resultIsOk,
|
||||
ProgramMemory,
|
||||
topLevelRange,
|
||||
} from 'lang/wasm'
|
||||
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
||||
import { ConstrainInfo } from 'lang/std/stdTypes'
|
||||
@ -600,8 +600,8 @@ const ConstraintSymbol = ({
|
||||
if (err(_node)) return
|
||||
const node = _node.node
|
||||
|
||||
const range: SourceRange = node
|
||||
? [node.start, node.end, true]
|
||||
const range = node
|
||||
? topLevelRange(node.start, node.end)
|
||||
: defaultSourceRange()
|
||||
|
||||
if (_type === 'intersectionTag') return null
|
||||
|
@ -59,6 +59,7 @@ import {
|
||||
sourceRangeFromRust,
|
||||
resultIsOk,
|
||||
SourceRange,
|
||||
topLevelRange,
|
||||
} from 'lang/wasm'
|
||||
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
|
||||
import {
|
||||
@ -628,7 +629,7 @@ export class SceneEntities {
|
||||
|
||||
const startRange = _node1.node.start
|
||||
const endRange = _node1.node.end
|
||||
const sourceRange: SourceRange = [startRange, endRange, true]
|
||||
const sourceRange = topLevelRange(startRange, endRange)
|
||||
const selection: Selections = computeSelectionFromSourceRangeAndAST(
|
||||
sourceRange,
|
||||
maybeModdedAst
|
||||
@ -2012,7 +2013,7 @@ export class SceneEntities {
|
||||
kclManager.programMemory,
|
||||
{
|
||||
type: 'sourceRange',
|
||||
sourceRange: [node.start, node.end, true],
|
||||
sourceRange: topLevelRange(node.start, node.end),
|
||||
},
|
||||
getChangeSketchInput()
|
||||
)
|
||||
@ -2263,7 +2264,7 @@ export class SceneEntities {
|
||||
)
|
||||
if (trap(_node, { suppress: true })) return
|
||||
const node = _node.node
|
||||
editorManager.setHighlightRange([[node.start, node.end, true]])
|
||||
editorManager.setHighlightRange([topLevelRange(node.start, node.end)])
|
||||
const yellow = 0xffff00
|
||||
colorSegment(selected, yellow)
|
||||
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||
|
@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import { trap } from 'lib/trap'
|
||||
import { codeToIdSelections } from 'lib/selections'
|
||||
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
||||
import { defaultSourceRange } from 'lang/wasm'
|
||||
import { defaultSourceRange, SourceRange, topLevelRange } from 'lang/wasm'
|
||||
|
||||
export function AstExplorer() {
|
||||
const { context } = useModelingContext()
|
||||
@ -118,19 +118,19 @@ function DisplayObj({
|
||||
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
|
||||
}`}
|
||||
onMouseEnter={(e) => {
|
||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
|
||||
editorManager.setHighlightRange([
|
||||
topLevelRange(obj?.start || 0, obj.end),
|
||||
])
|
||||
e.stopPropagation()
|
||||
}}
|
||||
onMouseMove={(e) => {
|
||||
e.stopPropagation()
|
||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
|
||||
editorManager.setHighlightRange([
|
||||
topLevelRange(obj?.start || 0, obj.end),
|
||||
])
|
||||
}}
|
||||
onClick={(e) => {
|
||||
const range: [number, number, boolean] = [
|
||||
obj?.start || 0,
|
||||
obj.end || 0,
|
||||
true,
|
||||
]
|
||||
const range = topLevelRange(obj?.start || 0, obj.end || 0)
|
||||
const idInfo = codeToIdSelections([
|
||||
{ codeRef: codeRefFromRange(range, kclManager.ast) },
|
||||
])[0]
|
||||
|
@ -17,7 +17,7 @@ import { StateFrom } from 'xstate'
|
||||
const semanticEntityNames: {
|
||||
[key: string]: Array<Artifact['type'] | 'defaultPlane'>
|
||||
} = {
|
||||
face: ['wall', 'cap', 'solid2D'],
|
||||
face: ['wall', 'cap', 'solid2d'],
|
||||
edge: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
||||
point: [],
|
||||
plane: ['defaultPlane'],
|
||||
|
@ -52,6 +52,7 @@ function CommandComboBox({
|
||||
className="w-5 h-5 bg-primary/10 dark:bg-primary text-primary dark:text-inherit"
|
||||
/>
|
||||
<Combobox.Input
|
||||
data-testid="cmd-bar-search"
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
className="w-full bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none"
|
||||
onKeyDown={(event) => {
|
||||
@ -85,6 +86,7 @@ function CommandComboBox({
|
||||
value={option}
|
||||
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90 ui-disabled:!text-chalkboard-50"
|
||||
disabled={optionIsDisabled(option)}
|
||||
data-testid={`cmd-bar-option`}
|
||||
>
|
||||
{'icon' in option && option.icon && (
|
||||
<CustomIcon name={option.icon} className="w-5 h-5" />
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { useMemo } from 'react'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import {
|
||||
ArtifactGraph,
|
||||
expandPlane,
|
||||
PlaneArtifactRich,
|
||||
} from 'lang/std/artifactGraph'
|
||||
import { expandPlane, PlaneArtifactRich } from 'lang/std/artifactGraph'
|
||||
import { ArtifactGraph } from 'lang/wasm'
|
||||
import { DebugDisplayArray, GenericObj } from './DebugDisplayObj'
|
||||
|
||||
export function DebugFeatureTree() {
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
getNextProjectIndex,
|
||||
interpolateProjectNameWithIndex,
|
||||
doesProjectNameNeedInterpolated,
|
||||
getUniqueProjectName,
|
||||
getNextFileName,
|
||||
} from 'lib/desktopFS'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
@ -337,16 +338,12 @@ const ProjectsContextDesktop = ({
|
||||
: settings.projects.defaultProjectName.current
|
||||
).trim()
|
||||
|
||||
if (doesProjectNameNeedInterpolated(name)) {
|
||||
const nextIndex = getNextProjectIndex(name, input.projects)
|
||||
name = interpolateProjectNameWithIndex(name, nextIndex)
|
||||
}
|
||||
|
||||
await createNewProjectDirectory(name)
|
||||
const uniqueName = getUniqueProjectName(name, input.projects)
|
||||
await createNewProjectDirectory(uniqueName)
|
||||
|
||||
return {
|
||||
message: `Successfully created "${name}"`,
|
||||
name,
|
||||
message: `Successfully created "${uniqueName}"`,
|
||||
name: uniqueName,
|
||||
}
|
||||
}),
|
||||
renameProject: fromPromise(async ({ input }) => {
|
||||
|
@ -301,7 +301,7 @@ export const Stream = () => {
|
||||
return
|
||||
}
|
||||
const path = getArtifactOfTypes(
|
||||
{ key: entity_id, types: ['path', 'solid2D', 'segment'] },
|
||||
{ key: entity_id, types: ['path', 'solid2d', 'segment'] },
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(path)) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { toolTips } from 'lang/langHelpers'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import { PathToNode, Program, Expr } from '../../lang/wasm'
|
||||
import { PathToNode, Program, Expr, topLevelRange } from '../../lang/wasm'
|
||||
import { getNodeFromPath } from '../../lang/queryAst'
|
||||
import {
|
||||
PathToNodeMap,
|
||||
@ -41,7 +41,7 @@ export function removeConstrainingValuesInfo({
|
||||
graphSelections: nodes.map(
|
||||
(node): Selection => ({
|
||||
codeRef: codeRefFromRange(
|
||||
[node.start, node.end, true],
|
||||
topLevelRange(node.start, node.end),
|
||||
kclManager.ast
|
||||
),
|
||||
})
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
ProgramMemory,
|
||||
recast,
|
||||
SourceRange,
|
||||
topLevelRange,
|
||||
} from 'lang/wasm'
|
||||
import { getNodeFromPath } from './queryAst'
|
||||
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
||||
@ -376,11 +377,7 @@ export class KclManager {
|
||||
}
|
||||
this.ast = { ...ast }
|
||||
// updateArtifactGraph relies on updated executeState/programMemory
|
||||
await this.engineCommandManager.updateArtifactGraph(
|
||||
this.ast,
|
||||
execState.artifactCommands,
|
||||
execState.artifacts
|
||||
)
|
||||
this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
|
||||
this._executeCallback()
|
||||
if (!isInterrupted) {
|
||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||
@ -473,7 +470,7 @@ export class KclManager {
|
||||
...artifact,
|
||||
codeRef: {
|
||||
...artifact.codeRef,
|
||||
range: [node.start, node.end, true],
|
||||
range: topLevelRange(node.start, node.end),
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -594,7 +591,7 @@ export class KclManager {
|
||||
if (start && end) {
|
||||
returnVal.graphSelections.push({
|
||||
codeRef: {
|
||||
range: [start, end, true],
|
||||
range: topLevelRange(start, end),
|
||||
pathToNode: path,
|
||||
},
|
||||
})
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { kclErrorsToDiagnostics, KCLError } from './errors'
|
||||
import { defaultArtifactGraph, topLevelRange } from 'lang/wasm'
|
||||
|
||||
describe('test kclErrToDiagnostic', () => {
|
||||
it('converts KCL errors to CodeMirror diagnostics', () => {
|
||||
@ -8,18 +9,20 @@ describe('test kclErrToDiagnostic', () => {
|
||||
message: '',
|
||||
kind: 'semantic',
|
||||
msg: 'Semantic error',
|
||||
sourceRange: [0, 1, true],
|
||||
sourceRange: topLevelRange(0, 1),
|
||||
operations: [],
|
||||
artifactCommands: [],
|
||||
artifactGraph: defaultArtifactGraph(),
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
message: '',
|
||||
kind: 'type',
|
||||
msg: 'Type error',
|
||||
sourceRange: [4, 5, true],
|
||||
sourceRange: topLevelRange(4, 5),
|
||||
operations: [],
|
||||
artifactCommands: [],
|
||||
artifactGraph: defaultArtifactGraph(),
|
||||
},
|
||||
]
|
||||
const diagnostics = kclErrorsToDiagnostics(errors)
|
||||
|
@ -5,7 +5,13 @@ import { posToOffset } from '@kittycad/codemirror-lsp-client'
|
||||
import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
|
||||
import { Text } from '@codemirror/state'
|
||||
import { EditorView } from 'codemirror'
|
||||
import { ArtifactCommand, SourceRange } from 'lang/wasm'
|
||||
import {
|
||||
ArtifactCommand,
|
||||
ArtifactGraph,
|
||||
defaultArtifactGraph,
|
||||
isTopLevelModule,
|
||||
SourceRange,
|
||||
} from 'lang/wasm'
|
||||
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
||||
|
||||
type ExtractKind<T> = T extends { kind: infer K } ? K : never
|
||||
@ -15,13 +21,15 @@ export class KCLError extends Error {
|
||||
msg: string
|
||||
operations: Operation[]
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactGraph: ArtifactGraph
|
||||
|
||||
constructor(
|
||||
kind: ExtractKind<RustKclError> | 'name',
|
||||
msg: string,
|
||||
sourceRange: SourceRange,
|
||||
operations: Operation[],
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactCommands: ArtifactCommand[],
|
||||
artifactGraph: ArtifactGraph
|
||||
) {
|
||||
super()
|
||||
this.kind = kind
|
||||
@ -29,6 +37,7 @@ export class KCLError extends Error {
|
||||
this.sourceRange = sourceRange
|
||||
this.operations = operations
|
||||
this.artifactCommands = artifactCommands
|
||||
this.artifactGraph = artifactGraph
|
||||
Object.setPrototypeOf(this, KCLError.prototype)
|
||||
}
|
||||
}
|
||||
@ -38,9 +47,17 @@ export class KCLLexicalError extends KCLError {
|
||||
msg: string,
|
||||
sourceRange: SourceRange,
|
||||
operations: Operation[],
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactCommands: ArtifactCommand[],
|
||||
artifactGraph: ArtifactGraph
|
||||
) {
|
||||
super('lexical', msg, sourceRange, operations, artifactCommands)
|
||||
super(
|
||||
'lexical',
|
||||
msg,
|
||||
sourceRange,
|
||||
operations,
|
||||
artifactCommands,
|
||||
artifactGraph
|
||||
)
|
||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||
}
|
||||
}
|
||||
@ -50,9 +67,17 @@ export class KCLInternalError extends KCLError {
|
||||
msg: string,
|
||||
sourceRange: SourceRange,
|
||||
operations: Operation[],
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactCommands: ArtifactCommand[],
|
||||
artifactGraph: ArtifactGraph
|
||||
) {
|
||||
super('internal', msg, sourceRange, operations, artifactCommands)
|
||||
super(
|
||||
'internal',
|
||||
msg,
|
||||
sourceRange,
|
||||
operations,
|
||||
artifactCommands,
|
||||
artifactGraph
|
||||
)
|
||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||
}
|
||||
}
|
||||
@ -62,9 +87,17 @@ export class KCLSyntaxError extends KCLError {
|
||||
msg: string,
|
||||
sourceRange: SourceRange,
|
||||
operations: Operation[],
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactCommands: ArtifactCommand[],
|
||||
artifactGraph: ArtifactGraph
|
||||
) {
|
||||
super('syntax', msg, sourceRange, operations, artifactCommands)
|
||||
super(
|
||||
'syntax',
|
||||
msg,
|
||||
sourceRange,
|
||||
operations,
|
||||
artifactCommands,
|
||||
artifactGraph
|
||||
)
|
||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||
}
|
||||
}
|
||||
@ -74,9 +107,17 @@ export class KCLSemanticError extends KCLError {
|
||||
msg: string,
|
||||
sourceRange: SourceRange,
|
||||
operations: Operation[],
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactCommands: ArtifactCommand[],
|
||||
artifactGraph: ArtifactGraph
|
||||
) {
|
||||
super('semantic', msg, sourceRange, operations, artifactCommands)
|
||||
super(
|
||||
'semantic',
|
||||
msg,
|
||||
sourceRange,
|
||||
operations,
|
||||
artifactCommands,
|
||||
artifactGraph
|
||||
)
|
||||
Object.setPrototypeOf(this, KCLSemanticError.prototype)
|
||||
}
|
||||
}
|
||||
@ -86,9 +127,10 @@ export class KCLTypeError extends KCLError {
|
||||
msg: string,
|
||||
sourceRange: SourceRange,
|
||||
operations: Operation[],
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactCommands: ArtifactCommand[],
|
||||
artifactGraph: ArtifactGraph
|
||||
) {
|
||||
super('type', msg, sourceRange, operations, artifactCommands)
|
||||
super('type', msg, sourceRange, operations, artifactCommands, artifactGraph)
|
||||
Object.setPrototypeOf(this, KCLTypeError.prototype)
|
||||
}
|
||||
}
|
||||
@ -98,9 +140,17 @@ export class KCLUnimplementedError extends KCLError {
|
||||
msg: string,
|
||||
sourceRange: SourceRange,
|
||||
operations: Operation[],
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactCommands: ArtifactCommand[],
|
||||
artifactGraph: ArtifactGraph
|
||||
) {
|
||||
super('unimplemented', msg, sourceRange, operations, artifactCommands)
|
||||
super(
|
||||
'unimplemented',
|
||||
msg,
|
||||
sourceRange,
|
||||
operations,
|
||||
artifactCommands,
|
||||
artifactGraph
|
||||
)
|
||||
Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
|
||||
}
|
||||
}
|
||||
@ -110,9 +160,17 @@ export class KCLUnexpectedError extends KCLError {
|
||||
msg: string,
|
||||
sourceRange: SourceRange,
|
||||
operations: Operation[],
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactCommands: ArtifactCommand[],
|
||||
artifactGraph: ArtifactGraph
|
||||
) {
|
||||
super('unexpected', msg, sourceRange, operations, artifactCommands)
|
||||
super(
|
||||
'unexpected',
|
||||
msg,
|
||||
sourceRange,
|
||||
operations,
|
||||
artifactCommands,
|
||||
artifactGraph
|
||||
)
|
||||
Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
|
||||
}
|
||||
}
|
||||
@ -122,14 +180,16 @@ export class KCLValueAlreadyDefined extends KCLError {
|
||||
key: string,
|
||||
sourceRange: SourceRange,
|
||||
operations: Operation[],
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactCommands: ArtifactCommand[],
|
||||
artifactGraph: ArtifactGraph
|
||||
) {
|
||||
super(
|
||||
'name',
|
||||
`Key ${key} was already defined elsewhere`,
|
||||
sourceRange,
|
||||
operations,
|
||||
artifactCommands
|
||||
artifactCommands,
|
||||
artifactGraph
|
||||
)
|
||||
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
|
||||
}
|
||||
@ -140,14 +200,16 @@ export class KCLUndefinedValueError extends KCLError {
|
||||
key: string,
|
||||
sourceRange: SourceRange,
|
||||
operations: Operation[],
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactCommands: ArtifactCommand[],
|
||||
artifactGraph: ArtifactGraph
|
||||
) {
|
||||
super(
|
||||
'name',
|
||||
`Key ${key} has not been defined`,
|
||||
sourceRange,
|
||||
operations,
|
||||
artifactCommands
|
||||
artifactCommands,
|
||||
artifactGraph
|
||||
)
|
||||
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
|
||||
}
|
||||
@ -167,9 +229,10 @@ export function lspDiagnosticsToKclErrors(
|
||||
new KCLError(
|
||||
'unexpected',
|
||||
message,
|
||||
[posToOffset(doc, range.start)!, posToOffset(doc, range.end)!, true],
|
||||
[posToOffset(doc, range.start)!, posToOffset(doc, range.end)!, 0],
|
||||
[],
|
||||
[]
|
||||
[],
|
||||
defaultArtifactGraph()
|
||||
)
|
||||
)
|
||||
.sort((a, b) => {
|
||||
@ -193,7 +256,7 @@ export function kclErrorsToDiagnostics(
|
||||
errors: KCLError[]
|
||||
): CodeMirrorDiagnostic[] {
|
||||
return errors
|
||||
?.filter((err) => err.sourceRange[2])
|
||||
?.filter((err) => isTopLevelModule(err.sourceRange))
|
||||
.map((err) => {
|
||||
return {
|
||||
from: err.sourceRange[0],
|
||||
@ -208,7 +271,7 @@ export function complilationErrorsToDiagnostics(
|
||||
errors: CompilationError[]
|
||||
): CodeMirrorDiagnostic[] {
|
||||
return errors
|
||||
?.filter((err) => err.sourceRange[2] === 0)
|
||||
?.filter((err) => isTopLevelModule(err.sourceRange))
|
||||
.map((err) => {
|
||||
let severity: any = 'error'
|
||||
if (err.severity === 'Warning') {
|
||||
|
@ -6,6 +6,8 @@ import {
|
||||
Sketch,
|
||||
initPromise,
|
||||
sketchFromKclValue,
|
||||
defaultArtifactGraph,
|
||||
topLevelRange,
|
||||
} from './wasm'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { KCLError } from './errors'
|
||||
@ -480,9 +482,10 @@ const theExtrude = startSketchOn('XY')
|
||||
new KCLError(
|
||||
'undefined_value',
|
||||
'memory item key `myVarZ` is not defined',
|
||||
[129, 135, true],
|
||||
topLevelRange(129, 135),
|
||||
[],
|
||||
[]
|
||||
[],
|
||||
defaultArtifactGraph()
|
||||
)
|
||||
)
|
||||
})
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
||||
import { Identifier, assertParse, initPromise, Parameter } from './wasm'
|
||||
import {
|
||||
Identifier,
|
||||
assertParse,
|
||||
initPromise,
|
||||
Parameter,
|
||||
SourceRange,
|
||||
topLevelRange,
|
||||
} from './wasm'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
@ -17,11 +24,10 @@ const sk3 = startSketchAt([0, 0])
|
||||
`
|
||||
const subStr = 'lineTo([3, 4], %, $yo)'
|
||||
const lineToSubstringIndex = code.indexOf(subStr)
|
||||
const sourceRange: [number, number, boolean] = [
|
||||
const sourceRange = topLevelRange(
|
||||
lineToSubstringIndex,
|
||||
lineToSubstringIndex + subStr.length,
|
||||
true,
|
||||
]
|
||||
lineToSubstringIndex + subStr.length
|
||||
)
|
||||
|
||||
const ast = assertParse(code)
|
||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||
@ -29,7 +35,7 @@ const sk3 = startSketchAt([0, 0])
|
||||
if (err(_node)) throw _node
|
||||
const { node } = _node
|
||||
|
||||
expect([node.start, node.end, true]).toEqual(sourceRange)
|
||||
expect(topLevelRange(node.start, node.end)).toEqual(sourceRange)
|
||||
expect(node.type).toBe('CallExpression')
|
||||
})
|
||||
it('gets path right for function definition params', () => {
|
||||
@ -45,11 +51,7 @@ const sk3 = startSketchAt([0, 0])
|
||||
const b1 = cube([0,0], 10)`
|
||||
const subStr = 'pos, scale'
|
||||
const subStrIndex = code.indexOf(subStr)
|
||||
const sourceRange: [number, number, boolean] = [
|
||||
subStrIndex,
|
||||
subStrIndex + 'pos'.length,
|
||||
true,
|
||||
]
|
||||
const sourceRange = topLevelRange(subStrIndex, subStrIndex + 'pos'.length)
|
||||
|
||||
const ast = assertParse(code)
|
||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||
@ -81,11 +83,7 @@ const b1 = cube([0,0], 10)`
|
||||
const b1 = cube([0,0], 10)`
|
||||
const subStr = 'scale, 0'
|
||||
const subStrIndex = code.indexOf(subStr)
|
||||
const sourceRange: [number, number, boolean] = [
|
||||
subStrIndex,
|
||||
subStrIndex + 'scale'.length,
|
||||
true,
|
||||
]
|
||||
const sourceRange = topLevelRange(subStrIndex, subStrIndex + 'scale'.length)
|
||||
|
||||
const ast = assertParse(code)
|
||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||
|
@ -1,4 +1,11 @@
|
||||
import { assertParse, recast, initPromise, Identifier } from './wasm'
|
||||
import {
|
||||
assertParse,
|
||||
recast,
|
||||
initPromise,
|
||||
Identifier,
|
||||
SourceRange,
|
||||
topLevelRange,
|
||||
} from './wasm'
|
||||
import {
|
||||
createLiteral,
|
||||
createIdentifier,
|
||||
@ -148,11 +155,7 @@ function giveSketchFnCallTagTestHelper(
|
||||
// making it more of an integration test, but easier to read the test intention is the goal
|
||||
const ast = assertParse(code)
|
||||
const start = code.indexOf(searchStr)
|
||||
const range: [number, number, boolean] = [
|
||||
start,
|
||||
start + searchStr.length,
|
||||
true,
|
||||
]
|
||||
const range = topLevelRange(start, start + searchStr.length)
|
||||
const sketchRes = giveSketchFnCallTag(ast, range)
|
||||
if (err(sketchRes)) throw sketchRes
|
||||
const { modifiedAst, tag, isTagExisting } = sketchRes
|
||||
@ -230,7 +233,7 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
[startIndex, startIndex, true],
|
||||
topLevelRange(startIndex, startIndex),
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
@ -244,7 +247,7 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
[startIndex, startIndex, true],
|
||||
topLevelRange(startIndex, startIndex),
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
@ -258,7 +261,7 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
[startIndex, startIndex, true],
|
||||
topLevelRange(startIndex, startIndex),
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
@ -272,7 +275,7 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
[startIndex, startIndex, true],
|
||||
topLevelRange(startIndex, startIndex),
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
@ -286,7 +289,7 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
ast,
|
||||
execState.memory,
|
||||
[startIndex, startIndex, true],
|
||||
topLevelRange(startIndex, startIndex),
|
||||
'newVar'
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
@ -306,18 +309,16 @@ describe('testing sketchOnExtrudedFace', () => {
|
||||
const ast = assertParse(code)
|
||||
|
||||
const segmentSnippet = `line([9.7, 9.19], %)`
|
||||
const segmentRange: [number, number, boolean] = [
|
||||
const segmentRange = topLevelRange(
|
||||
code.indexOf(segmentSnippet),
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length
|
||||
)
|
||||
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
||||
const extrudeSnippet = `extrude(5 + 7, %)`
|
||||
const extrudeRange: [number, number, boolean] = [
|
||||
const extrudeRange = topLevelRange(
|
||||
code.indexOf(extrudeSnippet),
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length
|
||||
)
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
const extruded = sketchOnExtrudedFace(
|
||||
@ -346,18 +347,16 @@ sketch001 = startSketchOn(part001, seg01)`)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = assertParse(code)
|
||||
const segmentSnippet = `close(%)`
|
||||
const segmentRange: [number, number, boolean] = [
|
||||
const segmentRange = topLevelRange(
|
||||
code.indexOf(segmentSnippet),
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length
|
||||
)
|
||||
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
||||
const extrudeSnippet = `extrude(5 + 7, %)`
|
||||
const extrudeRange: [number, number, boolean] = [
|
||||
const extrudeRange = topLevelRange(
|
||||
code.indexOf(extrudeSnippet),
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length
|
||||
)
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
const extruded = sketchOnExtrudedFace(
|
||||
@ -386,18 +385,16 @@ sketch001 = startSketchOn(part001, seg01)`)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = assertParse(code)
|
||||
const sketchSnippet = `startProfileAt([3.58, 2.06], %)`
|
||||
const sketchRange: [number, number, boolean] = [
|
||||
const sketchRange = topLevelRange(
|
||||
code.indexOf(sketchSnippet),
|
||||
code.indexOf(sketchSnippet) + sketchSnippet.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(sketchSnippet) + sketchSnippet.length
|
||||
)
|
||||
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
||||
const extrudeSnippet = `extrude(5 + 7, %)`
|
||||
const extrudeRange: [number, number, boolean] = [
|
||||
const extrudeRange = topLevelRange(
|
||||
code.indexOf(extrudeSnippet),
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length
|
||||
)
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
const extruded = sketchOnExtrudedFace(
|
||||
@ -435,18 +432,16 @@ sketch001 = startSketchOn(part001, 'END')`)
|
||||
part001 = extrude(5 + 7, sketch001)`
|
||||
const ast = assertParse(code)
|
||||
const segmentSnippet = `line([4.99, -0.46], %)`
|
||||
const segmentRange: [number, number, boolean] = [
|
||||
const segmentRange = topLevelRange(
|
||||
code.indexOf(segmentSnippet),
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length
|
||||
)
|
||||
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
||||
const extrudeSnippet = `extrude(5 + 7, sketch001)`
|
||||
const extrudeRange: [number, number, boolean] = [
|
||||
const extrudeRange = topLevelRange(
|
||||
code.indexOf(extrudeSnippet),
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length
|
||||
)
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
const updatedAst = sketchOnExtrudedFace(
|
||||
@ -471,11 +466,10 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const lineOfInterest = 'line([306.21, 198.85], %, $a)'
|
||||
const range: [number, number, boolean] = [
|
||||
const range = topLevelRange(
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
)
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
const modifiedAst = deleteSegmentFromPipeExpression(
|
||||
[],
|
||||
@ -549,11 +543,10 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
|
||||
const ast = assertParse(code)
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const lineOfInterest = line
|
||||
const range: [number, number, boolean] = [
|
||||
const range = topLevelRange(
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
)
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
const dependentSegments = findUsesOfTagInPipe(ast, pathToNode)
|
||||
const modifiedAst = deleteSegmentFromPipeExpression(
|
||||
@ -638,11 +631,10 @@ describe('Testing removeSingleConstraintInfo', () => {
|
||||
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const lineOfInterest = expectedFinish.split('(')[0] + '('
|
||||
const range: [number, number, boolean] = [
|
||||
const range = topLevelRange(
|
||||
code.indexOf(lineOfInterest) + 1,
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
)
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
let argPosition: SimplifiedArgDetails
|
||||
if (key === 'arrayIndex' && typeof value === 'number') {
|
||||
@ -692,11 +684,10 @@ describe('Testing removeSingleConstraintInfo', () => {
|
||||
|
||||
const execState = await enginelessExecutor(ast)
|
||||
const lineOfInterest = expectedFinish.split('(')[0] + '('
|
||||
const range: [number, number, boolean] = [
|
||||
const range = topLevelRange(
|
||||
code.indexOf(lineOfInterest) + 1,
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
)
|
||||
let argPosition: SimplifiedArgDetails
|
||||
if (key === 'arrayIndex' && typeof value === 'number') {
|
||||
argPosition = {
|
||||
@ -889,11 +880,10 @@ sketch002 = startSketchOn({
|
||||
const execState = await enginelessExecutor(ast)
|
||||
|
||||
// deleteFromSelection
|
||||
const range: [number, number, boolean] = [
|
||||
const range = topLevelRange(
|
||||
codeBefore.indexOf(lineOfInterest),
|
||||
codeBefore.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
codeBefore.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
)
|
||||
const artifact = { type } as Artifact
|
||||
const newAst = await deleteFromSelection(
|
||||
ast,
|
||||
|
@ -8,6 +8,8 @@ import {
|
||||
makeDefaultPlanes,
|
||||
PipeExpression,
|
||||
VariableDeclarator,
|
||||
SourceRange,
|
||||
topLevelRange,
|
||||
} from '../wasm'
|
||||
import {
|
||||
EdgeTreatmentType,
|
||||
@ -77,11 +79,10 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
||||
code: string,
|
||||
expectedExtrudeSnippet: string
|
||||
): CallExpression | PipeExpression | Error {
|
||||
const extrudeRange: [number, number, boolean] = [
|
||||
const extrudeRange = topLevelRange(
|
||||
code.indexOf(expectedExtrudeSnippet),
|
||||
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length
|
||||
)
|
||||
const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
const expectedExtrudeNodeResult = getNodeFromPath<
|
||||
VariableDeclarator | CallExpression
|
||||
@ -112,11 +113,10 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
||||
const ast = assertParse(code)
|
||||
|
||||
// selection
|
||||
const segmentRange: [number, number, boolean] = [
|
||||
const segmentRange = topLevelRange(
|
||||
code.indexOf(selectedSegmentSnippet),
|
||||
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length
|
||||
)
|
||||
const selection: Selection = {
|
||||
codeRef: codeRefFromRange(segmentRange, ast),
|
||||
}
|
||||
@ -260,12 +260,12 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
||||
const ast = assertParse(code)
|
||||
|
||||
// selection
|
||||
const segmentRanges: Array<[number, number, boolean]> = selectionSnippets.map(
|
||||
(selectionSnippet) => [
|
||||
const segmentRanges: Array<SourceRange> = selectionSnippets.map(
|
||||
(selectionSnippet) =>
|
||||
topLevelRange(
|
||||
code.indexOf(selectionSnippet),
|
||||
code.indexOf(selectionSnippet) + selectionSnippet.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(selectionSnippet) + selectionSnippet.length
|
||||
)
|
||||
)
|
||||
|
||||
// executeAst
|
||||
@ -596,11 +596,10 @@ extrude001 = extrude(-5, sketch001)
|
||||
it('should correctly identify getOppositeEdge and baseEdge edges', () => {
|
||||
const ast = assertParse(code)
|
||||
const lineOfInterest = `line([7.11, 3.48], %, $seg01)`
|
||||
const range: [number, number, boolean] = [
|
||||
const range = topLevelRange(
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
)
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
@ -615,11 +614,10 @@ extrude001 = extrude(-5, sketch001)
|
||||
it('should correctly identify getPreviousAdjacentEdge edges', () => {
|
||||
const ast = assertParse(code)
|
||||
const lineOfInterest = `line([-6.37, 3.88], %, $seg02)`
|
||||
const range: [number, number, boolean] = [
|
||||
const range = topLevelRange(
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
)
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
@ -634,11 +632,10 @@ extrude001 = extrude(-5, sketch001)
|
||||
it('should correctly identify no edges', () => {
|
||||
const ast = assertParse(code)
|
||||
const lineOfInterest = `line([-3.29, -13.85], %)`
|
||||
const range: [number, number, boolean] = [
|
||||
const range = topLevelRange(
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
)
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
@ -660,13 +657,12 @@ describe('Testing button states', () => {
|
||||
) => {
|
||||
const ast = assertParse(code)
|
||||
|
||||
const range: [number, number, boolean] = segmentSnippet
|
||||
? [
|
||||
const range = segmentSnippet
|
||||
? topLevelRange(
|
||||
code.indexOf(segmentSnippet),
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
: [ast.end, ast.end, true] // empty line in the end of the code
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length
|
||||
)
|
||||
: topLevelRange(ast.end, ast.end) // empty line in the end of the code
|
||||
|
||||
const selectionRanges: Selections = {
|
||||
graphSelections: [
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
ArtifactGraph,
|
||||
CallExpression,
|
||||
Expr,
|
||||
Identifier,
|
||||
@ -31,11 +32,7 @@ import {
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import { KclCommandValue } from 'lib/commandTypes'
|
||||
import {
|
||||
Artifact,
|
||||
ArtifactGraph,
|
||||
getSweepFromSuspectedPath,
|
||||
} from 'lang/std/artifactGraph'
|
||||
import { Artifact, getSweepFromSuspectedPath } from 'lang/std/artifactGraph'
|
||||
import {
|
||||
kclManager,
|
||||
engineCommandManager,
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { ArtifactGraph } from 'lang/std/artifactGraph'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Expr } from 'wasm-lib/kcl/bindings/Expr'
|
||||
import { Program } from 'wasm-lib/kcl/bindings/Program'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { PathToNode, VariableDeclarator } from 'lang/wasm'
|
||||
import { ArtifactGraph, PathToNode, VariableDeclarator } from 'lang/wasm'
|
||||
import {
|
||||
getPathToExtrudeForSegmentSelection,
|
||||
mutateAstWithTagForSketchSegment,
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
initPromise,
|
||||
PathToNode,
|
||||
Identifier,
|
||||
topLevelRange,
|
||||
} from './wasm'
|
||||
import {
|
||||
findAllPreviousVariables,
|
||||
@ -57,7 +58,7 @@ variableBelowShouldNotBeIncluded = 3
|
||||
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
|
||||
ast,
|
||||
execState.memory,
|
||||
[rangeStart, rangeStart, true]
|
||||
topLevelRange(rangeStart, rangeStart)
|
||||
)
|
||||
expect(variables).toEqual([
|
||||
{ key: 'baseThick', value: 1 },
|
||||
@ -87,7 +88,10 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
it('find a safe binaryExpression', () => {
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('100 + 100') + 2
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
const result = isNodeSafeToReplace(
|
||||
ast,
|
||||
topLevelRange(rangeStart, rangeStart)
|
||||
)
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
@ -100,7 +104,10 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
it('find a safe Identifier', () => {
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('abc')
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
const result = isNodeSafeToReplace(
|
||||
ast,
|
||||
topLevelRange(rangeStart, rangeStart)
|
||||
)
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('Identifier')
|
||||
@ -109,7 +116,10 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
it('find a safe CallExpression', () => {
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('def')
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
const result = isNodeSafeToReplace(
|
||||
ast,
|
||||
topLevelRange(rangeStart, rangeStart)
|
||||
)
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('CallExpression')
|
||||
@ -122,7 +132,7 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('ghi')
|
||||
const range: [number, number, boolean] = [rangeStart, rangeStart, true]
|
||||
const range = topLevelRange(rangeStart, rangeStart)
|
||||
const result = isNodeSafeToReplace(ast, range)
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(false)
|
||||
@ -132,7 +142,10 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
it('find an UNsafe Identifier, as it is a callee', () => {
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('ine([2.8,')
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
const result = isNodeSafeToReplace(
|
||||
ast,
|
||||
topLevelRange(rangeStart, rangeStart)
|
||||
)
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(false)
|
||||
expect(result.value?.type).toBe('CallExpression')
|
||||
@ -143,7 +156,10 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
it("find a safe BinaryExpression that's assigned to a variable", () => {
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('5 + 6') + 1
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
const result = isNodeSafeToReplace(
|
||||
ast,
|
||||
topLevelRange(rangeStart, rangeStart)
|
||||
)
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
@ -156,7 +172,10 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
it('find a safe BinaryExpression that has a CallExpression within', () => {
|
||||
const ast = assertParse(code)
|
||||
const rangeStart = code.indexOf('jkl') + 1
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
const result = isNodeSafeToReplace(
|
||||
ast,
|
||||
topLevelRange(rangeStart, rangeStart)
|
||||
)
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
@ -173,7 +192,10 @@ yo2 = hmm([identifierGuy + 5])`
|
||||
const ast = assertParse(code)
|
||||
|
||||
const rangeStart = code.indexOf('identifierGuy') + 1
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
||||
const result = isNodeSafeToReplace(
|
||||
ast,
|
||||
topLevelRange(rangeStart, rangeStart)
|
||||
)
|
||||
if (err(result)) throw result
|
||||
|
||||
expect(result.isSafe).toBe(true)
|
||||
@ -222,11 +244,10 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
||||
const ast = assertParse(code)
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [
|
||||
sourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
const result = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(sourceIndex, sourceIndex)
|
||||
)
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
@ -241,11 +262,10 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
||||
const ast = assertParse(code)
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [
|
||||
sourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
const result = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(sourceIndex, sourceIndex)
|
||||
)
|
||||
const expected = [
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
@ -257,18 +277,16 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
expect(result).toEqual(expected)
|
||||
// expect similar result for start of line
|
||||
const startSourceIndex = code.indexOf(searchLn)
|
||||
const startResult = getNodePathFromSourceRange(ast, [
|
||||
startSourceIndex,
|
||||
startSourceIndex,
|
||||
true,
|
||||
])
|
||||
const startResult = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(startSourceIndex, startSourceIndex)
|
||||
)
|
||||
expect(startResult).toEqual([...expected, ['callee', 'CallExpression']])
|
||||
// expect similar result when whole line is selected
|
||||
const selectWholeThing = getNodePathFromSourceRange(ast, [
|
||||
startSourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
const selectWholeThing = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(startSourceIndex, sourceIndex)
|
||||
)
|
||||
expect(selectWholeThing).toEqual(expected)
|
||||
})
|
||||
|
||||
@ -283,11 +301,10 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
const sourceIndex = code.indexOf(searchLn)
|
||||
const ast = assertParse(code)
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [
|
||||
sourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
const result = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(sourceIndex, sourceIndex)
|
||||
)
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[1, 'index'],
|
||||
@ -313,11 +330,10 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
const sourceIndex = code.indexOf(searchLn)
|
||||
const ast = assertParse(code)
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [
|
||||
sourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
const result = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(sourceIndex, sourceIndex)
|
||||
)
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[1, 'index'],
|
||||
@ -341,11 +357,10 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
const sourceIndex = code.indexOf(searchLn)
|
||||
const ast = assertParse(code)
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [
|
||||
sourceIndex,
|
||||
sourceIndex,
|
||||
true,
|
||||
])
|
||||
const result = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(sourceIndex, sourceIndex)
|
||||
)
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
@ -375,7 +390,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
const result = hasExtrudeSketch({
|
||||
ast,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
||||
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
|
||||
},
|
||||
programMemory: execState.memory,
|
||||
})
|
||||
@ -395,7 +410,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
const result = hasExtrudeSketch({
|
||||
ast,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
||||
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
|
||||
},
|
||||
programMemory: execState.memory,
|
||||
})
|
||||
@ -409,7 +424,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
||||
const result = hasExtrudeSketch({
|
||||
ast,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange([10, 11, true], ast),
|
||||
codeRef: codeRefFromRange(topLevelRange(10, 11), ast),
|
||||
},
|
||||
programMemory: execState.memory,
|
||||
})
|
||||
@ -431,11 +446,10 @@ describe('Testing findUsesOfTagInPipe', () => {
|
||||
const lineOfInterest = `198.85], %, $seg01`
|
||||
const characterIndex =
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
const pathToNode = getNodePathFromSourceRange(ast, [
|
||||
characterIndex,
|
||||
characterIndex,
|
||||
true,
|
||||
])
|
||||
const pathToNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(characterIndex, characterIndex)
|
||||
)
|
||||
const result = findUsesOfTagInPipe(ast, pathToNode)
|
||||
expect(result).toHaveLength(2)
|
||||
result.forEach((range) => {
|
||||
@ -448,11 +462,10 @@ describe('Testing findUsesOfTagInPipe', () => {
|
||||
const lineOfInterest = `line([306.21, 198.82], %)`
|
||||
const characterIndex =
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
const pathToNode = getNodePathFromSourceRange(ast, [
|
||||
characterIndex,
|
||||
characterIndex,
|
||||
true,
|
||||
])
|
||||
const pathToNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(characterIndex, characterIndex)
|
||||
)
|
||||
const result = findUsesOfTagInPipe(ast, pathToNode)
|
||||
expect(result).toHaveLength(0)
|
||||
})
|
||||
@ -498,7 +511,10 @@ sketch003 = startSketchOn(extrude001, 'END')
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
const extruded = hasSketchPipeBeenExtruded(
|
||||
{
|
||||
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
|
||||
codeRef: codeRefFromRange(
|
||||
topLevelRange(characterIndex, characterIndex),
|
||||
ast
|
||||
),
|
||||
},
|
||||
ast
|
||||
)
|
||||
@ -511,7 +527,10 @@ sketch003 = startSketchOn(extrude001, 'END')
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
const extruded = hasSketchPipeBeenExtruded(
|
||||
{
|
||||
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
|
||||
codeRef: codeRefFromRange(
|
||||
topLevelRange(characterIndex, characterIndex),
|
||||
ast
|
||||
),
|
||||
},
|
||||
ast
|
||||
)
|
||||
@ -524,7 +543,10 @@ sketch003 = startSketchOn(extrude001, 'END')
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
const extruded = hasSketchPipeBeenExtruded(
|
||||
{
|
||||
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
|
||||
codeRef: codeRefFromRange(
|
||||
topLevelRange(characterIndex, characterIndex),
|
||||
ast
|
||||
),
|
||||
},
|
||||
ast
|
||||
)
|
||||
@ -651,11 +673,10 @@ myNestedVar = [
|
||||
})
|
||||
|
||||
const literalIndex = code.indexOf(literalOfInterest)
|
||||
const pathToNode2 = getNodePathFromSourceRange(ast, [
|
||||
literalIndex + 2,
|
||||
literalIndex + 2,
|
||||
true,
|
||||
])
|
||||
const pathToNode2 = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(literalIndex + 2, literalIndex + 2)
|
||||
)
|
||||
expect(pathToNode).toEqual(pathToNode2)
|
||||
})
|
||||
})
|
||||
|
@ -2,6 +2,7 @@ import { ToolTip } from 'lang/langHelpers'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import {
|
||||
ArrayExpression,
|
||||
ArtifactGraph,
|
||||
BinaryExpression,
|
||||
CallExpression,
|
||||
Expr,
|
||||
@ -16,8 +17,8 @@ import {
|
||||
sketchFromKclValue,
|
||||
sketchFromKclValueOptional,
|
||||
SourceRange,
|
||||
sourceRangeFromRust,
|
||||
SyntaxType,
|
||||
topLevelRange,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
} from './wasm'
|
||||
@ -32,7 +33,7 @@ import {
|
||||
import { err, Reason } from 'lib/trap'
|
||||
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { ArtifactGraph, codeRefFromRange } from './std/artifactGraph'
|
||||
import { codeRefFromRange } from './std/artifactGraph'
|
||||
|
||||
/**
|
||||
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
|
||||
@ -819,7 +820,7 @@ export function isLinesParallelAndConstrained(
|
||||
return {
|
||||
isParallelAndConstrained,
|
||||
selection: {
|
||||
codeRef: codeRefFromRange(sourceRangeFromRust(prevSourceRange), ast),
|
||||
codeRef: codeRefFromRange(prevSourceRange, ast),
|
||||
artifact: artifactGraph.get(prevSegment.__geoMeta.id),
|
||||
},
|
||||
}
|
||||
@ -937,7 +938,7 @@ export function findUsesOfTagInPipe(
|
||||
const tagArgValue =
|
||||
tagArg.type === 'TagDeclarator' ? String(tagArg.value) : tagArg.name
|
||||
if (tagArgValue === tag)
|
||||
dependentRanges.push([node.start, node.end, true])
|
||||
dependentRanges.push(topLevelRange(node.start, node.end))
|
||||
},
|
||||
})
|
||||
return dependentRanges
|
||||
|
@ -1,559 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`testing createArtifactGraph > code with an extrusion, fillet and sketch of face: > snapshot of the artifactGraph 1`] = `
|
||||
Map {
|
||||
"UUID-0" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
12,
|
||||
31,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"id": "UUID",
|
||||
"pathIds": [
|
||||
"UUID",
|
||||
],
|
||||
"type": "plane",
|
||||
},
|
||||
"UUID-1" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
37,
|
||||
64,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"id": "UUID",
|
||||
"planeId": "UUID",
|
||||
"segIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"solid2dId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "path",
|
||||
},
|
||||
"UUID-2" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
70,
|
||||
86,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-3" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
92,
|
||||
119,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"edgeCutId": "UUID",
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-4" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
125,
|
||||
150,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-5" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
156,
|
||||
203,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-6" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
209,
|
||||
217,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"edgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-7" => {
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"type": "solid2D",
|
||||
},
|
||||
"UUID-8" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"subType": "extrusion",
|
||||
"surfaceIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"type": "sweep",
|
||||
},
|
||||
"UUID-9" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-10" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathIds": [
|
||||
"UUID",
|
||||
],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-11" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-12" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-13" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathIds": [],
|
||||
"subType": "start",
|
||||
"sweepId": "UUID",
|
||||
"type": "cap",
|
||||
},
|
||||
"UUID-14" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathIds": [],
|
||||
"subType": "end",
|
||||
"sweepId": "UUID",
|
||||
"type": "cap",
|
||||
},
|
||||
"UUID-15" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-16" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-17" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-18" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-19" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-20" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-21" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-22" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-23" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
260,
|
||||
299,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"consumedEdgeId": "UUID",
|
||||
"edgeIds": [],
|
||||
"id": "UUID",
|
||||
"subType": "fillet",
|
||||
"type": "edgeCut",
|
||||
},
|
||||
"UUID-24" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
350,
|
||||
377,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"id": "UUID",
|
||||
"planeId": "UUID",
|
||||
"segIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"solid2dId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "path",
|
||||
},
|
||||
"UUID-25" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
383,
|
||||
398,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-26" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
404,
|
||||
420,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-27" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
426,
|
||||
473,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"surfaceId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-28" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
479,
|
||||
487,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"edgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"type": "segment",
|
||||
},
|
||||
"UUID-29" => {
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"type": "solid2D",
|
||||
},
|
||||
"UUID-30" => {
|
||||
"codeRef": {
|
||||
"pathToNode": [
|
||||
[
|
||||
"body",
|
||||
"",
|
||||
],
|
||||
],
|
||||
"range": [
|
||||
501,
|
||||
522,
|
||||
true,
|
||||
],
|
||||
},
|
||||
"edgeIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"id": "UUID",
|
||||
"pathId": "UUID",
|
||||
"subType": "extrusion",
|
||||
"surfaceIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"type": "sweep",
|
||||
},
|
||||
"UUID-31" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-32" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-33" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-34" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"id": "UUID",
|
||||
"pathIds": [],
|
||||
"subType": "end",
|
||||
"sweepId": "UUID",
|
||||
"type": "cap",
|
||||
},
|
||||
"UUID-35" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-36" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-37" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-38" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-39" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-40" => {
|
||||
"id": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
}
|
||||
`;
|
@ -1,960 +0,0 @@
|
||||
import {
|
||||
makeDefaultPlanes,
|
||||
assertParse,
|
||||
initPromise,
|
||||
Program,
|
||||
ArtifactCommand,
|
||||
ExecState,
|
||||
} from 'lang/wasm'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import {
|
||||
ResponseMap,
|
||||
createArtifactGraph,
|
||||
filterArtifacts,
|
||||
expandPlane,
|
||||
expandPath,
|
||||
expandSweep,
|
||||
ArtifactGraph,
|
||||
expandSegment,
|
||||
getArtifactsToUpdate,
|
||||
} from './artifactGraph'
|
||||
import { err } from 'lib/trap'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { VITE_KC_DEV_TOKEN } from 'env'
|
||||
import fsp from 'fs/promises'
|
||||
import fs from 'fs'
|
||||
import { chromium } from 'playwright'
|
||||
import * as d3 from 'd3-force'
|
||||
import path from 'path'
|
||||
import pixelmatch from 'pixelmatch'
|
||||
import { PNG } from 'pngjs'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
|
||||
/*
|
||||
Note this is an integration test, these tests connect to our real dev server and make websocket commands.
|
||||
It's needed for testing the artifactGraph, as it is tied to the websocket commands.
|
||||
*/
|
||||
|
||||
const pathStart = 'src/lang/std/artifactMapCache'
|
||||
const fullPath = `${pathStart}/artifactMapCache.json`
|
||||
|
||||
const exampleCode1 = `sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10.55, 0], %, $seg01)
|
||||
|> line([0, -10], %, $seg02)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(-10, sketch001)
|
||||
|> fillet({ radius: 5, tags: [seg01] }, %)
|
||||
sketch002 = startSketchOn(extrude001, seg02)
|
||||
|> startProfileAt([-2, -6], %)
|
||||
|> line([2, 3], %)
|
||||
|> line([2, -3], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude002 = extrude(5, sketch002)
|
||||
`
|
||||
|
||||
const exampleCodeNo3D = `sketch003 = startSketchOn('YZ')
|
||||
|> startProfileAt([5.82, 0], %)
|
||||
|> angledLine([180, 11.54], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001) - 90,
|
||||
8.21
|
||||
], %, $rectangleSegmentB001)
|
||||
|> angledLine([
|
||||
segAng(rectangleSegmentA001),
|
||||
-segLen(rectangleSegmentA001)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
sketch004 = startSketchOn('-XZ')
|
||||
|> startProfileAt([0, 14.36], %)
|
||||
|> line([15.49, 0.05], %)
|
||||
|> tangentialArcTo([0, 0], %)
|
||||
|> tangentialArcTo([-6.8, 8.17], %)
|
||||
`
|
||||
|
||||
const sketchOnFaceOnFaceEtc = `sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([4, 8], %)
|
||||
|> line([5, -8], %, $seg01)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(6, sketch001)
|
||||
sketch002 = startSketchOn(extrude001, seg01)
|
||||
|> startProfileAt([-0.5, 0.5], %)
|
||||
|> line([2, 5], %)
|
||||
|> line([2, -5], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude002 = extrude(5, sketch002)
|
||||
sketch003 = startSketchOn(extrude002, 'END')
|
||||
|> startProfileAt([1, 1.5], %)
|
||||
|> line([0.5, 2], %, $seg02)
|
||||
|> line([1, -2], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude003 = extrude(4, sketch003)
|
||||
sketch004 = startSketchOn(extrude003, seg02)
|
||||
|> startProfileAt([-3, 14], %)
|
||||
|> line([0.5, 1], %)
|
||||
|> line([0.5, -2], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude004 = extrude(3, sketch004)
|
||||
`
|
||||
const exampleCodeOffsetPlanes = `
|
||||
offsetPlane001 = offsetPlane("XY", 20)
|
||||
offsetPlane002 = offsetPlane("XZ", -50)
|
||||
offsetPlane003 = offsetPlane("YZ", 10)
|
||||
|
||||
sketch002 = startSketchOn(offsetPlane001)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([6.78, 15.01], %)
|
||||
`
|
||||
|
||||
// add more code snippets here and use `getCommands` to get the artifactCommands and responseMap for more tests
|
||||
const codeToWriteCacheFor = {
|
||||
exampleCode1,
|
||||
sketchOnFaceOnFaceEtc,
|
||||
exampleCodeNo3D,
|
||||
exampleCodeOffsetPlanes,
|
||||
} as const
|
||||
|
||||
type CodeKey = keyof typeof codeToWriteCacheFor
|
||||
|
||||
type CacheShape = {
|
||||
[key in CodeKey]: {
|
||||
artifactCommands: ArtifactCommand[]
|
||||
responseMap: ResponseMap
|
||||
execStateArtifacts: ExecState['artifacts']
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
|
||||
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
|
||||
await new Promise((resolve) => {
|
||||
engineCommandManager.start({
|
||||
// disableWebRTC: true,
|
||||
token: VITE_KC_DEV_TOKEN,
|
||||
// there does seem to be a minimum resolution, not sure what it is but 256 works ok.
|
||||
width: 256,
|
||||
height: 256,
|
||||
makeDefaultPlanes: () => makeDefaultPlanes(engineCommandManager),
|
||||
setMediaStream: () => {},
|
||||
setIsStreamReady: () => {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
callbackOnEngineLiteConnect: async () => {
|
||||
const cacheEntries = Object.entries(codeToWriteCacheFor) as [
|
||||
CodeKey,
|
||||
string
|
||||
][]
|
||||
const cacheToWriteToFileTemp: Partial<CacheShape> = {}
|
||||
for (const [codeKey, code] of cacheEntries) {
|
||||
const ast = assertParse(code)
|
||||
await kclManager.executeAst({ ast })
|
||||
|
||||
cacheToWriteToFileTemp[codeKey] = {
|
||||
artifactCommands: kclManager.execState.artifactCommands,
|
||||
responseMap: engineCommandManager.responseMap,
|
||||
execStateArtifacts: kclManager.execState.artifacts,
|
||||
}
|
||||
}
|
||||
const cache = JSON.stringify(cacheToWriteToFileTemp)
|
||||
|
||||
await fsp.mkdir(pathStart, { recursive: true })
|
||||
await fsp.writeFile(fullPath, cache)
|
||||
resolve(true)
|
||||
},
|
||||
})
|
||||
})
|
||||
}, 20_000)
|
||||
|
||||
afterAll(() => {
|
||||
engineCommandManager.tearDown()
|
||||
})
|
||||
|
||||
describe('testing createArtifactGraph', () => {
|
||||
describe('code with offset planes and a sketch:', () => {
|
||||
let ast: Node<Program>
|
||||
let theMap: ReturnType<typeof createArtifactGraph>
|
||||
|
||||
it('setup', () => {
|
||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
||||
const {
|
||||
artifactCommands,
|
||||
responseMap,
|
||||
ast: _ast,
|
||||
execStateArtifacts,
|
||||
} = getCommands('exampleCodeOffsetPlanes')
|
||||
ast = _ast
|
||||
theMap = createArtifactGraph({
|
||||
artifactCommands,
|
||||
responseMap,
|
||||
ast,
|
||||
execStateArtifacts,
|
||||
})
|
||||
})
|
||||
|
||||
it(`there should be one sketch`, () => {
|
||||
const sketches = [...filterArtifacts({ types: ['path'] }, theMap)].map(
|
||||
(path) => expandPath(path[1], theMap)
|
||||
)
|
||||
expect(sketches).toHaveLength(1)
|
||||
sketches.forEach((path) => {
|
||||
if (err(path)) throw path
|
||||
expect(path.type).toBe('path')
|
||||
})
|
||||
})
|
||||
|
||||
it(`there should be three offsetPlanes`, () => {
|
||||
const offsetPlanes = [
|
||||
...filterArtifacts({ types: ['plane'] }, theMap),
|
||||
].map((plane) => expandPlane(plane[1], theMap))
|
||||
expect(offsetPlanes).toHaveLength(3)
|
||||
offsetPlanes.forEach((path) => {
|
||||
expect(path.type).toBe('plane')
|
||||
})
|
||||
})
|
||||
|
||||
it(`Only one offset plane should have a path`, () => {
|
||||
const offsetPlanes = [
|
||||
...filterArtifacts({ types: ['plane'] }, theMap),
|
||||
].map((plane) => expandPlane(plane[1], theMap))
|
||||
const offsetPlaneWithPaths = offsetPlanes.filter(
|
||||
(plane) => plane.paths.length
|
||||
)
|
||||
expect(offsetPlaneWithPaths).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
describe('code with an extrusion, fillet and sketch of face:', () => {
|
||||
let ast: Node<Program>
|
||||
let theMap: ReturnType<typeof createArtifactGraph>
|
||||
it('setup', () => {
|
||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
||||
const {
|
||||
artifactCommands,
|
||||
responseMap,
|
||||
ast: _ast,
|
||||
execStateArtifacts,
|
||||
} = getCommands('exampleCode1')
|
||||
ast = _ast
|
||||
theMap = createArtifactGraph({
|
||||
artifactCommands,
|
||||
responseMap,
|
||||
ast,
|
||||
execStateArtifacts,
|
||||
})
|
||||
})
|
||||
|
||||
it('there should be two planes for the extrusion and the sketch on face', () => {
|
||||
const planes = [...filterArtifacts({ types: ['plane'] }, theMap)].map(
|
||||
(plane) => expandPlane(plane[1], theMap)
|
||||
)
|
||||
expect(planes).toHaveLength(1)
|
||||
planes.forEach((path) => {
|
||||
expect(path.type).toBe('plane')
|
||||
})
|
||||
})
|
||||
it('there should be two paths for the extrusion and the sketch on face', () => {
|
||||
const paths = [...filterArtifacts({ types: ['path'] }, theMap)].map(
|
||||
(path) => expandPath(path[1], theMap)
|
||||
)
|
||||
expect(paths).toHaveLength(2)
|
||||
paths.forEach((path) => {
|
||||
if (err(path)) throw path
|
||||
expect(path.type).toBe('path')
|
||||
})
|
||||
})
|
||||
|
||||
it('there should be two extrusions, for the original and the sketchOnFace, the first extrusion should have 6 sides of the cube', () => {
|
||||
const extrusions = [...filterArtifacts({ types: ['sweep'] }, theMap)].map(
|
||||
(extrusion) => expandSweep(extrusion[1], theMap)
|
||||
)
|
||||
expect(extrusions).toHaveLength(2)
|
||||
extrusions.forEach((extrusion, index) => {
|
||||
if (err(extrusion)) throw extrusion
|
||||
expect(extrusion.type).toBe('sweep')
|
||||
const firstExtrusionIsACubeIE6Sides = 6
|
||||
// Each face of the triangular prism (5), but without the bottom cap.
|
||||
// The engine doesn't generate that.
|
||||
const secondExtrusionIsATriangularPrism = 4
|
||||
expect(extrusion.surfaces.length).toBe(
|
||||
!index
|
||||
? firstExtrusionIsACubeIE6Sides
|
||||
: secondExtrusionIsATriangularPrism
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('there should be 5 + 4 segments, 4 (+close) from the first extrusion and 3 (+close) from the second', () => {
|
||||
const segments = [...filterArtifacts({ types: ['segment'] }, theMap)].map(
|
||||
(segment) => expandSegment(segment[1], theMap)
|
||||
)
|
||||
expect(segments).toHaveLength(9)
|
||||
})
|
||||
|
||||
it('snapshot of the artifactGraph', () => {
|
||||
const stableMap = new Map(
|
||||
[...theMap].map(([, artifact], index): [string, any] => {
|
||||
const stableValue: any = {}
|
||||
Object.entries(artifact).forEach(([propName, value]) => {
|
||||
if (
|
||||
propName === 'type' ||
|
||||
propName === 'codeRef' ||
|
||||
propName === 'subType'
|
||||
) {
|
||||
stableValue[propName] = value
|
||||
return
|
||||
}
|
||||
if (Array.isArray(value))
|
||||
stableValue[propName] = value.map(() => 'UUID')
|
||||
if (typeof value === 'string' && value)
|
||||
stableValue[propName] = 'UUID'
|
||||
})
|
||||
return [`UUID-${index}`, stableValue]
|
||||
})
|
||||
)
|
||||
expect(stableMap).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('screenshot graph', async () => {
|
||||
// Ostensibly this takes a screen shot of the graph of the artifactGraph
|
||||
// but it's it also tests that all of the id links are correct because if one
|
||||
// of the edges refers to a non-existent node, the graph will throw.
|
||||
// further more we can check that each edge is bi-directional, if it's not
|
||||
// by checking the arrow heads going both ways, on the graph.
|
||||
await GraphTheGraph(theMap, 2000, 2000, 'exampleCode1.png')
|
||||
}, 20000)
|
||||
})
|
||||
|
||||
describe(`code with sketches but no extrusions or other 3D elements`, () => {
|
||||
let ast: Node<Program>
|
||||
let theMap: ReturnType<typeof createArtifactGraph>
|
||||
it(`setup`, () => {
|
||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
||||
const {
|
||||
artifactCommands,
|
||||
responseMap,
|
||||
ast: _ast,
|
||||
execStateArtifacts,
|
||||
} = getCommands('exampleCodeNo3D')
|
||||
ast = _ast
|
||||
theMap = createArtifactGraph({
|
||||
artifactCommands,
|
||||
responseMap,
|
||||
ast,
|
||||
execStateArtifacts,
|
||||
})
|
||||
})
|
||||
|
||||
it('there should be two planes, one for each sketch path', () => {
|
||||
const planes = [...filterArtifacts({ types: ['plane'] }, theMap)].map(
|
||||
(plane) => expandPlane(plane[1], theMap)
|
||||
)
|
||||
expect(planes).toHaveLength(2)
|
||||
planes.forEach((path) => {
|
||||
expect(path.type).toBe('plane')
|
||||
})
|
||||
})
|
||||
it('there should be two paths, one on each plane', () => {
|
||||
const paths = [...filterArtifacts({ types: ['path'] }, theMap)].map(
|
||||
(path) => expandPath(path[1], theMap)
|
||||
)
|
||||
expect(paths).toHaveLength(2)
|
||||
paths.forEach((path) => {
|
||||
if (err(path)) throw path
|
||||
expect(path.type).toBe('path')
|
||||
})
|
||||
})
|
||||
|
||||
it(`there should be 1 solid2D, just for the first closed path`, () => {
|
||||
const solid2Ds = [...filterArtifacts({ types: ['solid2D'] }, theMap)]
|
||||
expect(solid2Ds).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('there should be no extrusions', () => {
|
||||
const extrusions = [...filterArtifacts({ types: ['sweep'] }, theMap)].map(
|
||||
(extrusion) => expandSweep(extrusion[1], theMap)
|
||||
)
|
||||
expect(extrusions).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('there should be 8 segments, 4 + 1 (close) from the first sketch and 3 from the second', () => {
|
||||
const segments = [...filterArtifacts({ types: ['segment'] }, theMap)].map(
|
||||
(segment) => expandSegment(segment[1], theMap)
|
||||
)
|
||||
expect(segments).toHaveLength(8)
|
||||
})
|
||||
|
||||
it('screenshot graph', async () => {
|
||||
// Ostensibly this takes a screen shot of the graph of the artifactGraph
|
||||
// but it's it also tests that all of the id links are correct because if one
|
||||
// of the edges refers to a non-existent node, the graph will throw.
|
||||
// further more we can check that each edge is bi-directional, if it's not
|
||||
// by checking the arrow heads going both ways, on the graph.
|
||||
await GraphTheGraph(theMap, 2000, 2000, 'exampleCodeNo3D.png')
|
||||
}, 20000)
|
||||
})
|
||||
})
|
||||
|
||||
describe('capture graph of sketchOnFaceOnFace...', () => {
|
||||
describe('code with an extrusion, fillet and sketch of face:', () => {
|
||||
let ast: Node<Program>
|
||||
let theMap: ReturnType<typeof createArtifactGraph>
|
||||
it('setup', async () => {
|
||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
||||
const {
|
||||
artifactCommands,
|
||||
responseMap,
|
||||
ast: _ast,
|
||||
execStateArtifacts,
|
||||
} = getCommands('sketchOnFaceOnFaceEtc')
|
||||
ast = _ast
|
||||
theMap = createArtifactGraph({
|
||||
artifactCommands,
|
||||
responseMap,
|
||||
ast,
|
||||
execStateArtifacts,
|
||||
})
|
||||
|
||||
// Ostensibly this takes a screen shot of the graph of the artifactGraph
|
||||
// but it's it also tests that all of the id links are correct because if one
|
||||
// of the edges refers to a non-existent node, the graph will throw.
|
||||
// further more we can check that each edge is bi-directional, if it's not
|
||||
// by checking the arrow heads going both ways, on the graph.
|
||||
await GraphTheGraph(theMap, 3000, 3000, 'sketchOnFaceOnFaceEtc.png')
|
||||
}, 20000)
|
||||
})
|
||||
})
|
||||
|
||||
function getCommands(
|
||||
codeKey: CodeKey
|
||||
): CacheShape[CodeKey] & { ast: Node<Program> } {
|
||||
const ast = assertParse(codeKey)
|
||||
const file = fs.readFileSync(fullPath, 'utf-8')
|
||||
const parsed: CacheShape = JSON.parse(file)
|
||||
// these either already exist from the last run, or were created in
|
||||
const artifactCommands = parsed[codeKey].artifactCommands
|
||||
const responseMap = parsed[codeKey].responseMap
|
||||
const execStateArtifacts = parsed[codeKey].execStateArtifacts
|
||||
return {
|
||||
artifactCommands,
|
||||
responseMap,
|
||||
ast,
|
||||
execStateArtifacts,
|
||||
}
|
||||
}
|
||||
|
||||
async function GraphTheGraph(
|
||||
theMap: ArtifactGraph,
|
||||
sizeX: number,
|
||||
sizeY: number,
|
||||
imageName: string
|
||||
) {
|
||||
const nodes: Array<{ id: string; label: string }> = []
|
||||
const edges: Array<{ source: string; target: string; label: string }> = []
|
||||
let index = 0
|
||||
for (const [commandId, artifact] of theMap) {
|
||||
nodes.push({
|
||||
id: commandId,
|
||||
label: `${artifact.type}-${index++}`,
|
||||
})
|
||||
Object.entries(artifact).forEach(([propName, value]) => {
|
||||
if (
|
||||
propName === 'type' ||
|
||||
propName === 'codeRef' ||
|
||||
propName === 'subType' ||
|
||||
propName === 'id'
|
||||
)
|
||||
return
|
||||
if (Array.isArray(value))
|
||||
value.forEach((v) => {
|
||||
v && edges.push({ source: commandId, target: v, label: propName })
|
||||
})
|
||||
if (typeof value === 'string' && value)
|
||||
edges.push({ source: commandId, target: value, label: propName })
|
||||
})
|
||||
}
|
||||
|
||||
// Create a force simulation to calculate node positions
|
||||
const simulation = d3
|
||||
.forceSimulation(nodes as any)
|
||||
.force(
|
||||
'link',
|
||||
d3
|
||||
.forceLink(edges)
|
||||
.id((d: any) => d.id)
|
||||
.distance(100)
|
||||
)
|
||||
.force('charge', d3.forceManyBody().strength(-300))
|
||||
.force('center', d3.forceCenter(300, 200))
|
||||
.stop()
|
||||
|
||||
// Run the simulation
|
||||
for (let i = 0; i < 300; ++i) simulation.tick()
|
||||
|
||||
// Create traces for Plotly
|
||||
const nodeTrace = {
|
||||
x: nodes.map((node: any) => node.x),
|
||||
y: nodes.map((node: any) => node.y),
|
||||
text: nodes.map((node) => node.label), // Use the custom label
|
||||
mode: 'markers+text',
|
||||
type: 'scatter',
|
||||
marker: { size: 20, color: 'gray' }, // Nodes in gray
|
||||
textfont: { size: 14, color: 'black' }, // Labels in black
|
||||
textposition: 'top center', // Position text on top
|
||||
}
|
||||
|
||||
const edgeTrace = {
|
||||
x: [],
|
||||
y: [],
|
||||
mode: 'lines',
|
||||
type: 'scatter',
|
||||
line: { width: 2, color: 'lightgray' }, // Edges in light gray
|
||||
}
|
||||
|
||||
const annotations: any[] = []
|
||||
|
||||
edges.forEach((edge) => {
|
||||
const sourceNode = nodes.find(
|
||||
(node: any) => node.id === (edge as any).source.id
|
||||
)
|
||||
const targetNode = nodes.find(
|
||||
(node: any) => node.id === (edge as any).target.id
|
||||
)
|
||||
|
||||
// Check if nodes are found
|
||||
if (!sourceNode || !targetNode) {
|
||||
throw new Error(
|
||||
// @ts-ignore
|
||||
`Node not found: ${!sourceNode ? edge.source.id : edge.target.id}`
|
||||
)
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
edgeTrace.x.push(sourceNode.x, targetNode.x, null)
|
||||
// @ts-ignore
|
||||
edgeTrace.y.push(sourceNode.y, targetNode.y, null)
|
||||
|
||||
// Calculate offset for arrowhead
|
||||
const offsetFactor = 0.9 // Adjust this factor to control the offset distance
|
||||
// @ts-ignore
|
||||
const offsetX = (targetNode.x - sourceNode.x) * offsetFactor
|
||||
// @ts-ignore
|
||||
const offsetY = (targetNode.y - sourceNode.y) * offsetFactor
|
||||
|
||||
// Add arrowhead annotation with offset
|
||||
annotations.push({
|
||||
// @ts-ignore
|
||||
ax: sourceNode.x,
|
||||
// @ts-ignore
|
||||
ay: sourceNode.y,
|
||||
// @ts-ignore
|
||||
x: targetNode.x - offsetX,
|
||||
// @ts-ignore
|
||||
y: targetNode.y - offsetY,
|
||||
xref: 'x',
|
||||
yref: 'y',
|
||||
axref: 'x',
|
||||
ayref: 'y',
|
||||
showarrow: true,
|
||||
arrowhead: 2,
|
||||
arrowsize: 1,
|
||||
arrowwidth: 2,
|
||||
arrowcolor: 'darkgray', // Arrowheads in dark gray
|
||||
})
|
||||
|
||||
// Add edge label annotation closer to the edge tail (25% of the length)
|
||||
// @ts-ignore
|
||||
const labelX = sourceNode.x * 0.75 + targetNode.x * 0.25
|
||||
// @ts-ignore
|
||||
const labelY = sourceNode.y * 0.75 + targetNode.y * 0.25
|
||||
annotations.push({
|
||||
x: labelX,
|
||||
y: labelY,
|
||||
xref: 'x',
|
||||
yref: 'y',
|
||||
text: edge.label,
|
||||
showarrow: false,
|
||||
font: { size: 12, color: 'black' }, // Edge labels in black
|
||||
align: 'center',
|
||||
})
|
||||
})
|
||||
|
||||
const data = [edgeTrace, nodeTrace]
|
||||
|
||||
const layout = {
|
||||
// title: 'Force-Directed Graph with Nodes and Edges',
|
||||
xaxis: { showgrid: false, zeroline: false, showticklabels: false },
|
||||
yaxis: { showgrid: false, zeroline: false, showticklabels: false },
|
||||
showlegend: false,
|
||||
annotations: annotations,
|
||||
}
|
||||
|
||||
// Export to PNG using Playwright
|
||||
const browser = await chromium.launch()
|
||||
const page = await browser.newPage()
|
||||
await page.setContent(`
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="plotly-graph" style="width:${sizeX}px;height:${sizeY}px;"></div>
|
||||
<script>
|
||||
Plotly.newPlot('plotly-graph', ${JSON.stringify(
|
||||
data
|
||||
)}, ${JSON.stringify(layout)})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
await page.waitForSelector('#plotly-graph')
|
||||
const element = await page.$('#plotly-graph')
|
||||
|
||||
// @ts-ignore
|
||||
await element.screenshot({
|
||||
path: `./e2e/playwright/temp3.png`,
|
||||
})
|
||||
|
||||
await browser.close()
|
||||
|
||||
const originalImgPath = path.resolve(
|
||||
`./src/lang/std/artifactMapGraphs/${imageName}`
|
||||
)
|
||||
// chop the top 30 pixels off the image
|
||||
const originalImgExists = fs.existsSync(originalImgPath)
|
||||
const originalImg = originalImgExists
|
||||
? PNG.sync.read(fs.readFileSync(originalImgPath))
|
||||
: null
|
||||
// const img1Data = new Uint8Array(img1.data)
|
||||
// const img1DataChopped = img1Data.slice(30 * img1.width * 4)
|
||||
// img1.data = Buffer.from(img1DataChopped)
|
||||
|
||||
const newImagePath = path.resolve('./e2e/playwright/temp3.png')
|
||||
const newImage = PNG.sync.read(fs.readFileSync(newImagePath))
|
||||
const newImageData = new Uint8Array(newImage.data)
|
||||
const newImageDataChopped = newImageData.slice(30 * newImage.width * 4)
|
||||
newImage.data = Buffer.from(newImageDataChopped)
|
||||
|
||||
const { width, height } = originalImg ?? newImage
|
||||
const diff = new PNG({ width, height })
|
||||
|
||||
const imageSizeDifferent = originalImg?.data.length !== newImage.data.length
|
||||
let numDiffPixels = 0
|
||||
if (!imageSizeDifferent) {
|
||||
numDiffPixels = pixelmatch(
|
||||
originalImg.data,
|
||||
newImage.data,
|
||||
diff.data,
|
||||
width,
|
||||
height,
|
||||
{
|
||||
threshold: 0.1,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (numDiffPixels > 10 || imageSizeDifferent) {
|
||||
console.warn('numDiffPixels', numDiffPixels)
|
||||
// write file out to final place
|
||||
fs.writeFileSync(
|
||||
`src/lang/std/artifactMapGraphs/${imageName}`,
|
||||
PNG.sync.write(newImage)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
describe('testing getArtifactsToUpdate', () => {
|
||||
it('should return an array of artifacts to update', () => {
|
||||
const { artifactCommands, responseMap, ast, execStateArtifacts } =
|
||||
getCommands('exampleCode1')
|
||||
const map = createArtifactGraph({
|
||||
artifactCommands,
|
||||
responseMap,
|
||||
ast,
|
||||
execStateArtifacts,
|
||||
})
|
||||
const getArtifact = (id: string) => map.get(id)
|
||||
const currentPlaneId = 'UUID-1'
|
||||
const getUpdateObjects = (type: Models['ModelingCmd_type']['type']) => {
|
||||
const artifactCommand = artifactCommands.find(
|
||||
(a) => a.command.type === type
|
||||
)
|
||||
if (!artifactCommand) {
|
||||
throw new Error(`No artifactCommand found for ${type}`)
|
||||
}
|
||||
const artifactsToUpdate = getArtifactsToUpdate({
|
||||
artifactCommand,
|
||||
responseMap,
|
||||
getArtifact,
|
||||
currentPlaneId,
|
||||
ast,
|
||||
execStateArtifacts,
|
||||
})
|
||||
return artifactsToUpdate.map(({ artifact }) => artifact)
|
||||
}
|
||||
expect(getUpdateObjects('start_path')).toEqual([
|
||||
{
|
||||
type: 'path',
|
||||
segIds: [],
|
||||
id: expect.any(String),
|
||||
planeId: 'UUID-1',
|
||||
sweepId: undefined,
|
||||
codeRef: {
|
||||
pathToNode: [['body', '']],
|
||||
range: [37, 64, true],
|
||||
},
|
||||
},
|
||||
])
|
||||
expect(getUpdateObjects('extrude')).toEqual([
|
||||
{
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
id: expect.any(String),
|
||||
surfaceIds: [],
|
||||
edgeIds: [],
|
||||
codeRef: {
|
||||
range: [231, 254, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'path',
|
||||
id: expect.any(String),
|
||||
segIds: expect.any(Array),
|
||||
planeId: expect.any(String),
|
||||
sweepId: expect.any(String),
|
||||
codeRef: {
|
||||
range: [37, 64, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
solid2dId: expect.any(String),
|
||||
},
|
||||
])
|
||||
expect(getUpdateObjects('extend_path')).toEqual([
|
||||
{
|
||||
type: 'segment',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceId: undefined,
|
||||
edgeIds: [],
|
||||
codeRef: {
|
||||
range: [70, 86, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'path',
|
||||
id: expect.any(String),
|
||||
segIds: expect.any(Array),
|
||||
planeId: expect.any(String),
|
||||
sweepId: expect.any(String),
|
||||
codeRef: {
|
||||
range: [37, 64, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
solid2dId: expect.any(String),
|
||||
},
|
||||
])
|
||||
expect(getUpdateObjects('solid3d_fillet_edge')).toEqual([
|
||||
{
|
||||
type: 'edgeCut',
|
||||
subType: 'fillet',
|
||||
id: expect.any(String),
|
||||
consumedEdgeId: expect.any(String),
|
||||
edgeIds: [],
|
||||
surfaceId: undefined,
|
||||
codeRef: {
|
||||
range: [260, 299, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'segment',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceId: expect.any(String),
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [92, 119, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
edgeCutId: expect.any(String),
|
||||
},
|
||||
])
|
||||
expect(getUpdateObjects('solid3d_get_extrusion_face_info')).toEqual([
|
||||
{
|
||||
type: 'wall',
|
||||
id: expect.any(String),
|
||||
segId: expect.any(String),
|
||||
edgeCutEdgeIds: [],
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
type: 'segment',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceId: expect.any(String),
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [156, 203, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [231, 254, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'wall',
|
||||
id: expect.any(String),
|
||||
segId: expect.any(String),
|
||||
edgeCutEdgeIds: [],
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
type: 'segment',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceId: expect.any(String),
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [125, 150, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [231, 254, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'wall',
|
||||
id: expect.any(String),
|
||||
segId: expect.any(String),
|
||||
edgeCutEdgeIds: [],
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
type: 'segment',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceId: expect.any(String),
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [92, 119, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
edgeCutId: expect.any(String),
|
||||
},
|
||||
{
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [231, 254, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'wall',
|
||||
id: expect.any(String),
|
||||
segId: expect.any(String),
|
||||
edgeCutEdgeIds: [],
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
type: 'segment',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceId: expect.any(String),
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [70, 86, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [231, 254, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'cap',
|
||||
subType: 'start',
|
||||
id: expect.any(String),
|
||||
edgeCutEdgeIds: [],
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [231, 254, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'cap',
|
||||
subType: 'end',
|
||||
id: expect.any(String),
|
||||
edgeCutEdgeIds: [],
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
codeRef: {
|
||||
range: [231, 254, true],
|
||||
pathToNode: [['body', '']],
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
@ -1,17 +1,25 @@
|
||||
import {
|
||||
ArtifactCommand,
|
||||
ExecState,
|
||||
Artifact,
|
||||
ArtifactGraph,
|
||||
ArtifactId,
|
||||
PathToNode,
|
||||
Program,
|
||||
SourceRange,
|
||||
sourceRangeFromRust,
|
||||
PathArtifact,
|
||||
PlaneArtifact,
|
||||
WallArtifact,
|
||||
SegmentArtifact,
|
||||
Solid2dArtifact as Solid2D,
|
||||
SweepArtifact,
|
||||
SweepEdge,
|
||||
CapArtifact,
|
||||
EdgeCut,
|
||||
} from 'lang/wasm'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { err } from 'lib/trap'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
|
||||
export type ArtifactId = string
|
||||
export type { Artifact, ArtifactId, SegmentArtifact } from 'lang/wasm'
|
||||
|
||||
interface BaseArtifact {
|
||||
id: ArtifactId
|
||||
@ -22,30 +30,12 @@ export interface CodeRef {
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
|
||||
export interface PlaneArtifact extends BaseArtifact {
|
||||
type: 'plane'
|
||||
pathIds: Array<ArtifactId>
|
||||
codeRef: CodeRef
|
||||
}
|
||||
export interface PlaneArtifactRich extends BaseArtifact {
|
||||
type: 'plane'
|
||||
paths: Array<PathArtifact>
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
export interface PathArtifact extends BaseArtifact {
|
||||
type: 'path'
|
||||
planeId: ArtifactId
|
||||
segIds: Array<ArtifactId>
|
||||
sweepId?: ArtifactId
|
||||
solid2dId?: ArtifactId
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
interface solid2D extends BaseArtifact {
|
||||
type: 'solid2D'
|
||||
pathId: ArtifactId
|
||||
}
|
||||
export interface PathArtifactRich extends BaseArtifact {
|
||||
type: 'path'
|
||||
/** A path must always lie on a plane */
|
||||
@ -53,18 +43,10 @@ export interface PathArtifactRich extends BaseArtifact {
|
||||
/** A path must always contain 0 or more segments */
|
||||
segments: Array<SegmentArtifact>
|
||||
/** A path may not result in a sweep artifact */
|
||||
sweep?: SweepArtifact
|
||||
sweep: SweepArtifact | null
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
export interface SegmentArtifact extends BaseArtifact {
|
||||
type: 'segment'
|
||||
pathId: ArtifactId
|
||||
surfaceId?: ArtifactId
|
||||
edgeIds: Array<ArtifactId>
|
||||
edgeCutId?: ArtifactId
|
||||
codeRef: CodeRef
|
||||
}
|
||||
interface SegmentArtifactRich extends BaseArtifact {
|
||||
type: 'segment'
|
||||
path: PathArtifact
|
||||
@ -74,15 +56,6 @@ interface SegmentArtifactRich extends BaseArtifact {
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
|
||||
interface SweepArtifact extends BaseArtifact {
|
||||
type: 'sweep'
|
||||
subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
|
||||
pathId: string
|
||||
surfaceIds: Array<string>
|
||||
edgeIds: Array<string>
|
||||
codeRef: CodeRef
|
||||
}
|
||||
interface SweepArtifactRich extends BaseArtifact {
|
||||
type: 'sweep'
|
||||
subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
|
||||
@ -92,58 +65,6 @@ interface SweepArtifactRich extends BaseArtifact {
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
interface WallArtifact extends BaseArtifact {
|
||||
type: 'wall'
|
||||
segId: ArtifactId
|
||||
edgeCutEdgeIds: Array<ArtifactId>
|
||||
sweepId: ArtifactId
|
||||
pathIds: Array<ArtifactId>
|
||||
}
|
||||
interface CapArtifact extends BaseArtifact {
|
||||
type: 'cap'
|
||||
subType: 'start' | 'end'
|
||||
edgeCutEdgeIds: Array<ArtifactId>
|
||||
sweepId: ArtifactId
|
||||
pathIds: Array<ArtifactId>
|
||||
}
|
||||
|
||||
interface SweepEdge extends BaseArtifact {
|
||||
type: 'sweepEdge'
|
||||
segId: ArtifactId
|
||||
sweepId: ArtifactId
|
||||
subType: 'opposite' | 'adjacent'
|
||||
}
|
||||
|
||||
/** A edgeCut is a more generic term for both fillet or chamfer */
|
||||
interface EdgeCut extends BaseArtifact {
|
||||
type: 'edgeCut'
|
||||
subType: 'fillet' | 'chamfer'
|
||||
consumedEdgeId: ArtifactId
|
||||
edgeIds: Array<ArtifactId>
|
||||
surfaceId?: ArtifactId
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
interface EdgeCutEdge extends BaseArtifact {
|
||||
type: 'edgeCutEdge'
|
||||
edgeCutId: ArtifactId
|
||||
surfaceId: ArtifactId
|
||||
}
|
||||
|
||||
export type Artifact =
|
||||
| PlaneArtifact
|
||||
| PathArtifact
|
||||
| SegmentArtifact
|
||||
| SweepArtifact
|
||||
| WallArtifact
|
||||
| CapArtifact
|
||||
| SweepEdge
|
||||
| EdgeCut
|
||||
| EdgeCutEdge
|
||||
| solid2D
|
||||
|
||||
export type ArtifactGraph = Map<ArtifactId, Artifact>
|
||||
|
||||
export type EngineCommand = Models['WebSocketRequest_type']
|
||||
|
||||
type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
|
||||
@ -152,437 +73,6 @@ export interface ResponseMap {
|
||||
[commandId: string]: OkWebSocketResponseData
|
||||
}
|
||||
|
||||
/** Creates a graph of artifacts from a list of ordered commands and their responses
|
||||
* muting the Map should happen entirely this function, other functions called within
|
||||
* should return data on how to update the map, and not do so directly.
|
||||
*/
|
||||
export function createArtifactGraph({
|
||||
artifactCommands,
|
||||
responseMap,
|
||||
ast,
|
||||
execStateArtifacts,
|
||||
}: {
|
||||
artifactCommands: Array<ArtifactCommand>
|
||||
responseMap: ResponseMap
|
||||
ast: Node<Program>
|
||||
execStateArtifacts: ExecState['artifacts']
|
||||
}) {
|
||||
const myMap = new Map<ArtifactId, Artifact>()
|
||||
|
||||
/** see docstring for {@link getArtifactsToUpdate} as to why this is needed */
|
||||
let currentPlaneId = ''
|
||||
|
||||
for (const artifactCommand of artifactCommands) {
|
||||
if (artifactCommand.command.type === 'enable_sketch_mode') {
|
||||
currentPlaneId = artifactCommand.command.entity_id
|
||||
}
|
||||
if (artifactCommand.command.type === 'sketch_mode_disable') {
|
||||
currentPlaneId = ''
|
||||
}
|
||||
const artifactsToUpdate = getArtifactsToUpdate({
|
||||
artifactCommand,
|
||||
responseMap,
|
||||
getArtifact: (id: ArtifactId) => myMap.get(id),
|
||||
currentPlaneId,
|
||||
ast,
|
||||
execStateArtifacts,
|
||||
})
|
||||
artifactsToUpdate.forEach(({ id, artifact }) => {
|
||||
const mergedArtifact = mergeArtifacts(myMap.get(id), artifact)
|
||||
myMap.set(id, mergedArtifact)
|
||||
})
|
||||
}
|
||||
return myMap
|
||||
}
|
||||
|
||||
/** Merges two artifacts, since our artifacts only contain strings and arrays of string for values we coerce that
|
||||
* but maybe types can be improved here.
|
||||
*/
|
||||
function mergeArtifacts(
|
||||
oldArtifact: Artifact | undefined,
|
||||
newArtifact: Artifact
|
||||
): Artifact {
|
||||
// only has string and array of strings
|
||||
interface GenericArtifact {
|
||||
[key: string]: string | Array<string>
|
||||
}
|
||||
if (!oldArtifact) return newArtifact
|
||||
// merging artifacts of different types should never happen, but if it does, just return the new artifact
|
||||
if (oldArtifact.type !== newArtifact.type) return newArtifact
|
||||
const _oldArtifact = oldArtifact as any as GenericArtifact
|
||||
const mergedArtifact = { ...oldArtifact, ...newArtifact } as GenericArtifact
|
||||
Object.entries(newArtifact as any as GenericArtifact).forEach(
|
||||
([propName, value]) => {
|
||||
const otherValue = _oldArtifact[propName]
|
||||
if (Array.isArray(value) && Array.isArray(otherValue)) {
|
||||
mergedArtifact[propName] = [...new Set([...otherValue, ...value])]
|
||||
}
|
||||
}
|
||||
)
|
||||
return mergedArtifact as any as Artifact
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a single command and it's response in order to populate the artifact map
|
||||
* It does not mutate the map directly, but returns an array of artifacts to update
|
||||
*
|
||||
* @param currentPlaneId is only needed for `start_path` commands because this command does not have a pathId
|
||||
* instead it relies on the id used with the `enable_sketch_mode` command, so this much be kept track of
|
||||
* outside of this function. It would be good to update the `start_path` command to include the planeId so we
|
||||
* can remove this.
|
||||
*/
|
||||
export function getArtifactsToUpdate({
|
||||
artifactCommand,
|
||||
getArtifact,
|
||||
responseMap,
|
||||
currentPlaneId,
|
||||
ast,
|
||||
execStateArtifacts,
|
||||
}: {
|
||||
artifactCommand: ArtifactCommand
|
||||
responseMap: ResponseMap
|
||||
/** Passing in a getter because we don't wan this function to update the map directly */
|
||||
getArtifact: (id: ArtifactId) => Artifact | undefined
|
||||
currentPlaneId: ArtifactId
|
||||
ast: Node<Program>
|
||||
execStateArtifacts: ExecState['artifacts']
|
||||
}): Array<{
|
||||
id: ArtifactId
|
||||
artifact: Artifact
|
||||
}> {
|
||||
const range = sourceRangeFromRust(artifactCommand.range)
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
|
||||
const id = artifactCommand.cmdId
|
||||
const response = responseMap[id]
|
||||
const cmd = artifactCommand.command
|
||||
const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
|
||||
if (!response) return returnArr
|
||||
if (cmd.type === 'make_plane' && range[1] !== 0) {
|
||||
// If we're calling `make_plane` and the code range doesn't end at `0`
|
||||
// it's not a default plane, but a custom one from the offsetPlane standard library function
|
||||
return [
|
||||
{
|
||||
id,
|
||||
artifact: {
|
||||
type: 'plane',
|
||||
id,
|
||||
pathIds: [],
|
||||
codeRef: { range, pathToNode },
|
||||
},
|
||||
},
|
||||
]
|
||||
} else if (cmd.type === 'enable_sketch_mode') {
|
||||
const plane = getArtifact(currentPlaneId)
|
||||
const pathIds = plane?.type === 'plane' ? plane?.pathIds : []
|
||||
const codeRef =
|
||||
plane?.type === 'plane' ? plane?.codeRef : { range, pathToNode }
|
||||
const existingPlane = getArtifact(currentPlaneId)
|
||||
if (existingPlane?.type === 'wall') {
|
||||
return [
|
||||
{
|
||||
id: currentPlaneId,
|
||||
artifact: {
|
||||
type: 'wall',
|
||||
id: currentPlaneId,
|
||||
segId: existingPlane.segId,
|
||||
edgeCutEdgeIds: existingPlane.edgeCutEdgeIds,
|
||||
sweepId: existingPlane.sweepId,
|
||||
pathIds: existingPlane.pathIds,
|
||||
},
|
||||
},
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
id: currentPlaneId,
|
||||
artifact: { type: 'plane', id: currentPlaneId, pathIds, codeRef },
|
||||
},
|
||||
]
|
||||
}
|
||||
} else if (cmd.type === 'start_path') {
|
||||
returnArr.push({
|
||||
id,
|
||||
artifact: {
|
||||
type: 'path',
|
||||
id,
|
||||
segIds: [],
|
||||
planeId: currentPlaneId,
|
||||
sweepId: undefined,
|
||||
codeRef: { range, pathToNode },
|
||||
},
|
||||
})
|
||||
const plane = getArtifact(currentPlaneId)
|
||||
const codeRef =
|
||||
plane?.type === 'plane' ? plane?.codeRef : { range, pathToNode }
|
||||
if (plane?.type === 'plane') {
|
||||
returnArr.push({
|
||||
id: currentPlaneId,
|
||||
artifact: { type: 'plane', id: currentPlaneId, pathIds: [id], codeRef },
|
||||
})
|
||||
}
|
||||
if (plane?.type === 'wall') {
|
||||
returnArr.push({
|
||||
id: currentPlaneId,
|
||||
artifact: {
|
||||
type: 'wall',
|
||||
id: currentPlaneId,
|
||||
segId: plane.segId,
|
||||
edgeCutEdgeIds: plane.edgeCutEdgeIds,
|
||||
sweepId: plane.sweepId,
|
||||
pathIds: [id],
|
||||
},
|
||||
})
|
||||
}
|
||||
return returnArr
|
||||
} else if (cmd.type === 'extend_path' || cmd.type === 'close_path') {
|
||||
const pathId = cmd.type === 'extend_path' ? cmd.path : cmd.path_id
|
||||
returnArr.push({
|
||||
id,
|
||||
artifact: {
|
||||
type: 'segment',
|
||||
id,
|
||||
pathId,
|
||||
surfaceId: undefined,
|
||||
edgeIds: [],
|
||||
codeRef: { range, pathToNode },
|
||||
},
|
||||
})
|
||||
const path = getArtifact(pathId)
|
||||
if (path?.type === 'path')
|
||||
returnArr.push({
|
||||
id: pathId,
|
||||
artifact: { ...path, segIds: [id] },
|
||||
})
|
||||
if (
|
||||
response?.type === 'modeling' &&
|
||||
response.data.modeling_response.type === 'close_path'
|
||||
) {
|
||||
returnArr.push({
|
||||
id: response.data.modeling_response.data.face_id,
|
||||
artifact: {
|
||||
type: 'solid2D',
|
||||
id: response.data.modeling_response.data.face_id,
|
||||
pathId,
|
||||
},
|
||||
})
|
||||
const path = getArtifact(pathId)
|
||||
if (path?.type === 'path')
|
||||
returnArr.push({
|
||||
id: pathId,
|
||||
artifact: {
|
||||
...path,
|
||||
solid2dId: response.data.modeling_response.data.face_id,
|
||||
},
|
||||
})
|
||||
}
|
||||
return returnArr
|
||||
} else if (
|
||||
cmd.type === 'extrude' ||
|
||||
cmd.type === 'revolve' ||
|
||||
cmd.type === 'sweep'
|
||||
) {
|
||||
const subType = cmd.type === 'extrude' ? 'extrusion' : cmd.type
|
||||
returnArr.push({
|
||||
id,
|
||||
artifact: {
|
||||
type: 'sweep',
|
||||
subType: subType,
|
||||
id,
|
||||
pathId: cmd.target,
|
||||
surfaceIds: [],
|
||||
edgeIds: [],
|
||||
codeRef: { range, pathToNode },
|
||||
},
|
||||
})
|
||||
const path = getArtifact(cmd.target)
|
||||
if (path?.type === 'path')
|
||||
returnArr.push({
|
||||
id: cmd.target,
|
||||
artifact: { ...path, sweepId: id },
|
||||
})
|
||||
return returnArr
|
||||
} else if (
|
||||
cmd.type === 'loft' &&
|
||||
response.type === 'modeling' &&
|
||||
response.data.modeling_response.type === 'loft'
|
||||
) {
|
||||
returnArr.push({
|
||||
id,
|
||||
artifact: {
|
||||
type: 'sweep',
|
||||
subType: 'loft',
|
||||
id,
|
||||
// TODO: make sure to revisit this choice, don't think it matters for now
|
||||
pathId: cmd.section_ids[0],
|
||||
surfaceIds: [],
|
||||
edgeIds: [],
|
||||
codeRef: { range, pathToNode },
|
||||
},
|
||||
})
|
||||
for (const sectionId of cmd.section_ids) {
|
||||
const path = getArtifact(sectionId)
|
||||
if (path?.type === 'path')
|
||||
returnArr.push({
|
||||
id: sectionId,
|
||||
artifact: { ...path, sweepId: id },
|
||||
})
|
||||
}
|
||||
return returnArr
|
||||
} else if (
|
||||
cmd.type === 'solid3d_get_extrusion_face_info' &&
|
||||
response?.type === 'modeling' &&
|
||||
response.data.modeling_response.type === 'solid3d_get_extrusion_face_info'
|
||||
) {
|
||||
let lastPath: PathArtifact
|
||||
response.data.modeling_response.data.faces.forEach(
|
||||
({ curve_id, cap, face_id }) => {
|
||||
if (cap === 'none' && curve_id && face_id) {
|
||||
const seg = getArtifact(curve_id)
|
||||
if (seg?.type !== 'segment') return
|
||||
const path = getArtifact(seg.pathId)
|
||||
if (path?.type === 'path' && seg?.type === 'segment') {
|
||||
lastPath = path
|
||||
returnArr.push({
|
||||
id: face_id,
|
||||
artifact: {
|
||||
type: 'wall',
|
||||
id: face_id,
|
||||
segId: curve_id,
|
||||
edgeCutEdgeIds: [],
|
||||
// TODO: Add explicit check for sweepId. Should never use ''
|
||||
sweepId: path.sweepId ?? '',
|
||||
pathIds: [],
|
||||
},
|
||||
})
|
||||
returnArr.push({
|
||||
id: curve_id,
|
||||
artifact: { ...seg, surfaceId: face_id },
|
||||
})
|
||||
if (path.sweepId) {
|
||||
const sweep = getArtifact(path.sweepId)
|
||||
if (sweep?.type === 'sweep') {
|
||||
returnArr.push({
|
||||
id: path.sweepId,
|
||||
artifact: {
|
||||
...sweep,
|
||||
surfaceIds: [face_id],
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
response.data.modeling_response.data.faces.forEach(({ cap, face_id }) => {
|
||||
if ((cap === 'top' || cap === 'bottom') && face_id) {
|
||||
const path = lastPath
|
||||
if (path?.type === 'path') {
|
||||
returnArr.push({
|
||||
id: face_id,
|
||||
artifact: {
|
||||
type: 'cap',
|
||||
id: face_id,
|
||||
subType: cap === 'bottom' ? 'start' : 'end',
|
||||
edgeCutEdgeIds: [],
|
||||
// TODO: Add explicit check for sweepId. Should never use ''
|
||||
sweepId: path.sweepId ?? '',
|
||||
pathIds: [],
|
||||
},
|
||||
})
|
||||
if (path.sweepId) {
|
||||
const sweep = getArtifact(path.sweepId)
|
||||
if (sweep?.type !== 'sweep') return
|
||||
returnArr.push({
|
||||
id: path.sweepId,
|
||||
artifact: {
|
||||
...sweep,
|
||||
surfaceIds: [face_id],
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return returnArr
|
||||
} else if (
|
||||
// is opposite edge
|
||||
(cmd.type === 'solid3d_get_opposite_edge' &&
|
||||
response.type === 'modeling' &&
|
||||
response.data.modeling_response.type === 'solid3d_get_opposite_edge' &&
|
||||
response.data.modeling_response.data.edge) ||
|
||||
// or is adjacent edge
|
||||
(cmd.type === 'solid3d_get_next_adjacent_edge' &&
|
||||
response.type === 'modeling' &&
|
||||
response.data.modeling_response.type ===
|
||||
'solid3d_get_next_adjacent_edge' &&
|
||||
response.data.modeling_response.data.edge)
|
||||
) {
|
||||
const wall = getArtifact(cmd.face_id)
|
||||
if (wall?.type !== 'wall') return returnArr
|
||||
const sweep = getArtifact(wall.sweepId)
|
||||
if (sweep?.type !== 'sweep') return returnArr
|
||||
const path = getArtifact(sweep.pathId)
|
||||
if (path?.type !== 'path') return returnArr
|
||||
const segment = getArtifact(cmd.edge_id)
|
||||
if (segment?.type !== 'segment') return returnArr
|
||||
|
||||
return [
|
||||
{
|
||||
id: response.data.modeling_response.data.edge,
|
||||
artifact: {
|
||||
type: 'sweepEdge',
|
||||
id: response.data.modeling_response.data.edge,
|
||||
subType:
|
||||
cmd.type === 'solid3d_get_next_adjacent_edge'
|
||||
? 'adjacent'
|
||||
: 'opposite',
|
||||
segId: cmd.edge_id,
|
||||
// TODO: Add explicit check for sweepId. Should never use ''
|
||||
sweepId: path.sweepId ?? '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: cmd.edge_id,
|
||||
artifact: {
|
||||
...segment,
|
||||
edgeIds: [response.data.modeling_response.data.edge],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: sweep.id,
|
||||
artifact: {
|
||||
...sweep,
|
||||
edgeIds: [response.data.modeling_response.data.edge],
|
||||
},
|
||||
},
|
||||
]
|
||||
} else if (cmd.type === 'solid3d_fillet_edge') {
|
||||
returnArr.push({
|
||||
id,
|
||||
artifact: {
|
||||
type: 'edgeCut',
|
||||
id,
|
||||
subType: cmd.cut_type,
|
||||
consumedEdgeId: cmd.edge_id,
|
||||
edgeIds: [],
|
||||
surfaceId: undefined,
|
||||
codeRef: { range, pathToNode },
|
||||
},
|
||||
})
|
||||
const consumedEdge = getArtifact(cmd.edge_id)
|
||||
if (consumedEdge?.type === 'segment') {
|
||||
returnArr.push({
|
||||
id: cmd.edge_id,
|
||||
artifact: { ...consumedEdge, edgeCutId: id },
|
||||
})
|
||||
}
|
||||
return returnArr
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/** filter map items of a specific type */
|
||||
export function filterArtifacts<T extends Artifact['type'][]>(
|
||||
{
|
||||
@ -676,7 +166,7 @@ export function expandPath(
|
||||
},
|
||||
artifactGraph
|
||||
)
|
||||
: undefined
|
||||
: null
|
||||
const plane = getArtifactOfTypes(
|
||||
{ key: path.planeId, types: ['plane', 'wall'] },
|
||||
artifactGraph
|
||||
@ -778,11 +268,11 @@ export function getCapCodeRef(
|
||||
}
|
||||
|
||||
export function getSolid2dCodeRef(
|
||||
solid2D: solid2D,
|
||||
solid2d: Solid2D,
|
||||
artifactGraph: ArtifactGraph
|
||||
): CodeRef | Error {
|
||||
const path = getArtifactOfTypes(
|
||||
{ key: solid2D.pathId, types: ['path'] },
|
||||
{ key: solid2d.pathId, types: ['path'] },
|
||||
artifactGraph
|
||||
)
|
||||
if (err(path)) return path
|
||||
@ -881,7 +371,7 @@ export function getCodeRefsByArtifactId(
|
||||
artifactGraph: ArtifactGraph
|
||||
): Array<CodeRef> | null {
|
||||
const artifact = artifactGraph.get(id)
|
||||
if (artifact?.type === 'solid2D') {
|
||||
if (artifact?.type === 'solid2d') {
|
||||
const codeRef = getSolid2dCodeRef(artifact, artifactGraph)
|
||||
if (err(codeRef)) return null
|
||||
return [codeRef]
|
||||
|
@ -1,9 +1,7 @@
|
||||
import {
|
||||
ArtifactCommand,
|
||||
defaultRustSourceRange,
|
||||
ArtifactGraph,
|
||||
defaultSourceRange,
|
||||
ExecState,
|
||||
Program,
|
||||
RustSourceRange,
|
||||
SourceRange,
|
||||
} from 'lang/wasm'
|
||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
||||
@ -17,12 +15,7 @@ import {
|
||||
darkModeMatcher,
|
||||
} from 'lib/theme'
|
||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||
import {
|
||||
ArtifactGraph,
|
||||
EngineCommand,
|
||||
ResponseMap,
|
||||
createArtifactGraph,
|
||||
} from 'lang/std/artifactGraph'
|
||||
import { EngineCommand, ResponseMap } from 'lang/std/artifactGraph'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { exportMake } from 'lib/exportMake'
|
||||
import toast from 'react-hot-toast'
|
||||
@ -36,7 +29,6 @@ import { KclManager } from 'lang/KclSingleton'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { markOnce } from 'lib/performance'
|
||||
import { MachineManager } from 'components/MachineManagerProvider'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
|
||||
// TODO(paultag): This ought to be tweakable.
|
||||
const pingIntervalMs = 5_000
|
||||
@ -1309,8 +1301,8 @@ export enum EngineCommandManagerEvents {
|
||||
|
||||
interface PendingMessage {
|
||||
command: EngineCommand
|
||||
range: RustSourceRange
|
||||
idToRangeMap: { [key: string]: RustSourceRange }
|
||||
range: SourceRange
|
||||
idToRangeMap: { [key: string]: SourceRange }
|
||||
resolve: (data: [Models['WebSocketResponse_type']]) => void
|
||||
reject: (reason: string) => void
|
||||
promise: Promise<[Models['WebSocketResponse_type']]>
|
||||
@ -1994,7 +1986,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
{
|
||||
command,
|
||||
idToRangeMap: {},
|
||||
range: defaultRustSourceRange(),
|
||||
range: defaultSourceRange(),
|
||||
},
|
||||
true // isSceneCommand
|
||||
)
|
||||
@ -2025,9 +2017,9 @@ export class EngineCommandManager extends EventTarget {
|
||||
return Promise.reject(new Error('rangeStr is undefined'))
|
||||
if (commandStr === undefined)
|
||||
return Promise.reject(new Error('commandStr is undefined'))
|
||||
const range: RustSourceRange = JSON.parse(rangeStr)
|
||||
const range: SourceRange = JSON.parse(rangeStr)
|
||||
const command: EngineCommand = JSON.parse(commandStr)
|
||||
const idToRangeMap: { [key: string]: RustSourceRange } =
|
||||
const idToRangeMap: { [key: string]: SourceRange } =
|
||||
JSON.parse(idToRangeStr)
|
||||
|
||||
// Current executeAst is stale, going to interrupt, a new executeAst will trigger
|
||||
@ -2087,17 +2079,8 @@ export class EngineCommandManager extends EventTarget {
|
||||
Object.values(this.pendingCommands).map((a) => a.promise)
|
||||
)
|
||||
}
|
||||
updateArtifactGraph(
|
||||
ast: Node<Program>,
|
||||
artifactCommands: ArtifactCommand[],
|
||||
execStateArtifacts: ExecState['artifacts']
|
||||
) {
|
||||
this.artifactGraph = createArtifactGraph({
|
||||
artifactCommands,
|
||||
responseMap: this.responseMap,
|
||||
ast,
|
||||
execStateArtifacts,
|
||||
})
|
||||
updateArtifactGraph(execStateArtifactGraph: ExecState['artifactGraph']) {
|
||||
this.artifactGraph = execStateArtifactGraph
|
||||
// TODO check if these still need to be deferred once e2e tests are working again.
|
||||
if (this.artifactGraph.size) {
|
||||
this.deferredArtifactEmptied(null)
|
||||
|
@ -11,8 +11,8 @@ import {
|
||||
assertParse,
|
||||
recast,
|
||||
initPromise,
|
||||
SourceRange,
|
||||
CallExpression,
|
||||
topLevelRange,
|
||||
} from '../wasm'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
@ -124,7 +124,10 @@ describe('testing changeSketchArguments', () => {
|
||||
execState.memory,
|
||||
{
|
||||
type: 'sourceRange',
|
||||
sourceRange: [sourceStart, sourceStart + lineToChange.length, true],
|
||||
sourceRange: topLevelRange(
|
||||
sourceStart,
|
||||
sourceStart + lineToChange.length
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'straight-segment',
|
||||
@ -219,11 +222,10 @@ describe('testing addTagForSketchOnFace', () => {
|
||||
const ast = assertParse(code)
|
||||
await enginelessExecutor(ast)
|
||||
const sourceStart = code.indexOf(originalLine)
|
||||
const sourceRange: [number, number, boolean] = [
|
||||
const sourceRange = topLevelRange(
|
||||
sourceStart,
|
||||
sourceStart + originalLine.length,
|
||||
true,
|
||||
]
|
||||
sourceStart + originalLine.length
|
||||
)
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const sketchOnFaceRetVal = addTagForSketchOnFace(
|
||||
@ -292,11 +294,10 @@ ${insertCode}
|
||||
await enginelessExecutor(ast)
|
||||
const sourceStart = code.indexOf(originalChamfer)
|
||||
const extraChars = originalChamfer.indexOf('chamfer')
|
||||
const sourceRange: [number, number, boolean] = [
|
||||
const sourceRange = topLevelRange(
|
||||
sourceStart + extraChars,
|
||||
sourceStart + originalChamfer.length - extraChars,
|
||||
true,
|
||||
]
|
||||
sourceStart + originalChamfer.length - extraChars
|
||||
)
|
||||
|
||||
if (err(ast)) throw ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
@ -357,7 +358,6 @@ describe('testing getConstraintInfo', () => {
|
||||
offset = 0
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14, 13.14], %)`
|
||||
const ast = assertParse(code)
|
||||
test.each([
|
||||
[
|
||||
'line',
|
||||
@ -366,7 +366,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: false,
|
||||
value: '3',
|
||||
sourceRange: [78, 79, true],
|
||||
sourceRange: topLevelRange(78, 79),
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'line',
|
||||
@ -375,7 +375,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: false,
|
||||
value: '4',
|
||||
sourceRange: [81, 82, true],
|
||||
sourceRange: topLevelRange(81, 82),
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'line',
|
||||
@ -389,7 +389,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [118, 122, true],
|
||||
sourceRange: topLevelRange(118, 122),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -398,7 +398,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'length',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [137, 141, true],
|
||||
sourceRange: topLevelRange(137, 141),
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -412,7 +412,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: false,
|
||||
value: '6.14',
|
||||
sourceRange: [164, 168, true],
|
||||
sourceRange: topLevelRange(164, 168),
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'lineTo',
|
||||
@ -421,7 +421,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [170, 174, true],
|
||||
sourceRange: topLevelRange(170, 174),
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'lineTo',
|
||||
@ -435,7 +435,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'horizontal',
|
||||
isConstrained: true,
|
||||
value: 'xLineTo',
|
||||
sourceRange: [185, 192, true],
|
||||
sourceRange: topLevelRange(185, 192),
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLineTo',
|
||||
@ -444,7 +444,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: false,
|
||||
value: '8',
|
||||
sourceRange: [193, 194, true],
|
||||
sourceRange: topLevelRange(193, 194),
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLineTo',
|
||||
@ -458,7 +458,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'vertical',
|
||||
isConstrained: true,
|
||||
value: 'yLineTo',
|
||||
sourceRange: [204, 211, true],
|
||||
sourceRange: topLevelRange(204, 211),
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLineTo',
|
||||
@ -467,7 +467,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: false,
|
||||
value: '5',
|
||||
sourceRange: [212, 213, true],
|
||||
sourceRange: topLevelRange(212, 213),
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLineTo',
|
||||
@ -481,7 +481,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'vertical',
|
||||
isConstrained: true,
|
||||
value: 'yLine',
|
||||
sourceRange: [223, 228, true],
|
||||
sourceRange: topLevelRange(223, 228),
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLine',
|
||||
@ -490,7 +490,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [229, 233, true],
|
||||
sourceRange: topLevelRange(229, 233),
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLine',
|
||||
@ -504,7 +504,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'horizontal',
|
||||
isConstrained: true,
|
||||
value: 'xLine',
|
||||
sourceRange: [247, 252, true],
|
||||
sourceRange: topLevelRange(247, 252),
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLine',
|
||||
@ -513,7 +513,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [253, 257, true],
|
||||
sourceRange: topLevelRange(253, 257),
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLine',
|
||||
@ -527,7 +527,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [301, 305, true],
|
||||
sourceRange: topLevelRange(301, 305),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -536,7 +536,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [320, 324, true],
|
||||
sourceRange: topLevelRange(320, 324),
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -550,7 +550,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '30',
|
||||
sourceRange: [373, 375, true],
|
||||
sourceRange: topLevelRange(373, 375),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -559,7 +559,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: false,
|
||||
value: '3',
|
||||
sourceRange: [390, 391, true],
|
||||
sourceRange: topLevelRange(390, 391),
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -573,7 +573,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '12.14',
|
||||
sourceRange: [434, 439, true],
|
||||
sourceRange: topLevelRange(434, 439),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -582,7 +582,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: false,
|
||||
value: '12',
|
||||
sourceRange: [450, 452, true],
|
||||
sourceRange: topLevelRange(450, 452),
|
||||
argPosition: { type: 'objectProperty', key: 'to' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -596,7 +596,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '30',
|
||||
sourceRange: [495, 497, true],
|
||||
sourceRange: topLevelRange(495, 497),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -605,7 +605,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: false,
|
||||
value: '10.14',
|
||||
sourceRange: [508, 513, true],
|
||||
sourceRange: topLevelRange(508, 513),
|
||||
argPosition: { type: 'objectProperty', key: 'to' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -619,7 +619,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [567, 571, true],
|
||||
sourceRange: topLevelRange(567, 571),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
@ -628,7 +628,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'intersectionOffset',
|
||||
isConstrained: false,
|
||||
value: '0',
|
||||
sourceRange: [608, 609, true],
|
||||
sourceRange: topLevelRange(608, 609),
|
||||
argPosition: { type: 'objectProperty', key: 'offset' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
@ -637,7 +637,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'intersectionTag',
|
||||
isConstrained: false,
|
||||
value: 'a',
|
||||
sourceRange: [592, 593, true],
|
||||
sourceRange: topLevelRange(592, 593),
|
||||
argPosition: {
|
||||
key: 'intersectTag',
|
||||
type: 'objectProperty',
|
||||
@ -654,7 +654,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'tangentialWithPrevious',
|
||||
isConstrained: true,
|
||||
value: 'tangentialArcTo',
|
||||
sourceRange: [623, 638, true],
|
||||
sourceRange: topLevelRange(623, 638),
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -663,7 +663,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [640, 644, true],
|
||||
sourceRange: topLevelRange(640, 644),
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -672,7 +672,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: false,
|
||||
value: '13.14',
|
||||
sourceRange: [646, 651, true],
|
||||
sourceRange: topLevelRange(646, 651),
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -680,11 +680,11 @@ describe('testing getConstraintInfo', () => {
|
||||
],
|
||||
],
|
||||
])('testing %s when inputs are unconstrained', (functionName, expected) => {
|
||||
const sourceRange: SourceRange = [
|
||||
const ast = assertParse(code)
|
||||
const sourceRange = topLevelRange(
|
||||
code.indexOf(functionName),
|
||||
code.indexOf(functionName) + functionName.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(functionName) + functionName.length
|
||||
)
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const callExp = getNodeFromPath<Node<CallExpression>>(
|
||||
@ -717,7 +717,6 @@ describe('testing getConstraintInfo', () => {
|
||||
offset = 0
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14, 13.14], %)`
|
||||
const ast = assertParse(code)
|
||||
test.each([
|
||||
[
|
||||
`angledLine(`,
|
||||
@ -726,7 +725,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [112, 116, true],
|
||||
sourceRange: topLevelRange(112, 116),
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -735,7 +734,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'length',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [118, 122, true],
|
||||
sourceRange: topLevelRange(118, 122),
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -749,7 +748,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [277, 281, true],
|
||||
sourceRange: topLevelRange(277, 281),
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -758,7 +757,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: false,
|
||||
value: '3.14',
|
||||
sourceRange: [283, 287, true],
|
||||
sourceRange: topLevelRange(283, 287),
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -772,7 +771,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '30',
|
||||
sourceRange: [321, 323, true],
|
||||
sourceRange: topLevelRange(321, 323),
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -781,7 +780,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: false,
|
||||
value: '3',
|
||||
sourceRange: [325, 326, true],
|
||||
sourceRange: topLevelRange(325, 326),
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -795,7 +794,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '12',
|
||||
sourceRange: [354, 356, true],
|
||||
sourceRange: topLevelRange(354, 356),
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -804,7 +803,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: false,
|
||||
value: '12',
|
||||
sourceRange: [358, 360, true],
|
||||
sourceRange: topLevelRange(358, 360),
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -818,7 +817,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: false,
|
||||
value: '30',
|
||||
sourceRange: [388, 390, true],
|
||||
sourceRange: topLevelRange(388, 390),
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -827,7 +826,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: false,
|
||||
value: '10',
|
||||
sourceRange: [392, 394, true],
|
||||
sourceRange: topLevelRange(392, 394),
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -835,11 +834,11 @@ describe('testing getConstraintInfo', () => {
|
||||
],
|
||||
],
|
||||
])('testing %s when inputs are unconstrained', (functionName, expected) => {
|
||||
const sourceRange: SourceRange = [
|
||||
const ast = assertParse(code)
|
||||
const sourceRange = topLevelRange(
|
||||
code.indexOf(functionName),
|
||||
code.indexOf(functionName) + functionName.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(functionName) + functionName.length
|
||||
)
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const callExp = getNodeFromPath<Node<CallExpression>>(
|
||||
@ -872,7 +871,6 @@ describe('testing getConstraintInfo', () => {
|
||||
offset = 0 + 0
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 0, 13.14 + 0], %)`
|
||||
const ast = assertParse(code)
|
||||
test.each([
|
||||
[
|
||||
'line',
|
||||
@ -881,7 +879,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: true,
|
||||
value: '3 + 0',
|
||||
sourceRange: [83, 88, true],
|
||||
sourceRange: topLevelRange(83, 88),
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'line',
|
||||
@ -890,7 +888,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: true,
|
||||
value: '4 + 0',
|
||||
sourceRange: [90, 95, true],
|
||||
sourceRange: topLevelRange(90, 95),
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'line',
|
||||
@ -904,7 +902,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [129, 137, true],
|
||||
sourceRange: topLevelRange(129, 137),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -913,7 +911,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'length',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [148, 156, true],
|
||||
sourceRange: topLevelRange(148, 156),
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLine',
|
||||
@ -927,7 +925,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: true,
|
||||
value: '6.14 + 0',
|
||||
sourceRange: [178, 186, true],
|
||||
sourceRange: topLevelRange(178, 186),
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'lineTo',
|
||||
@ -936,7 +934,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [188, 196, true],
|
||||
sourceRange: topLevelRange(188, 196),
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'lineTo',
|
||||
@ -950,7 +948,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'horizontal',
|
||||
isConstrained: true,
|
||||
value: 'xLineTo',
|
||||
sourceRange: [209, 216, true],
|
||||
sourceRange: topLevelRange(209, 216),
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLineTo',
|
||||
@ -959,7 +957,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: true,
|
||||
value: '8 + 0',
|
||||
sourceRange: [217, 222, true],
|
||||
sourceRange: topLevelRange(217, 222),
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLineTo',
|
||||
@ -973,7 +971,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'vertical',
|
||||
isConstrained: true,
|
||||
value: 'yLineTo',
|
||||
sourceRange: [234, 241, true],
|
||||
sourceRange: topLevelRange(234, 241),
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLineTo',
|
||||
@ -982,7 +980,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: true,
|
||||
value: '5 + 0',
|
||||
sourceRange: [242, 247, true],
|
||||
sourceRange: topLevelRange(242, 247),
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLineTo',
|
||||
@ -996,7 +994,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'vertical',
|
||||
isConstrained: true,
|
||||
value: 'yLine',
|
||||
sourceRange: [259, 264, true],
|
||||
sourceRange: topLevelRange(259, 264),
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLine',
|
||||
@ -1005,7 +1003,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [265, 273, true],
|
||||
sourceRange: topLevelRange(265, 273),
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'yLine',
|
||||
@ -1019,7 +1017,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'horizontal',
|
||||
isConstrained: true,
|
||||
value: 'xLine',
|
||||
sourceRange: [289, 294, true],
|
||||
sourceRange: topLevelRange(289, 294),
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLine',
|
||||
@ -1028,7 +1026,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [295, 303, true],
|
||||
sourceRange: topLevelRange(295, 303),
|
||||
argPosition: { type: 'singleValue' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'xLine',
|
||||
@ -1042,7 +1040,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [345, 353, true],
|
||||
sourceRange: topLevelRange(345, 353),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -1051,7 +1049,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xRelative',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [364, 372, true],
|
||||
sourceRange: topLevelRange(364, 372),
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
@ -1065,7 +1063,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '30 + 0',
|
||||
sourceRange: [416, 422, true],
|
||||
sourceRange: topLevelRange(416, 422),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -1074,7 +1072,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yRelative',
|
||||
isConstrained: true,
|
||||
value: '3 + 0',
|
||||
sourceRange: [433, 438, true],
|
||||
sourceRange: topLevelRange(433, 438),
|
||||
argPosition: { type: 'objectProperty', key: 'length' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
@ -1088,7 +1086,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '12.14 + 0',
|
||||
sourceRange: [476, 485, true],
|
||||
sourceRange: topLevelRange(476, 485),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -1097,7 +1095,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: true,
|
||||
value: '12 + 0',
|
||||
sourceRange: [492, 498, true],
|
||||
sourceRange: topLevelRange(492, 498),
|
||||
argPosition: { type: 'objectProperty', key: 'to' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToX',
|
||||
@ -1111,7 +1109,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '30 + 0',
|
||||
sourceRange: [536, 542, true],
|
||||
sourceRange: topLevelRange(536, 542),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -1120,7 +1118,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: true,
|
||||
value: '10.14 + 0',
|
||||
sourceRange: [549, 558, true],
|
||||
sourceRange: topLevelRange(549, 558),
|
||||
argPosition: { type: 'objectProperty', key: 'to' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineToY',
|
||||
@ -1134,7 +1132,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'angle',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [616, 624, true],
|
||||
sourceRange: topLevelRange(616, 624),
|
||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
@ -1143,7 +1141,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'intersectionOffset',
|
||||
isConstrained: true,
|
||||
value: '0 + 0',
|
||||
sourceRange: [671, 676, true],
|
||||
sourceRange: topLevelRange(671, 676),
|
||||
argPosition: { type: 'objectProperty', key: 'offset' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
@ -1152,7 +1150,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'intersectionTag',
|
||||
isConstrained: false,
|
||||
value: 'a',
|
||||
sourceRange: [650, 651, true],
|
||||
sourceRange: topLevelRange(650, 651),
|
||||
argPosition: { key: 'intersectTag', type: 'objectProperty' },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
@ -1166,7 +1164,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'tangentialWithPrevious',
|
||||
isConstrained: true,
|
||||
value: 'tangentialArcTo',
|
||||
sourceRange: [697, 712, true],
|
||||
sourceRange: topLevelRange(697, 712),
|
||||
argPosition: undefined,
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -1175,7 +1173,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'xAbsolute',
|
||||
isConstrained: true,
|
||||
value: '3.14 + 0',
|
||||
sourceRange: [714, 722, true],
|
||||
sourceRange: topLevelRange(714, 722),
|
||||
argPosition: { type: 'arrayItem', index: 0 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -1184,7 +1182,7 @@ describe('testing getConstraintInfo', () => {
|
||||
type: 'yAbsolute',
|
||||
isConstrained: true,
|
||||
value: '13.14 + 0',
|
||||
sourceRange: [724, 733, true],
|
||||
sourceRange: topLevelRange(724, 733),
|
||||
argPosition: { type: 'arrayItem', index: 1 },
|
||||
pathToNode: expect.any(Array),
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
@ -1192,11 +1190,11 @@ describe('testing getConstraintInfo', () => {
|
||||
],
|
||||
],
|
||||
])('testing %s when inputs are unconstrained', (functionName, expected) => {
|
||||
const sourceRange: SourceRange = [
|
||||
const ast = assertParse(code)
|
||||
const sourceRange = topLevelRange(
|
||||
code.indexOf(functionName),
|
||||
code.indexOf(functionName) + functionName.length,
|
||||
true,
|
||||
]
|
||||
code.indexOf(functionName) + functionName.length
|
||||
)
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const callExp = getNodeFromPath<Node<CallExpression>>(
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
VariableDeclaration,
|
||||
Identifier,
|
||||
sketchFromKclValue,
|
||||
topLevelRange,
|
||||
} from 'lang/wasm'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
@ -222,7 +223,7 @@ const commonConstraintInfoHelper = (
|
||||
code.slice(input1.start, input1.end),
|
||||
stdLibFnName,
|
||||
isArr ? abbreviatedInputs[0].arrayInput : abbreviatedInputs[0].objInput,
|
||||
[input1.start, input1.end, true],
|
||||
topLevelRange(input1.start, input1.end),
|
||||
pathToFirstArg
|
||||
)
|
||||
)
|
||||
@ -234,7 +235,7 @@ const commonConstraintInfoHelper = (
|
||||
code.slice(input2.start, input2.end),
|
||||
stdLibFnName,
|
||||
isArr ? abbreviatedInputs[1].arrayInput : abbreviatedInputs[1].objInput,
|
||||
[input2.start, input2.end, true],
|
||||
topLevelRange(input2.start, input2.end),
|
||||
pathToSecondArg
|
||||
)
|
||||
)
|
||||
@ -266,7 +267,7 @@ const horzVertConstraintInfoHelper = (
|
||||
callee.name,
|
||||
stdLibFnName,
|
||||
undefined,
|
||||
[callee.start, callee.end, true],
|
||||
topLevelRange(callee.start, callee.end),
|
||||
pathToCallee
|
||||
),
|
||||
constrainInfo(
|
||||
@ -275,7 +276,7 @@ const horzVertConstraintInfoHelper = (
|
||||
code.slice(firstArg.start, firstArg.end),
|
||||
stdLibFnName,
|
||||
abbreviatedInput,
|
||||
[firstArg.start, firstArg.end, true],
|
||||
topLevelRange(firstArg.start, firstArg.end),
|
||||
pathToFirstArg
|
||||
),
|
||||
]
|
||||
@ -905,7 +906,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
||||
callee.name,
|
||||
'tangentialArcTo',
|
||||
undefined,
|
||||
[callee.start, callee.end, true],
|
||||
topLevelRange(callee.start, callee.end),
|
||||
pathToCallee
|
||||
),
|
||||
constrainInfo(
|
||||
@ -914,7 +915,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
||||
code.slice(firstArg.elements[0].start, firstArg.elements[0].end),
|
||||
'tangentialArcTo',
|
||||
0,
|
||||
[firstArg.elements[0].start, firstArg.elements[0].end, true],
|
||||
topLevelRange(firstArg.elements[0].start, firstArg.elements[0].end),
|
||||
pathToFirstArg
|
||||
),
|
||||
constrainInfo(
|
||||
@ -923,7 +924,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
||||
code.slice(firstArg.elements[1].start, firstArg.elements[1].end),
|
||||
'tangentialArcTo',
|
||||
1,
|
||||
[firstArg.elements[1].start, firstArg.elements[1].end, true],
|
||||
topLevelRange(firstArg.elements[1].start, firstArg.elements[1].end),
|
||||
pathToSecondArg
|
||||
),
|
||||
]
|
||||
@ -1052,7 +1053,7 @@ export const circle: SketchLineHelper = {
|
||||
code.slice(radiusDetails.expr.start, radiusDetails.expr.end),
|
||||
'circle',
|
||||
'radius',
|
||||
[radiusDetails.expr.start, radiusDetails.expr.end, true],
|
||||
topLevelRange(radiusDetails.expr.start, radiusDetails.expr.end),
|
||||
pathToRadiusLiteral
|
||||
),
|
||||
{
|
||||
@ -1061,11 +1062,10 @@ export const circle: SketchLineHelper = {
|
||||
isConstrained: isNotLiteralArrayOrStatic(
|
||||
centerDetails.expr.elements[0]
|
||||
),
|
||||
sourceRange: [
|
||||
sourceRange: topLevelRange(
|
||||
centerDetails.expr.elements[0].start,
|
||||
centerDetails.expr.elements[0].end,
|
||||
true,
|
||||
],
|
||||
centerDetails.expr.elements[0].end
|
||||
),
|
||||
pathToNode: pathToXArg,
|
||||
value: code.slice(
|
||||
centerDetails.expr.elements[0].start,
|
||||
@ -1083,11 +1083,10 @@ export const circle: SketchLineHelper = {
|
||||
isConstrained: isNotLiteralArrayOrStatic(
|
||||
centerDetails.expr.elements[1]
|
||||
),
|
||||
sourceRange: [
|
||||
sourceRange: topLevelRange(
|
||||
centerDetails.expr.elements[1].start,
|
||||
centerDetails.expr.elements[1].end,
|
||||
true,
|
||||
],
|
||||
centerDetails.expr.elements[1].end
|
||||
),
|
||||
pathToNode: pathToYArg,
|
||||
value: code.slice(
|
||||
centerDetails.expr.elements[1].start,
|
||||
@ -1763,7 +1762,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
code.slice(angle.start, angle.end),
|
||||
'angledLineThatIntersects',
|
||||
'angle',
|
||||
[angle.start, angle.end, true],
|
||||
topLevelRange(angle.start, angle.end),
|
||||
pathToAngleProp
|
||||
)
|
||||
)
|
||||
@ -1782,7 +1781,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
code.slice(offset.start, offset.end),
|
||||
'angledLineThatIntersects',
|
||||
'offset',
|
||||
[offset.start, offset.end, true],
|
||||
topLevelRange(offset.start, offset.end),
|
||||
pathToOffsetProp
|
||||
)
|
||||
)
|
||||
@ -1801,7 +1800,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
code.slice(tag.start, tag.end),
|
||||
'angledLineThatIntersects',
|
||||
'intersectTag',
|
||||
[tag.start, tag.end, true],
|
||||
topLevelRange(tag.start, tag.end),
|
||||
pathToTagProp
|
||||
)
|
||||
returnVal.push(info)
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
initPromise,
|
||||
sketchFromKclValue,
|
||||
SourceRange,
|
||||
topLevelRange,
|
||||
} from '../wasm'
|
||||
import {
|
||||
ConstraintType,
|
||||
@ -31,10 +32,10 @@ async function testingSwapSketchFnCall({
|
||||
constraintType: ConstraintType
|
||||
}): Promise<{
|
||||
newCode: string
|
||||
originalRange: [number, number, boolean]
|
||||
originalRange: SourceRange
|
||||
}> {
|
||||
const startIndex = inputCode.indexOf(callToSwap)
|
||||
const range: SourceRange = [startIndex, startIndex + callToSwap.length, true]
|
||||
const range = topLevelRange(startIndex, startIndex + callToSwap.length)
|
||||
const ast = assertParse(inputCode)
|
||||
|
||||
const execState = await enginelessExecutor(ast)
|
||||
@ -375,7 +376,10 @@ part001 = startSketchOn('XY')
|
||||
execState.memory.get('part001'),
|
||||
'part001'
|
||||
) as Sketch
|
||||
const _segment = getSketchSegmentFromSourceRange(sg, [index, index, true])
|
||||
const _segment = getSketchSegmentFromSourceRange(
|
||||
sg,
|
||||
topLevelRange(index, index)
|
||||
)
|
||||
if (err(_segment)) throw _segment
|
||||
const { __geoMeta, ...segment } = _segment.segment
|
||||
expect(segment).toEqual({
|
||||
@ -390,7 +394,7 @@ part001 = startSketchOn('XY')
|
||||
const index = code.indexOf('// segment-in-start') - 7
|
||||
const _segment = getSketchSegmentFromSourceRange(
|
||||
sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
|
||||
[index, index, true]
|
||||
topLevelRange(index, index)
|
||||
)
|
||||
if (err(_segment)) throw _segment
|
||||
const { __geoMeta, ...segment } = _segment.segment
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
Path,
|
||||
PathToNode,
|
||||
Expr,
|
||||
topLevelRange,
|
||||
} from '../wasm'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
@ -31,7 +32,7 @@ export function getSketchSegmentFromPathToNode(
|
||||
const node = nodeMeta.node
|
||||
if (!node || typeof node.start !== 'number' || !node.end)
|
||||
return new Error('no node found')
|
||||
const sourceRange: SourceRange = [node.start, node.end, true]
|
||||
const sourceRange = topLevelRange(node.start, node.end)
|
||||
return getSketchSegmentFromSourceRange(sketch, sourceRange)
|
||||
}
|
||||
export function getSketchSegmentFromSourceRange(
|
||||
|
@ -1,4 +1,11 @@
|
||||
import { assertParse, Expr, recast, initPromise, Program } from '../wasm'
|
||||
import {
|
||||
assertParse,
|
||||
Expr,
|
||||
recast,
|
||||
initPromise,
|
||||
Program,
|
||||
topLevelRange,
|
||||
} from '../wasm'
|
||||
import {
|
||||
getConstraintType,
|
||||
getTransformInfos,
|
||||
@ -125,7 +132,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
|
||||
)
|
||||
}
|
||||
const start = codeBeforeLine + line.indexOf('|> ' + 5)
|
||||
const range: [number, number, boolean] = [start, start, true]
|
||||
const range = topLevelRange(start, start)
|
||||
return {
|
||||
codeRef: codeRefFromRange(range, ast),
|
||||
}
|
||||
@ -297,7 +304,7 @@ part001 = startSketchOn('XY')
|
||||
const comment = ln.split('//')[1]
|
||||
const start = inputScript.indexOf('//' + comment) - 7
|
||||
return {
|
||||
codeRef: codeRefFromRange([start, start, true], ast),
|
||||
codeRef: codeRefFromRange(topLevelRange(start, start), ast),
|
||||
}
|
||||
})
|
||||
|
||||
@ -386,7 +393,7 @@ part001 = startSketchOn('XY')
|
||||
const comment = ln.split('//')[1]
|
||||
const start = inputScript.indexOf('//' + comment) - 7
|
||||
return {
|
||||
codeRef: codeRefFromRange([start, start, true], ast),
|
||||
codeRef: codeRefFromRange(topLevelRange(start, start), ast),
|
||||
}
|
||||
})
|
||||
|
||||
@ -446,7 +453,7 @@ part001 = startSketchOn('XY')
|
||||
const comment = ln.split('//')[1]
|
||||
const start = inputScript.indexOf('//' + comment) - 7
|
||||
return {
|
||||
codeRef: codeRefFromRange([start, start, true], ast),
|
||||
codeRef: codeRefFromRange(topLevelRange(start, start), ast),
|
||||
}
|
||||
})
|
||||
|
||||
@ -541,7 +548,7 @@ async function helperThing(
|
||||
const comment = ln.split('//')[1]
|
||||
const start = inputScript.indexOf('//' + comment) - 7
|
||||
return {
|
||||
codeRef: codeRefFromRange([start, start, true], ast),
|
||||
codeRef: codeRefFromRange(topLevelRange(start, start), ast),
|
||||
}
|
||||
})
|
||||
|
||||
@ -610,7 +617,7 @@ part001 = startSketchOn('XY')
|
||||
}
|
||||
const offsetIndex = index - 7
|
||||
const expectedConstraintLevel = getConstraintLevelFromSourceRange(
|
||||
[offsetIndex, offsetIndex, true],
|
||||
topLevelRange(offsetIndex, offsetIndex),
|
||||
ast
|
||||
)
|
||||
if (err(expectedConstraintLevel)) {
|
||||
|
@ -5,8 +5,9 @@ import {
|
||||
Literal,
|
||||
ArrayExpression,
|
||||
BinaryExpression,
|
||||
ArtifactGraph,
|
||||
} from './wasm'
|
||||
import { ArtifactGraph, filterArtifacts } from 'lang/std/artifactGraph'
|
||||
import { filterArtifacts } from 'lang/std/artifactGraph'
|
||||
import { isOverlap } from 'lib/utils'
|
||||
|
||||
export function updatePathToNodeFromMap(
|
||||
|
108
src/lang/wasm.ts
108
src/lang/wasm.ts
@ -44,17 +44,30 @@ import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
|
||||
import { Environment } from '../wasm-lib/kcl/bindings/Environment'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
|
||||
import { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
||||
import { SourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
||||
import { getAllCurrentSettings } from 'lib/settings/settingsUtils'
|
||||
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
||||
import { KclErrorWithOutputs } from 'wasm-lib/kcl/bindings/KclErrorWithOutputs'
|
||||
import { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
import { ArtifactId } from 'wasm-lib/kcl/bindings/ArtifactId'
|
||||
import { ArtifactCommand } from 'wasm-lib/kcl/bindings/ArtifactCommand'
|
||||
import { Artifact as RustArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
import { ArtifactId } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
import { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
import { ArtifactGraph as RustArtifactGraph } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
import { Artifact } from './std/artifactGraph'
|
||||
import { getNodePathFromSourceRange } from './queryAst'
|
||||
|
||||
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/ArtifactCommand'
|
||||
export type { ArtifactId } from 'wasm-lib/kcl/bindings/ArtifactId'
|
||||
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { ArtifactId } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { Cap as CapArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { CodeRef } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { EdgeCut } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { Path as PathArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { Plane as PlaneArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { Segment as SegmentArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { Solid2d as Solid2dArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { Sweep as SweepArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { SweepEdge } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { Wall as WallArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||
export type { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
|
||||
@ -76,7 +89,7 @@ export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
|
||||
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
|
||||
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
|
||||
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
|
||||
export type { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
||||
export type { SourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
||||
|
||||
export type SyntaxType =
|
||||
| 'Program'
|
||||
@ -105,35 +118,36 @@ export type { Solid } from '../wasm-lib/kcl/bindings/Solid'
|
||||
export type { KclValue } from '../wasm-lib/kcl/bindings/KclValue'
|
||||
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
||||
|
||||
/**
|
||||
* The first two items are the start and end points (byte offsets from the start of the file).
|
||||
* The third item is whether the source range belongs to the 'main' file, i.e., the file currently
|
||||
* being rendered/displayed in the editor (TODO we need to handle modules better in the frontend).
|
||||
*/
|
||||
export type SourceRange = [number, number, boolean]
|
||||
|
||||
/**
|
||||
* Convert a SourceRange as used inside the KCL interpreter into the above one for use in the
|
||||
* frontend (essentially we're eagerly checking whether the frontend should care about the SourceRange
|
||||
* so as not to expose details of the interpreter's current representation of module ids throughout
|
||||
* the frontend).
|
||||
*/
|
||||
export function sourceRangeFromRust(s: RustSourceRange): SourceRange {
|
||||
return [s[0], s[1], s[2] === 0]
|
||||
export function sourceRangeFromRust(s: SourceRange): SourceRange {
|
||||
return [s[0], s[1], s[2]]
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default SourceRange for testing or as a placeholder.
|
||||
*/
|
||||
export function defaultSourceRange(): SourceRange {
|
||||
return [0, 0, true]
|
||||
return [0, 0, 0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default RustSourceRange for testing or as a placeholder.
|
||||
* Create a SourceRange for the top-level module.
|
||||
*/
|
||||
export function defaultRustSourceRange(): RustSourceRange {
|
||||
return [0, 0, 0]
|
||||
export function topLevelRange(start: number, end: number): SourceRange {
|
||||
return [start, end, 0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this source range is from the file being executed. Returns
|
||||
* false if it's from a file that was imported.
|
||||
*/
|
||||
export function isTopLevelModule(range: SourceRange): boolean {
|
||||
return range[2] === 0
|
||||
}
|
||||
|
||||
export const wasmUrl = () => {
|
||||
@ -234,7 +248,8 @@ export const parse = (code: string | Error): ParseResult | Error => {
|
||||
parsed.msg,
|
||||
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||
[],
|
||||
[]
|
||||
[],
|
||||
defaultArtifactGraph()
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -258,8 +273,9 @@ export const isPathToNodeNumber = (
|
||||
export interface ExecState {
|
||||
memory: ProgramMemory
|
||||
operations: Operation[]
|
||||
artifacts: { [key in ArtifactId]?: Artifact }
|
||||
artifacts: { [key in ArtifactId]?: RustArtifact }
|
||||
artifactCommands: ArtifactCommand[]
|
||||
artifactGraph: ArtifactGraph
|
||||
}
|
||||
|
||||
/**
|
||||
@ -272,18 +288,53 @@ export function emptyExecState(): ExecState {
|
||||
operations: [],
|
||||
artifacts: {},
|
||||
artifactCommands: [],
|
||||
artifactGraph: defaultArtifactGraph(),
|
||||
}
|
||||
}
|
||||
|
||||
function execStateFromRust(execOutcome: RustExecOutcome): ExecState {
|
||||
function execStateFromRust(
|
||||
execOutcome: RustExecOutcome,
|
||||
program: Node<Program>
|
||||
): ExecState {
|
||||
const artifactGraph = rustArtifactGraphToMap(execOutcome.artifactGraph)
|
||||
// We haven't ported pathToNode logic to Rust yet, so we need to fill it in.
|
||||
for (const [id, artifact] of artifactGraph) {
|
||||
if (!artifact) continue
|
||||
if (!('codeRef' in artifact)) continue
|
||||
const pathToNode = getNodePathFromSourceRange(
|
||||
program,
|
||||
sourceRangeFromRust(artifact.codeRef.range)
|
||||
)
|
||||
artifact.codeRef.pathToNode = pathToNode
|
||||
}
|
||||
|
||||
return {
|
||||
memory: ProgramMemory.fromRaw(execOutcome.memory),
|
||||
operations: execOutcome.operations,
|
||||
artifacts: execOutcome.artifacts,
|
||||
artifactCommands: execOutcome.artifactCommands,
|
||||
artifactGraph,
|
||||
}
|
||||
}
|
||||
|
||||
export type ArtifactGraph = Map<ArtifactId, Artifact>
|
||||
|
||||
function rustArtifactGraphToMap(
|
||||
rustArtifactGraph: RustArtifactGraph
|
||||
): ArtifactGraph {
|
||||
const map = new Map<ArtifactId, Artifact>()
|
||||
for (const [id, artifact] of Object.entries(rustArtifactGraph.map)) {
|
||||
if (!artifact) continue
|
||||
map.set(id, artifact)
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
export function defaultArtifactGraph(): ArtifactGraph {
|
||||
return new Map()
|
||||
}
|
||||
|
||||
interface Memory {
|
||||
[key: string]: KclValue | undefined
|
||||
}
|
||||
@ -543,7 +594,7 @@ export const executor = async (
|
||||
engineCommandManager,
|
||||
fileSystemManager
|
||||
)
|
||||
return execStateFromRust(execOutcome)
|
||||
return execStateFromRust(execOutcome, node)
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
||||
@ -552,7 +603,8 @@ export const executor = async (
|
||||
parsed.error.msg,
|
||||
sourceRangeFromRust(parsed.error.sourceRanges[0]),
|
||||
parsed.operations,
|
||||
parsed.artifactCommands
|
||||
parsed.artifactCommands,
|
||||
rustArtifactGraphToMap(parsed.artifactGraph)
|
||||
)
|
||||
|
||||
return Promise.reject(kclError)
|
||||
@ -613,7 +665,8 @@ export const modifyAstForSketch = async (
|
||||
parsed.msg,
|
||||
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||
[],
|
||||
[]
|
||||
[],
|
||||
defaultArtifactGraph()
|
||||
)
|
||||
|
||||
console.log(kclError)
|
||||
@ -683,7 +736,8 @@ export function programMemoryInit(): ProgramMemory | Error {
|
||||
parsed.msg,
|
||||
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||
[],
|
||||
[]
|
||||
[],
|
||||
defaultArtifactGraph()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -280,7 +280,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['solid2D', 'segment'],
|
||||
selectionTypes: ['solid2d', 'segment'],
|
||||
multiple: false, // TODO: multiple selection
|
||||
required: true,
|
||||
skip: true,
|
||||
@ -312,7 +312,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
args: {
|
||||
profile: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['solid2D'],
|
||||
selectionTypes: ['solid2d'],
|
||||
required: true,
|
||||
skip: true,
|
||||
multiple: false,
|
||||
@ -337,7 +337,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['solid2D'],
|
||||
selectionTypes: ['solid2d'],
|
||||
multiple: true,
|
||||
required: true,
|
||||
skip: false,
|
||||
@ -373,7 +373,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['solid2D', 'segment'],
|
||||
selectionTypes: ['solid2d', 'segment'],
|
||||
multiple: false, // TODO: multiple selection
|
||||
required: true,
|
||||
skip: true,
|
||||
@ -578,7 +578,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: [
|
||||
'solid2D',
|
||||
'solid2d',
|
||||
'segment',
|
||||
'sweepEdge',
|
||||
'cap',
|
||||
|
@ -116,16 +116,16 @@ export const loftValidator = async ({
|
||||
}
|
||||
const { selection } = data
|
||||
|
||||
if (selection.graphSelections.some((s) => s.artifact?.type !== 'solid2D')) {
|
||||
return 'Unable to loft, some selection are not solid2Ds'
|
||||
if (selection.graphSelections.some((s) => s.artifact?.type !== 'solid2d')) {
|
||||
return 'Unable to loft, some selection are not solid2ds'
|
||||
}
|
||||
|
||||
const sectionIds = data.selection.graphSelections.flatMap((s) =>
|
||||
s.artifact?.type === 'solid2D' ? s.artifact.pathId : []
|
||||
s.artifact?.type === 'solid2d' ? s.artifact.pathId : []
|
||||
)
|
||||
|
||||
if (sectionIds.length < 2) {
|
||||
return 'Unable to loft, selection contains less than two solid2Ds'
|
||||
return 'Unable to loft, selection contains less than two solid2ds'
|
||||
}
|
||||
|
||||
const loftCommand = async () => {
|
||||
|
58
src/lib/desktopFS.test.ts
Normal file
58
src/lib/desktopFS.test.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { getUniqueProjectName } from './desktopFS'
|
||||
import { FileEntry } from './project'
|
||||
|
||||
/** Create a dummy project */
|
||||
function project(name: string, children?: FileEntry[]): FileEntry {
|
||||
return {
|
||||
name,
|
||||
children: children || [
|
||||
{ name: 'main.kcl', children: null, path: 'main.kcl' },
|
||||
],
|
||||
path: `/projects/${name}`,
|
||||
}
|
||||
}
|
||||
|
||||
describe(`Getting unique project names`, () => {
|
||||
it(`should return the same name if no conflicts`, () => {
|
||||
const projectName = 'new-project'
|
||||
const projects = [project('existing-project'), project('another-project')]
|
||||
const result = getUniqueProjectName(projectName, projects)
|
||||
expect(result).toBe(projectName)
|
||||
})
|
||||
it(`should return a unique name if there is a conflict`, () => {
|
||||
const projectName = 'existing-project'
|
||||
const projects = [project('existing-project'), project('another-project')]
|
||||
const result = getUniqueProjectName(projectName, projects)
|
||||
expect(result).toBe('existing-project-1')
|
||||
})
|
||||
it(`should increment an ending index until a unique one is found`, () => {
|
||||
const projectName = 'existing-project-1'
|
||||
const projects = [
|
||||
project('existing-project'),
|
||||
project('existing-project-1'),
|
||||
project('existing-project-2'),
|
||||
]
|
||||
const result = getUniqueProjectName(projectName, projects)
|
||||
expect(result).toBe('existing-project-3')
|
||||
})
|
||||
it(`should prefer the formatting of the index identifier if present`, () => {
|
||||
const projectName = 'existing-project-$nn'
|
||||
const projects = [
|
||||
project('existing-project'),
|
||||
project('existing-project-1'),
|
||||
project('existing-project-2'),
|
||||
]
|
||||
const result = getUniqueProjectName(projectName, projects)
|
||||
expect(result).toBe('existing-project-03')
|
||||
})
|
||||
it(`be able to get an incrementing index regardless of padding zeroes`, () => {
|
||||
const projectName = 'existing-project-$nn'
|
||||
const projects = [
|
||||
project('existing-project'),
|
||||
project('existing-project-01'),
|
||||
project('existing-project-2'),
|
||||
]
|
||||
const result = getUniqueProjectName(projectName, projects)
|
||||
expect(result).toBe('existing-project-03')
|
||||
})
|
||||
})
|
@ -54,8 +54,10 @@ export function getNextProjectIndex(
|
||||
const matches = projects.map((project) => project.name?.match(regex))
|
||||
const indices = matches
|
||||
.filter(Boolean)
|
||||
.map((match) => match![1])
|
||||
.map(Number)
|
||||
.map((match) => (match !== null ? match[1] : '-1'))
|
||||
.map((maybeMatchIndex) => {
|
||||
return parseInt(maybeMatchIndex || '0', 10)
|
||||
})
|
||||
const maxIndex = Math.max(...indices, -1)
|
||||
return maxIndex + 1
|
||||
}
|
||||
@ -83,6 +85,33 @@ export function doesProjectNameNeedInterpolated(projectName: string) {
|
||||
return projectName.includes(INDEX_IDENTIFIER)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a target name, which may include our magic index interpolation string,
|
||||
* and a list of projects, return a unique name that doesn't conflict with any
|
||||
* of the existing projects, incrementing any ending number if necessary.
|
||||
* @param name
|
||||
* @param projects
|
||||
* @returns
|
||||
*/
|
||||
export function getUniqueProjectName(name: string, projects: FileEntry[]) {
|
||||
// The name may have our magic index interpolation string in it
|
||||
const needsInterpolation = doesProjectNameNeedInterpolated(name)
|
||||
|
||||
if (needsInterpolation) {
|
||||
const nextIndex = getNextProjectIndex(name, projects)
|
||||
return interpolateProjectNameWithIndex(name, nextIndex)
|
||||
} else {
|
||||
let newName = name
|
||||
while (projects.some((project) => project.name === newName)) {
|
||||
const nameEndsWithNumber = newName.match(/\d+$/)
|
||||
newName = nameEndsWithNumber
|
||||
? newName.replace(/\d+$/, (num) => `${parseInt(num, 10) + 1}`)
|
||||
: `${name}-1`
|
||||
}
|
||||
return newName
|
||||
}
|
||||
}
|
||||
|
||||
function escapeRegExpChars(string: string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { defaultRustSourceRange } from 'lang/wasm'
|
||||
import { defaultSourceRange } from 'lang/wasm'
|
||||
import { filterOperations } from './operations'
|
||||
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
||||
|
||||
@ -8,7 +8,7 @@ function stdlib(name: string): Operation {
|
||||
name,
|
||||
unlabeledArg: null,
|
||||
labeledArgs: {},
|
||||
sourceRange: defaultRustSourceRange(),
|
||||
sourceRange: defaultSourceRange(),
|
||||
isError: false,
|
||||
}
|
||||
}
|
||||
@ -17,10 +17,10 @@ function userCall(name: string): Operation {
|
||||
return {
|
||||
type: 'UserDefinedFunctionCall',
|
||||
name,
|
||||
functionSourceRange: defaultRustSourceRange(),
|
||||
functionSourceRange: defaultSourceRange(),
|
||||
unlabeledArg: null,
|
||||
labeledArgs: {},
|
||||
sourceRange: defaultRustSourceRange(),
|
||||
sourceRange: defaultSourceRange(),
|
||||
}
|
||||
}
|
||||
function userReturn(): Operation {
|
||||
|
@ -3,8 +3,8 @@ import { VITE_KC_API_BASE_URL } from 'env'
|
||||
import crossPlatformFetch from './crossPlatformFetch'
|
||||
import { err, reportRejection } from './trap'
|
||||
import { Selections } from './selections'
|
||||
import { ArtifactGraph, getArtifactOfTypes } from 'lang/std/artifactGraph'
|
||||
import { SourceRange } from 'lang/wasm'
|
||||
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
||||
import { ArtifactGraph, SourceRange, topLevelRange } from 'lang/wasm'
|
||||
import toast from 'react-hot-toast'
|
||||
import { codeManager, editorManager, kclManager } from './singletons'
|
||||
import { ToastPromptToEditCadSuccess } from 'components/ToastTextToCad'
|
||||
@ -334,7 +334,7 @@ const reBuildNewCodeWithRanges = (
|
||||
} else if (change.added && !change.removed) {
|
||||
const start = newCodeWithRanges.length
|
||||
const end = start + change.value.length
|
||||
insertRanges.push([start, end, true])
|
||||
insertRanges.push(topLevelRange(start, end))
|
||||
newCodeWithRanges += change.value
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
SourceRange,
|
||||
Expr,
|
||||
defaultSourceRange,
|
||||
topLevelRange,
|
||||
} from 'lang/wasm'
|
||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { isNonNullable, uuidv4 } from 'lib/utils'
|
||||
@ -63,7 +64,7 @@ type Selection__old =
|
||||
| 'line-end'
|
||||
| 'line-mid'
|
||||
| 'extrude-wall'
|
||||
| 'solid2D'
|
||||
| 'solid2d'
|
||||
| 'start-cap'
|
||||
| 'end-cap'
|
||||
| 'point'
|
||||
@ -103,13 +104,13 @@ function convertSelectionToOld(selection: Selection): Selection__old | null {
|
||||
// return {} as Selection__old
|
||||
// TODO implementation
|
||||
const _artifact = selection.artifact
|
||||
if (_artifact?.type === 'solid2D') {
|
||||
if (_artifact?.type === 'solid2d') {
|
||||
const codeRef = getSolid2dCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return { range: codeRef.range, type: 'solid2D' }
|
||||
return { range: codeRef.range, type: 'solid2d' }
|
||||
}
|
||||
if (_artifact?.type === 'cap') {
|
||||
const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
|
||||
@ -269,7 +270,7 @@ export function getEventForSegmentSelection(
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: {
|
||||
codeRef: {
|
||||
range: [node.node.start, node.node.end, true],
|
||||
range: topLevelRange(node.node.start, node.node.end),
|
||||
pathToNode: group.userData.pathToNode,
|
||||
},
|
||||
},
|
||||
@ -381,10 +382,13 @@ export function processCodeMirrorRanges({
|
||||
if (!isChange) return null
|
||||
const codeBasedSelections: Selections['graphSelections'] =
|
||||
codeMirrorRanges.map(({ from, to }) => {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, [from, to, true])
|
||||
const pathToNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(from, to)
|
||||
)
|
||||
return {
|
||||
codeRef: {
|
||||
range: [from, to, true],
|
||||
range: topLevelRange(from, to),
|
||||
pathToNode,
|
||||
},
|
||||
}
|
||||
@ -447,7 +451,10 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
||||
if (err(nodeMeta)) return
|
||||
const node = nodeMeta.node
|
||||
const groupHasCursor = codeBasedSelections.some((selection) => {
|
||||
return isOverlap(selection?.codeRef?.range, [node.start, node.end, true])
|
||||
return isOverlap(
|
||||
selection?.codeRef?.range,
|
||||
topLevelRange(node.start, node.end)
|
||||
)
|
||||
})
|
||||
|
||||
const color = groupHasCursor
|
||||
@ -575,7 +582,7 @@ export function getSelectionTypeDisplayText(
|
||||
([type, count]) =>
|
||||
`${count} ${type
|
||||
.replace('wall', 'face')
|
||||
.replace('solid2D', 'face')
|
||||
.replace('solid2d', 'face')
|
||||
.replace('segment', 'face')}${count > 1 ? 's' : ''}`
|
||||
)
|
||||
.toArray()
|
||||
@ -650,7 +657,7 @@ export function codeToIdSelections(
|
||||
const artifact = engineCommandManager.artifactGraph.get(
|
||||
entry.artifact.solid2dId || ''
|
||||
)
|
||||
if (artifact?.type !== 'solid2D') {
|
||||
if (artifact?.type !== 'solid2d') {
|
||||
bestCandidate = {
|
||||
artifact: entry.artifact,
|
||||
selection,
|
||||
@ -873,7 +880,7 @@ export function updateSelections(
|
||||
return {
|
||||
artifact: artifact,
|
||||
codeRef: {
|
||||
range: [node.start, node.end, true],
|
||||
range: topLevelRange(node.start, node.end),
|
||||
pathToNode: pathToNode,
|
||||
},
|
||||
}
|
||||
@ -887,7 +894,7 @@ export function updateSelections(
|
||||
if (err(node)) return node
|
||||
pathToNodeBasedSelections.push({
|
||||
codeRef: {
|
||||
range: [node.node.start, node.node.end, true],
|
||||
range: topLevelRange(node.node.start, node.node.end),
|
||||
pathToNode: pathToNode,
|
||||
},
|
||||
})
|
||||
|
@ -6,16 +6,16 @@ import {
|
||||
hasLeadingZero,
|
||||
hasDigitsLeftOfDecimal,
|
||||
} from './utils'
|
||||
import { SourceRange } from '../lang/wasm'
|
||||
import { SourceRange, topLevelRange } from '../lang/wasm'
|
||||
|
||||
describe('testing isOverlapping', () => {
|
||||
testBothOrders([0, 3, true], [3, 10, true])
|
||||
testBothOrders([0, 5, true], [3, 4, true])
|
||||
testBothOrders([0, 5, true], [5, 10, true])
|
||||
testBothOrders([0, 5, true], [6, 10, true], false)
|
||||
testBothOrders([0, 5, true], [-1, 1, true])
|
||||
testBothOrders([0, 5, true], [-1, 0, true])
|
||||
testBothOrders([0, 5, true], [-2, -1, true], false)
|
||||
testBothOrders(topLevelRange(0, 3), topLevelRange(3, 10))
|
||||
testBothOrders(topLevelRange(0, 5), topLevelRange(3, 4))
|
||||
testBothOrders(topLevelRange(0, 5), topLevelRange(5, 10))
|
||||
testBothOrders(topLevelRange(0, 5), topLevelRange(6, 10), false)
|
||||
testBothOrders(topLevelRange(0, 5), topLevelRange(-1, 1))
|
||||
testBothOrders(topLevelRange(0, 5), topLevelRange(-1, 0))
|
||||
testBothOrders(topLevelRange(0, 5), topLevelRange(-2, -1), false)
|
||||
})
|
||||
|
||||
function testBothOrders(a: SourceRange, b: SourceRange, result = true) {
|
||||
|
@ -161,7 +161,7 @@ const Home = () => {
|
||||
}}
|
||||
data-testid="home-new-file"
|
||||
>
|
||||
New project
|
||||
Create project
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
|
7
src/wasm-lib/Cargo.lock
generated
7
src/wasm-lib/Cargo.lock
generated
@ -1382,12 +1382,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iai"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71a816c97c42258aa5834d07590b718b4c9a598944cd39a52dc25b351185d678"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
@ -1739,7 +1733,6 @@ dependencies = [
|
||||
"gltf-json",
|
||||
"handlebars",
|
||||
"http 1.2.0",
|
||||
"iai",
|
||||
"image",
|
||||
"indexmap 2.7.0",
|
||||
"insta",
|
||||
|
@ -17,6 +17,7 @@ use kittycad_modeling_cmds::{
|
||||
websocket::{ModelingBatch, ModelingCmdReq, OkWebSocketResponseData, WebSocketRequest, WebSocketResponse},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
const CPP_PREFIX: &str = "const double scaleFactor = 100;\n";
|
||||
const NEED_PLANES: bool = true;
|
||||
@ -369,6 +370,10 @@ impl kcl_lib::EngineManager for EngineConnection {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
|
||||
IndexMap::new()
|
||||
}
|
||||
|
||||
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
|
||||
Vec::new()
|
||||
}
|
||||
|
@ -113,7 +113,6 @@ base64 = "0.22.1"
|
||||
criterion = { version = "0.5.1", features = ["async_tokio"] }
|
||||
expectorate = "1.1.0"
|
||||
handlebars = "6.3.0"
|
||||
iai = "0.1"
|
||||
image = { version = "0.25.5", default-features = false, features = ["png"] }
|
||||
insta = { version = "1.41.1", features = ["json", "filters", "redactions"] }
|
||||
itertools = "0.13.0"
|
||||
@ -129,10 +128,6 @@ workspace = true
|
||||
name = "compiler_benchmark_criterion"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "compiler_benchmark_iai"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "digest_benchmark"
|
||||
harness = false
|
||||
@ -142,15 +137,7 @@ name = "lsp_semantic_tokens_benchmark_criterion"
|
||||
harness = false
|
||||
required-features = ["lsp-test-util"]
|
||||
|
||||
[[bench]]
|
||||
name = "lsp_semantic_tokens_benchmark_iai"
|
||||
harness = false
|
||||
required-features = ["lsp-test-util"]
|
||||
|
||||
[[bench]]
|
||||
name = "executor_benchmark_criterion"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "executor_benchmark_iai"
|
||||
harness = false
|
||||
|
@ -1,35 +0,0 @@
|
||||
use iai::black_box;
|
||||
|
||||
pub fn parse(program: &str) {
|
||||
black_box(kcl_lib::Program::parse(program).unwrap());
|
||||
}
|
||||
|
||||
fn parse_kitt() {
|
||||
parse(KITT_PROGRAM)
|
||||
}
|
||||
fn parse_pipes() {
|
||||
parse(PIPES_PROGRAM)
|
||||
}
|
||||
fn parse_cube() {
|
||||
parse(CUBE_PROGRAM)
|
||||
}
|
||||
fn parse_math() {
|
||||
parse(MATH_PROGRAM)
|
||||
}
|
||||
fn parse_lsystem() {
|
||||
parse(LSYSTEM_PROGRAM)
|
||||
}
|
||||
|
||||
iai::main! {
|
||||
parse_kitt,
|
||||
parse_pipes,
|
||||
parse_cube,
|
||||
parse_math,
|
||||
parse_lsystem,
|
||||
}
|
||||
|
||||
const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl");
|
||||
const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
|
||||
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
|
||||
const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl");
|
||||
const LSYSTEM_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl");
|
@ -1,27 +0,0 @@
|
||||
use iai::black_box;
|
||||
|
||||
async fn execute_server_rack_heavy() {
|
||||
let code = SERVER_RACK_HEAVY_PROGRAM;
|
||||
black_box(
|
||||
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
async fn execute_server_rack_lite() {
|
||||
let code = SERVER_RACK_LITE_PROGRAM;
|
||||
black_box(
|
||||
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
iai::main! {
|
||||
execute_server_rack_lite,
|
||||
execute_server_rack_heavy,
|
||||
}
|
||||
|
||||
const SERVER_RACK_HEAVY_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-heavy.kcl");
|
||||
const SERVER_RACK_LITE_PROGRAM: &str = include_str!("../../tests/executor/inputs/server-rack-lite.kcl");
|
@ -1,45 +0,0 @@
|
||||
use iai::black_box;
|
||||
use kcl_lib::kcl_lsp_server;
|
||||
use tower_lsp::LanguageServer;
|
||||
|
||||
async fn kcl_lsp_semantic_tokens(code: &str) {
|
||||
let server = kcl_lsp_server(false).await.unwrap();
|
||||
|
||||
// Send open file.
|
||||
server
|
||||
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentItem {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: code.to_string(),
|
||||
},
|
||||
})
|
||||
.await;
|
||||
|
||||
// Send semantic tokens request.
|
||||
black_box(
|
||||
server
|
||||
.semantic_tokens_full(tower_lsp::lsp_types::SemanticTokensParams {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
},
|
||||
partial_result_params: Default::default(),
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
async fn semantic_tokens_global_tags() {
|
||||
let code = GLOBAL_TAGS_FILE;
|
||||
kcl_lsp_semantic_tokens(code).await;
|
||||
}
|
||||
|
||||
iai::main! {
|
||||
semantic_tokens_global_tags,
|
||||
}
|
||||
|
||||
const GLOBAL_TAGS_FILE: &str = include_str!("../../tests/executor/inputs/global-tags.kcl");
|
@ -383,6 +383,16 @@ impl EngineManager for EngineConnection {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
|
||||
self.responses
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let (k, v) = entry.pair();
|
||||
(*k, v.clone())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
|
||||
let mut artifact_commands = self.artifact_commands.lock().unwrap();
|
||||
std::mem::take(&mut *artifact_commands)
|
||||
|
@ -77,6 +77,10 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
|
||||
IndexMap::new()
|
||||
}
|
||||
|
||||
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
|
||||
let mut artifact_commands = self.artifact_commands.lock().unwrap();
|
||||
std::mem::take(&mut *artifact_commands)
|
||||
|
@ -52,6 +52,7 @@ pub struct EngineConnection {
|
||||
manager: Arc<EngineCommandManager>,
|
||||
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
|
||||
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
responses: Arc<Mutex<IndexMap<Uuid, WebSocketResponse>>>,
|
||||
artifact_commands: Arc<Mutex<Vec<ArtifactCommand>>>,
|
||||
execution_kind: Arc<Mutex<ExecutionKind>>,
|
||||
}
|
||||
@ -66,6 +67,7 @@ impl EngineConnection {
|
||||
manager: Arc::new(manager),
|
||||
batch: Arc::new(Mutex::new(Vec::new())),
|
||||
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
||||
responses: Arc::new(Mutex::new(IndexMap::new())),
|
||||
artifact_commands: Arc::new(Mutex::new(Vec::new())),
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
@ -106,6 +108,11 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
|
||||
let responses = self.responses.lock().unwrap();
|
||||
responses.clone()
|
||||
}
|
||||
|
||||
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
|
||||
let mut artifact_commands = self.artifact_commands.lock().unwrap();
|
||||
std::mem::take(&mut *artifact_commands)
|
||||
@ -265,6 +272,9 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut responses = self.responses.lock().unwrap();
|
||||
responses.insert(id, ws_result.clone());
|
||||
|
||||
Ok(ws_result)
|
||||
}
|
||||
|
||||
|
@ -67,6 +67,9 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
/// Get the batch of end commands to be sent to the engine.
|
||||
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>;
|
||||
|
||||
/// Get the command responses from the engine.
|
||||
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse>;
|
||||
|
||||
/// Take the artifact commands generated up to this point and clear them.
|
||||
fn take_artifact_commands(&self) -> Vec<ArtifactCommand>;
|
||||
|
||||
|
@ -3,7 +3,7 @@ use thiserror::Error;
|
||||
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
|
||||
|
||||
use crate::{
|
||||
execution::{ArtifactCommand, Operation},
|
||||
execution::{ArtifactCommand, ArtifactGraph, Operation},
|
||||
lsp::IntoDiagnostic,
|
||||
source_range::{ModuleId, SourceRange},
|
||||
};
|
||||
@ -114,14 +114,21 @@ pub struct KclErrorWithOutputs {
|
||||
pub error: KclError,
|
||||
pub operations: Vec<Operation>,
|
||||
pub artifact_commands: Vec<ArtifactCommand>,
|
||||
pub artifact_graph: ArtifactGraph,
|
||||
}
|
||||
|
||||
impl KclErrorWithOutputs {
|
||||
pub fn new(error: KclError, operations: Vec<Operation>, artifact_commands: Vec<ArtifactCommand>) -> Self {
|
||||
pub fn new(
|
||||
error: KclError,
|
||||
operations: Vec<Operation>,
|
||||
artifact_commands: Vec<ArtifactCommand>,
|
||||
artifact_graph: ArtifactGraph,
|
||||
) -> Self {
|
||||
Self {
|
||||
error,
|
||||
operations,
|
||||
artifact_commands,
|
||||
artifact_graph,
|
||||
}
|
||||
}
|
||||
pub fn no_outputs(error: KclError) -> Self {
|
||||
@ -129,6 +136,7 @@ impl KclErrorWithOutputs {
|
||||
error,
|
||||
operations: Default::default(),
|
||||
artifact_commands: Default::default(),
|
||||
artifact_graph: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,29 @@
|
||||
use kittycad_modeling_cmds::ModelingCmd;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use fnv::FnvHashMap;
|
||||
use indexmap::IndexMap;
|
||||
use kittycad_modeling_cmds::{
|
||||
self as kcmc,
|
||||
id::ModelingCmdId,
|
||||
ok_response::OkModelingCmdResponse,
|
||||
shared::ExtrusionFaceCapType,
|
||||
websocket::{BatchResponse, OkWebSocketResponseData, WebSocketResponse},
|
||||
EnableSketchMode, ModelingCmd, SketchModeDisable,
|
||||
};
|
||||
use serde::{ser::SerializeSeq, Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::SourceRange;
|
||||
use crate::{
|
||||
parsing::ast::types::{Node, Program},
|
||||
KclError, SourceRange,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod mermaid_tests;
|
||||
|
||||
/// A command that may create or update artifacts on the TS side. Because
|
||||
/// engine commands are batched, we don't have the response yet when these are
|
||||
/// created.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ArtifactCommand {
|
||||
/// Identifier of the command that can be matched with its response.
|
||||
@ -22,8 +36,8 @@ pub struct ArtifactCommand {
|
||||
pub command: ModelingCmd,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
pub struct ArtifactId(Uuid);
|
||||
|
||||
impl ArtifactId {
|
||||
@ -56,22 +70,835 @@ impl From<&ArtifactId> for Uuid {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Artifact {
|
||||
pub id: ArtifactId,
|
||||
#[serde(flatten)]
|
||||
pub inner: ArtifactInner,
|
||||
pub source_range: SourceRange,
|
||||
impl From<ModelingCmdId> for ArtifactId {
|
||||
fn from(id: ModelingCmdId) -> Self {
|
||||
Self::new(*id.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ArtifactInner {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
StartSketchOnFace { face_id: Uuid },
|
||||
#[serde(rename_all = "camelCase")]
|
||||
StartSketchOnPlane { plane_id: Uuid },
|
||||
impl From<&ModelingCmdId> for ArtifactId {
|
||||
fn from(id: &ModelingCmdId) -> Self {
|
||||
Self::new(*id.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub type DummyPathToNode = Vec<()>;
|
||||
|
||||
fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
// Always output an empty array, for now.
|
||||
let seq = serializer.serialize_seq(Some(0))?;
|
||||
seq.end()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeRef {
|
||||
pub range: SourceRange,
|
||||
// TODO: We should implement this in Rust.
|
||||
#[serde(default, serialize_with = "serialize_dummy_path_to_node")]
|
||||
#[ts(type = "Array<[string | number, string]>")]
|
||||
pub path_to_node: DummyPathToNode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Plane {
|
||||
pub id: ArtifactId,
|
||||
pub path_ids: Vec<ArtifactId>,
|
||||
pub code_ref: CodeRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Path {
|
||||
pub id: ArtifactId,
|
||||
pub plane_id: ArtifactId,
|
||||
pub seg_ids: Vec<ArtifactId>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub sweep_id: Option<ArtifactId>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub solid2d_id: Option<ArtifactId>,
|
||||
pub code_ref: CodeRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Segment {
|
||||
pub id: ArtifactId,
|
||||
pub path_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub surface_id: Option<ArtifactId>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub edge_ids: Vec<ArtifactId>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub edge_cut_id: Option<ArtifactId>,
|
||||
pub code_ref: CodeRef,
|
||||
}
|
||||
|
||||
/// A sweep is a more generic term for extrude, revolve, loft, and sweep.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Sweep {
|
||||
pub id: ArtifactId,
|
||||
pub sub_type: SweepSubType,
|
||||
pub path_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub surface_ids: Vec<ArtifactId>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub edge_ids: Vec<ArtifactId>,
|
||||
pub code_ref: CodeRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum SweepSubType {
|
||||
Extrusion,
|
||||
Revolve,
|
||||
Loft,
|
||||
Sweep,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Solid2d {
|
||||
pub id: ArtifactId,
|
||||
pub path_id: ArtifactId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Wall {
|
||||
pub id: ArtifactId,
|
||||
pub seg_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub edge_cut_edge_ids: Vec<ArtifactId>,
|
||||
pub sweep_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub path_ids: Vec<ArtifactId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Cap {
|
||||
pub id: ArtifactId,
|
||||
pub sub_type: CapSubType,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub edge_cut_edge_ids: Vec<ArtifactId>,
|
||||
pub sweep_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub path_ids: Vec<ArtifactId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum CapSubType {
|
||||
Start,
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SweepEdge {
|
||||
pub id: ArtifactId,
|
||||
pub sub_type: SweepEdgeSubType,
|
||||
pub seg_id: ArtifactId,
|
||||
pub sweep_id: ArtifactId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum SweepEdgeSubType {
|
||||
Opposite,
|
||||
Adjacent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EdgeCut {
|
||||
pub id: ArtifactId,
|
||||
pub sub_type: EdgeCutSubType,
|
||||
pub consumed_edge_id: ArtifactId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub edge_ids: Vec<ArtifactId>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub surface_id: Option<ArtifactId>,
|
||||
pub code_ref: CodeRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EdgeCutSubType {
|
||||
Fillet,
|
||||
Chamfer,
|
||||
}
|
||||
|
||||
impl From<kcmc::shared::CutType> for EdgeCutSubType {
|
||||
fn from(cut_type: kcmc::shared::CutType) -> Self {
|
||||
match cut_type {
|
||||
kcmc::shared::CutType::Fillet => EdgeCutSubType::Fillet,
|
||||
kcmc::shared::CutType::Chamfer => EdgeCutSubType::Chamfer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EdgeCutEdge {
|
||||
pub id: ArtifactId,
|
||||
pub edge_cut_id: ArtifactId,
|
||||
pub surface_id: ArtifactId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum Artifact {
|
||||
Plane(Plane),
|
||||
Path(Path),
|
||||
Segment(Segment),
|
||||
Solid2d(Solid2d),
|
||||
#[serde(rename_all = "camelCase")]
|
||||
StartSketchOnFace {
|
||||
id: ArtifactId,
|
||||
face_id: Uuid,
|
||||
source_range: SourceRange,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
StartSketchOnPlane {
|
||||
id: ArtifactId,
|
||||
plane_id: Uuid,
|
||||
source_range: SourceRange,
|
||||
},
|
||||
Sweep(Sweep),
|
||||
Wall(Wall),
|
||||
Cap(Cap),
|
||||
SweepEdge(SweepEdge),
|
||||
EdgeCut(EdgeCut),
|
||||
EdgeCutEdge(EdgeCutEdge),
|
||||
}
|
||||
|
||||
impl Artifact {
|
||||
pub(crate) fn id(&self) -> ArtifactId {
|
||||
match self {
|
||||
Artifact::Plane(a) => a.id,
|
||||
Artifact::Path(a) => a.id,
|
||||
Artifact::Segment(a) => a.id,
|
||||
Artifact::Solid2d(a) => a.id,
|
||||
Artifact::StartSketchOnFace { id, .. } => *id,
|
||||
Artifact::StartSketchOnPlane { id, .. } => *id,
|
||||
Artifact::Sweep(a) => a.id,
|
||||
Artifact::Wall(a) => a.id,
|
||||
Artifact::Cap(a) => a.id,
|
||||
Artifact::SweepEdge(a) => a.id,
|
||||
Artifact::EdgeCut(a) => a.id,
|
||||
Artifact::EdgeCutEdge(a) => a.id,
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub(crate) fn code_ref(&self) -> Option<&CodeRef> {
|
||||
match self {
|
||||
Artifact::Plane(a) => Some(&a.code_ref),
|
||||
Artifact::Path(a) => Some(&a.code_ref),
|
||||
Artifact::Segment(a) => Some(&a.code_ref),
|
||||
Artifact::Solid2d(_) => None,
|
||||
// TODO: We should add code refs for these.
|
||||
Artifact::StartSketchOnFace { .. } => None,
|
||||
Artifact::StartSketchOnPlane { .. } => None,
|
||||
Artifact::Sweep(a) => Some(&a.code_ref),
|
||||
Artifact::Wall(_) => None,
|
||||
Artifact::Cap(_) => None,
|
||||
Artifact::SweepEdge(_) => None,
|
||||
Artifact::EdgeCut(a) => Some(&a.code_ref),
|
||||
Artifact::EdgeCutEdge(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge the new artifact into self. If it can't because it's a different
|
||||
/// type, return the new artifact which should be used as a replacement.
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
match self {
|
||||
Artifact::Plane(a) => a.merge(new),
|
||||
Artifact::Path(a) => a.merge(new),
|
||||
Artifact::Segment(a) => a.merge(new),
|
||||
Artifact::Solid2d(_) => Some(new),
|
||||
Artifact::StartSketchOnFace { .. } => Some(new),
|
||||
Artifact::StartSketchOnPlane { .. } => Some(new),
|
||||
Artifact::Sweep(a) => a.merge(new),
|
||||
Artifact::Wall(a) => a.merge(new),
|
||||
Artifact::Cap(a) => a.merge(new),
|
||||
Artifact::SweepEdge(_) => Some(new),
|
||||
Artifact::EdgeCut(a) => a.merge(new),
|
||||
Artifact::EdgeCutEdge(_) => Some(new),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plane {
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
let Artifact::Plane(new) = new else {
|
||||
return Some(new);
|
||||
};
|
||||
merge_ids(&mut self.path_ids, new.path_ids);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Path {
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
let Artifact::Path(new) = new else {
|
||||
return Some(new);
|
||||
};
|
||||
merge_opt_id(&mut self.sweep_id, new.sweep_id);
|
||||
merge_ids(&mut self.seg_ids, new.seg_ids);
|
||||
merge_opt_id(&mut self.solid2d_id, new.solid2d_id);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Segment {
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
let Artifact::Segment(new) = new else {
|
||||
return Some(new);
|
||||
};
|
||||
merge_opt_id(&mut self.surface_id, new.surface_id);
|
||||
merge_ids(&mut self.edge_ids, new.edge_ids);
|
||||
merge_opt_id(&mut self.edge_cut_id, new.edge_cut_id);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Sweep {
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
let Artifact::Sweep(new) = new else {
|
||||
return Some(new);
|
||||
};
|
||||
merge_ids(&mut self.surface_ids, new.surface_ids);
|
||||
merge_ids(&mut self.edge_ids, new.edge_ids);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Wall {
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
let Artifact::Wall(new) = new else {
|
||||
return Some(new);
|
||||
};
|
||||
merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
|
||||
merge_ids(&mut self.path_ids, new.path_ids);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Cap {
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
let Artifact::Cap(new) = new else {
|
||||
return Some(new);
|
||||
};
|
||||
merge_ids(&mut self.edge_cut_edge_ids, new.edge_cut_edge_ids);
|
||||
merge_ids(&mut self.path_ids, new.path_ids);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl EdgeCut {
|
||||
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
|
||||
let Artifact::EdgeCut(new) = new else {
|
||||
return Some(new);
|
||||
};
|
||||
merge_opt_id(&mut self.surface_id, new.surface_id);
|
||||
merge_ids(&mut self.edge_ids, new.edge_ids);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize, ts_rs::TS)]
|
||||
#[ts(export_to = "Artifact.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ArtifactGraph {
|
||||
map: IndexMap<ArtifactId, Artifact>,
|
||||
}
|
||||
|
||||
pub(super) fn build_artifact_graph(
|
||||
artifact_commands: &[ArtifactCommand],
|
||||
responses: &IndexMap<Uuid, WebSocketResponse>,
|
||||
ast: &Node<Program>,
|
||||
exec_artifacts: &IndexMap<ArtifactId, Artifact>,
|
||||
) -> Result<ArtifactGraph, KclError> {
|
||||
let mut map = IndexMap::new();
|
||||
|
||||
let mut current_plane_id = None;
|
||||
|
||||
for artifact_command in artifact_commands {
|
||||
if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command {
|
||||
current_plane_id = Some(entity_id);
|
||||
}
|
||||
if let ModelingCmd::SketchModeDisable(SketchModeDisable { .. }) = artifact_command.command {
|
||||
current_plane_id = None;
|
||||
}
|
||||
|
||||
let flattened_responses = flatten_modeling_command_responses(responses);
|
||||
let artifact_updates = artifacts_to_update(
|
||||
&map,
|
||||
artifact_command,
|
||||
&flattened_responses,
|
||||
current_plane_id,
|
||||
ast,
|
||||
exec_artifacts,
|
||||
)?;
|
||||
for artifact in artifact_updates {
|
||||
// Merge with existing artifacts.
|
||||
merge_artifact_into_map(&mut map, artifact);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ArtifactGraph { map })
|
||||
}
|
||||
|
||||
/// Flatten the responses into a map of command IDs to modeling command
|
||||
/// responses. The raw responses from the engine contain batches.
|
||||
fn flatten_modeling_command_responses(
|
||||
responses: &IndexMap<Uuid, WebSocketResponse>,
|
||||
) -> FnvHashMap<Uuid, OkModelingCmdResponse> {
|
||||
let mut map = FnvHashMap::default();
|
||||
for (cmd_id, ws_response) in responses {
|
||||
let WebSocketResponse::Success(response) = ws_response else {
|
||||
// Response not successful.
|
||||
continue;
|
||||
};
|
||||
match &response.resp {
|
||||
OkWebSocketResponseData::Modeling { modeling_response } => {
|
||||
map.insert(*cmd_id, modeling_response.clone());
|
||||
}
|
||||
OkWebSocketResponseData::ModelingBatch { responses } =>
|
||||
{
|
||||
#[expect(
|
||||
clippy::iter_over_hash_type,
|
||||
reason = "Since we're moving entries to another unordered map, it's fine that the order is undefined"
|
||||
)]
|
||||
for (cmd_id, batch_response) in responses {
|
||||
if let BatchResponse::Success {
|
||||
response: modeling_response,
|
||||
} = batch_response
|
||||
{
|
||||
map.insert(*cmd_id.as_ref(), modeling_response.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
OkWebSocketResponseData::IceServerInfo { .. }
|
||||
| OkWebSocketResponseData::TrickleIce { .. }
|
||||
| OkWebSocketResponseData::SdpAnswer { .. }
|
||||
| OkWebSocketResponseData::Export { .. }
|
||||
| OkWebSocketResponseData::MetricsRequest { .. }
|
||||
| OkWebSocketResponseData::ModelingSessionData { .. }
|
||||
| OkWebSocketResponseData::Pong { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
fn merge_artifact_into_map(map: &mut IndexMap<ArtifactId, Artifact>, new_artifact: Artifact) {
|
||||
let id = new_artifact.id();
|
||||
let Some(old_artifact) = map.get_mut(&id) else {
|
||||
// No old artifact exists. Insert the new one.
|
||||
map.insert(id, new_artifact);
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(replacement) = old_artifact.merge(new_artifact) {
|
||||
*old_artifact = replacement;
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge the new IDs into the base vector, avoiding duplicates. This is O(nm)
|
||||
/// runtime. Rationale is that most of the ID collections in the artifact graph
|
||||
/// are pretty small, but we may want to change this in the future.
|
||||
fn merge_ids(base: &mut Vec<ArtifactId>, new: Vec<ArtifactId>) {
|
||||
let original_len = base.len();
|
||||
for id in new {
|
||||
// Don't bother inspecting new items that we just pushed.
|
||||
let original_base = &base[..original_len];
|
||||
if !original_base.contains(&id) {
|
||||
base.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_opt_id(base: &mut Option<ArtifactId>, new: Option<ArtifactId>) {
|
||||
// Always use the new one, even if it clears it.
|
||||
*base = new;
|
||||
}
|
||||
|
||||
fn artifacts_to_update(
|
||||
artifacts: &IndexMap<ArtifactId, Artifact>,
|
||||
artifact_command: &ArtifactCommand,
|
||||
responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
|
||||
current_plane_id: Option<Uuid>,
|
||||
_ast: &Node<Program>,
|
||||
_exec_artifacts: &IndexMap<ArtifactId, Artifact>,
|
||||
) -> Result<Vec<Artifact>, KclError> {
|
||||
// TODO: Build path-to-node from artifact_command source range. Right now,
|
||||
// we're serializing an empty array, and the TS wrapper fills it in with the
|
||||
// correct value.
|
||||
let path_to_node = Vec::new();
|
||||
|
||||
let range = artifact_command.range;
|
||||
let uuid = artifact_command.cmd_id;
|
||||
let id = ArtifactId::new(uuid);
|
||||
|
||||
let Some(response) = responses.get(&uuid) else {
|
||||
// Response not found or not successful.
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
let cmd = &artifact_command.command;
|
||||
|
||||
match cmd {
|
||||
ModelingCmd::MakePlane(_) => {
|
||||
if range.is_synthetic() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
// If we're calling `make_plane` and the code range doesn't end at
|
||||
// `0` it's not a default plane, but a custom one from the
|
||||
// offsetPlane standard library function.
|
||||
return Ok(vec![Artifact::Plane(Plane {
|
||||
id,
|
||||
path_ids: Vec::new(),
|
||||
code_ref: CodeRef { range, path_to_node },
|
||||
})]);
|
||||
}
|
||||
ModelingCmd::EnableSketchMode(_) => {
|
||||
let current_plane_id = current_plane_id.ok_or_else(|| {
|
||||
KclError::internal(format!(
|
||||
"Expected a current plane ID when processing EnableSketchMode command, but we have none: {id:?}"
|
||||
))
|
||||
})?;
|
||||
let existing_plane = artifacts.get(&ArtifactId::new(current_plane_id));
|
||||
match existing_plane {
|
||||
Some(Artifact::Wall(wall)) => {
|
||||
return Ok(vec![Artifact::Wall(Wall {
|
||||
id: current_plane_id.into(),
|
||||
seg_id: wall.seg_id,
|
||||
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
|
||||
sweep_id: wall.sweep_id,
|
||||
path_ids: wall.path_ids.clone(),
|
||||
})]);
|
||||
}
|
||||
Some(_) | None => {
|
||||
let path_ids = match existing_plane {
|
||||
Some(Artifact::Plane(Plane { path_ids, .. })) => path_ids.clone(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
return Ok(vec![Artifact::Plane(Plane {
|
||||
id: current_plane_id.into(),
|
||||
path_ids,
|
||||
code_ref: CodeRef { range, path_to_node },
|
||||
})]);
|
||||
}
|
||||
}
|
||||
}
|
||||
ModelingCmd::StartPath(_) => {
|
||||
let mut return_arr = Vec::new();
|
||||
let current_plane_id = current_plane_id.ok_or_else(|| {
|
||||
KclError::internal(format!(
|
||||
"Expected a current plane ID when processing StartPath command, but we have none: {id:?}"
|
||||
))
|
||||
})?;
|
||||
return_arr.push(Artifact::Path(Path {
|
||||
id,
|
||||
plane_id: current_plane_id.into(),
|
||||
seg_ids: Vec::new(),
|
||||
sweep_id: None,
|
||||
solid2d_id: None,
|
||||
code_ref: CodeRef { range, path_to_node },
|
||||
}));
|
||||
let plane = artifacts.get(&ArtifactId::new(current_plane_id));
|
||||
if let Some(Artifact::Plane(plane)) = plane {
|
||||
let code_ref = plane.code_ref.clone();
|
||||
return_arr.push(Artifact::Plane(Plane {
|
||||
id: current_plane_id.into(),
|
||||
path_ids: vec![id],
|
||||
code_ref,
|
||||
}));
|
||||
}
|
||||
if let Some(Artifact::Wall(wall)) = plane {
|
||||
return_arr.push(Artifact::Wall(Wall {
|
||||
id: current_plane_id.into(),
|
||||
seg_id: wall.seg_id,
|
||||
edge_cut_edge_ids: wall.edge_cut_edge_ids.clone(),
|
||||
sweep_id: wall.sweep_id,
|
||||
path_ids: vec![id],
|
||||
}));
|
||||
}
|
||||
return Ok(return_arr);
|
||||
}
|
||||
ModelingCmd::ClosePath(_) | ModelingCmd::ExtendPath(_) => {
|
||||
let path_id = ArtifactId::new(match cmd {
|
||||
ModelingCmd::ClosePath(c) => c.path_id,
|
||||
ModelingCmd::ExtendPath(e) => e.path.into(),
|
||||
_ => unreachable!(),
|
||||
});
|
||||
let mut return_arr = Vec::new();
|
||||
return_arr.push(Artifact::Segment(Segment {
|
||||
id,
|
||||
path_id,
|
||||
surface_id: None,
|
||||
edge_ids: Vec::new(),
|
||||
edge_cut_id: None,
|
||||
code_ref: CodeRef { range, path_to_node },
|
||||
}));
|
||||
let path = artifacts.get(&path_id);
|
||||
if let Some(Artifact::Path(path)) = path {
|
||||
let mut new_path = path.clone();
|
||||
new_path.seg_ids = vec![id];
|
||||
return_arr.push(Artifact::Path(new_path));
|
||||
}
|
||||
if let OkModelingCmdResponse::ClosePath(close_path) = response {
|
||||
return_arr.push(Artifact::Solid2d(Solid2d {
|
||||
id: close_path.face_id.into(),
|
||||
path_id,
|
||||
}));
|
||||
if let Some(Artifact::Path(path)) = path {
|
||||
let mut new_path = path.clone();
|
||||
new_path.solid2d_id = Some(close_path.face_id.into());
|
||||
return_arr.push(Artifact::Path(new_path));
|
||||
}
|
||||
}
|
||||
return Ok(return_arr);
|
||||
}
|
||||
ModelingCmd::Extrude(kcmc::Extrude { target, .. })
|
||||
| ModelingCmd::Revolve(kcmc::Revolve { target, .. })
|
||||
| ModelingCmd::Sweep(kcmc::Sweep { target, .. }) => {
|
||||
let sub_type = match cmd {
|
||||
ModelingCmd::Extrude(_) => SweepSubType::Extrusion,
|
||||
ModelingCmd::Revolve(_) => SweepSubType::Revolve,
|
||||
ModelingCmd::Sweep(_) => SweepSubType::Sweep,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut return_arr = Vec::new();
|
||||
let target = ArtifactId::from(target);
|
||||
return_arr.push(Artifact::Sweep(Sweep {
|
||||
id,
|
||||
sub_type,
|
||||
path_id: target,
|
||||
surface_ids: Vec::new(),
|
||||
edge_ids: Vec::new(),
|
||||
code_ref: CodeRef { range, path_to_node },
|
||||
}));
|
||||
let path = artifacts.get(&target);
|
||||
if let Some(Artifact::Path(path)) = path {
|
||||
let mut new_path = path.clone();
|
||||
new_path.sweep_id = Some(id);
|
||||
return_arr.push(Artifact::Path(new_path));
|
||||
}
|
||||
return Ok(return_arr);
|
||||
}
|
||||
ModelingCmd::Loft(loft_cmd) => {
|
||||
let OkModelingCmdResponse::Loft(_) = response else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let mut return_arr = Vec::new();
|
||||
return_arr.push(Artifact::Sweep(Sweep {
|
||||
id,
|
||||
sub_type: SweepSubType::Loft,
|
||||
// TODO: Using the first one. Make sure to revisit this
|
||||
// choice, don't think it matters for now.
|
||||
path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
|
||||
KclError::internal(format!(
|
||||
"Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"
|
||||
))
|
||||
})?),
|
||||
surface_ids: Vec::new(),
|
||||
edge_ids: Vec::new(),
|
||||
code_ref: CodeRef { range, path_to_node },
|
||||
}));
|
||||
for section_id in &loft_cmd.section_ids {
|
||||
let path = artifacts.get(&ArtifactId::new(*section_id));
|
||||
if let Some(Artifact::Path(path)) = path {
|
||||
let mut new_path = path.clone();
|
||||
new_path.sweep_id = Some(id);
|
||||
return_arr.push(Artifact::Path(new_path));
|
||||
}
|
||||
}
|
||||
return Ok(return_arr);
|
||||
}
|
||||
ModelingCmd::Solid3dGetExtrusionFaceInfo(_) => {
|
||||
let OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(face_info) = response else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let mut return_arr = Vec::new();
|
||||
let mut last_path = None;
|
||||
for face in &face_info.faces {
|
||||
if face.cap != ExtrusionFaceCapType::None {
|
||||
continue;
|
||||
}
|
||||
let Some(curve_id) = face.curve_id.map(ArtifactId::new) else {
|
||||
continue;
|
||||
};
|
||||
let Some(face_id) = face.face_id.map(ArtifactId::new) else {
|
||||
continue;
|
||||
};
|
||||
let Some(Artifact::Segment(seg)) = artifacts.get(&curve_id) else {
|
||||
continue;
|
||||
};
|
||||
let Some(Artifact::Path(path)) = artifacts.get(&seg.path_id) else {
|
||||
continue;
|
||||
};
|
||||
last_path = Some(path);
|
||||
let path_sweep_id = path.sweep_id.ok_or_else(|| {
|
||||
KclError::internal(format!(
|
||||
"Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
|
||||
))
|
||||
})?;
|
||||
return_arr.push(Artifact::Wall(Wall {
|
||||
id: face_id,
|
||||
seg_id: curve_id,
|
||||
edge_cut_edge_ids: Vec::new(),
|
||||
sweep_id: path_sweep_id,
|
||||
path_ids: vec![],
|
||||
}));
|
||||
let mut new_seg = seg.clone();
|
||||
new_seg.surface_id = Some(face_id);
|
||||
return_arr.push(Artifact::Segment(new_seg));
|
||||
if let Some(Artifact::Sweep(sweep)) = path.sweep_id.and_then(|id| artifacts.get(&id)) {
|
||||
let mut new_sweep = sweep.clone();
|
||||
new_sweep.surface_ids = vec![face_id];
|
||||
return_arr.push(Artifact::Sweep(new_sweep));
|
||||
}
|
||||
}
|
||||
if let Some(path) = last_path {
|
||||
for face in &face_info.faces {
|
||||
let sub_type = match face.cap {
|
||||
ExtrusionFaceCapType::Top => CapSubType::End,
|
||||
ExtrusionFaceCapType::Bottom => CapSubType::Start,
|
||||
ExtrusionFaceCapType::None | ExtrusionFaceCapType::Both => continue,
|
||||
};
|
||||
let Some(face_id) = face.face_id.map(ArtifactId::new) else {
|
||||
continue;
|
||||
};
|
||||
let path_sweep_id = path.sweep_id.ok_or_else(|| {
|
||||
KclError::internal(format!(
|
||||
"Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
|
||||
))
|
||||
})?;
|
||||
return_arr.push(Artifact::Cap(Cap {
|
||||
id: face_id,
|
||||
sub_type,
|
||||
edge_cut_edge_ids: Vec::new(),
|
||||
sweep_id: path_sweep_id,
|
||||
path_ids: Vec::new(),
|
||||
}));
|
||||
let Some(Artifact::Sweep(sweep)) = artifacts.get(&path_sweep_id) else {
|
||||
continue;
|
||||
};
|
||||
let mut new_sweep = sweep.clone();
|
||||
new_sweep.surface_ids = vec![face_id];
|
||||
return_arr.push(Artifact::Sweep(new_sweep));
|
||||
}
|
||||
}
|
||||
return Ok(return_arr);
|
||||
}
|
||||
ModelingCmd::Solid3dGetNextAdjacentEdge(kcmc::Solid3dGetNextAdjacentEdge { face_id, edge_id, .. })
|
||||
| ModelingCmd::Solid3dGetOppositeEdge(kcmc::Solid3dGetOppositeEdge { face_id, edge_id, .. }) => {
|
||||
let sub_type = match cmd {
|
||||
ModelingCmd::Solid3dGetNextAdjacentEdge(_) => SweepEdgeSubType::Adjacent,
|
||||
ModelingCmd::Solid3dGetOppositeEdge(_) => SweepEdgeSubType::Opposite,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let face_id = ArtifactId::new(*face_id);
|
||||
let edge_id = ArtifactId::new(*edge_id);
|
||||
let Some(Artifact::Wall(wall)) = artifacts.get(&face_id) else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let Some(Artifact::Sweep(sweep)) = artifacts.get(&wall.sweep_id) else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let Some(Artifact::Path(_)) = artifacts.get(&sweep.path_id) else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let Some(Artifact::Segment(segment)) = artifacts.get(&edge_id) else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let response_edge_id = match response {
|
||||
OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(r) => {
|
||||
let Some(edge_id) = r.edge else {
|
||||
return Err(KclError::internal(format!(
|
||||
"Expected Solid3dGetNextAdjacentEdge response to have an edge ID, but found none: id={id:?}, {response:?}"
|
||||
)));
|
||||
};
|
||||
edge_id.into()
|
||||
}
|
||||
OkModelingCmdResponse::Solid3dGetOppositeEdge(r) => r.edge.into(),
|
||||
_ => {
|
||||
return Err(KclError::internal(format!(
|
||||
"Expected Solid3dGetNextAdjacentEdge or Solid3dGetOppositeEdge response, but got: id={id:?}, {response:?}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let mut return_arr = Vec::new();
|
||||
return_arr.push(Artifact::SweepEdge(SweepEdge {
|
||||
id: response_edge_id,
|
||||
sub_type,
|
||||
seg_id: edge_id,
|
||||
sweep_id: sweep.id,
|
||||
}));
|
||||
let mut new_segment = segment.clone();
|
||||
new_segment.edge_ids = vec![response_edge_id];
|
||||
return_arr.push(Artifact::Segment(new_segment));
|
||||
let mut new_sweep = sweep.clone();
|
||||
new_sweep.edge_ids = vec![response_edge_id];
|
||||
return_arr.push(Artifact::Sweep(new_sweep));
|
||||
return Ok(return_arr);
|
||||
}
|
||||
ModelingCmd::Solid3dFilletEdge(cmd) => {
|
||||
let mut return_arr = Vec::new();
|
||||
return_arr.push(Artifact::EdgeCut(EdgeCut {
|
||||
id,
|
||||
sub_type: cmd.cut_type.into(),
|
||||
consumed_edge_id: cmd.edge_id.into(),
|
||||
edge_ids: Vec::new(),
|
||||
surface_id: None,
|
||||
code_ref: CodeRef { range, path_to_node },
|
||||
}));
|
||||
let consumed_edge = artifacts.get(&ArtifactId::new(cmd.edge_id));
|
||||
if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
|
||||
let mut new_segment = consumed_edge.clone();
|
||||
new_segment.edge_cut_id = Some(id);
|
||||
return_arr.push(Artifact::Segment(new_segment));
|
||||
} else {
|
||||
// TODO: Handle other types like SweepEdge.
|
||||
}
|
||||
return Ok(return_arr);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
527
src/wasm-lib/kcl/src/execution/artifact/mermaid_tests.rs
Normal file
527
src/wasm-lib/kcl/src/execution/artifact/mermaid_tests.rs
Normal file
@ -0,0 +1,527 @@
|
||||
//! Tests for the artifact graph that convert it to Mermaid diagrams.
|
||||
use std::fmt::Write;
|
||||
|
||||
use super::*;
|
||||
|
||||
type NodeId = u32;
|
||||
|
||||
type Edges = IndexMap<(NodeId, NodeId), EdgeInfo>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct EdgeInfo {
|
||||
direction: EdgeDirection,
|
||||
flow: EdgeFlow,
|
||||
kind: EdgeKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum EdgeDirection {
|
||||
Forward,
|
||||
Backward,
|
||||
Bidirectional,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum EdgeFlow {
|
||||
SourceToTarget,
|
||||
TargetToSource,
|
||||
}
|
||||
|
||||
impl EdgeFlow {
|
||||
#[must_use]
|
||||
fn reverse(&self) -> EdgeFlow {
|
||||
match self {
|
||||
EdgeFlow::SourceToTarget => EdgeFlow::TargetToSource,
|
||||
EdgeFlow::TargetToSource => EdgeFlow::SourceToTarget,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum EdgeKind {
|
||||
PathToSweep,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl EdgeDirection {
|
||||
#[must_use]
|
||||
fn merge(&self, other: EdgeDirection) -> EdgeDirection {
|
||||
match self {
|
||||
EdgeDirection::Forward => match other {
|
||||
EdgeDirection::Forward => EdgeDirection::Forward,
|
||||
EdgeDirection::Backward => EdgeDirection::Bidirectional,
|
||||
EdgeDirection::Bidirectional => EdgeDirection::Bidirectional,
|
||||
},
|
||||
EdgeDirection::Backward => match other {
|
||||
EdgeDirection::Forward => EdgeDirection::Bidirectional,
|
||||
EdgeDirection::Backward => EdgeDirection::Backward,
|
||||
EdgeDirection::Bidirectional => EdgeDirection::Bidirectional,
|
||||
},
|
||||
EdgeDirection::Bidirectional => EdgeDirection::Bidirectional,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Artifact {
|
||||
/// The IDs pointing back to prior nodes in a depth-first traversal of
|
||||
/// the graph. This should be disjoint with `child_ids`.
|
||||
pub(crate) fn back_edges(&self) -> Vec<ArtifactId> {
|
||||
match self {
|
||||
Artifact::Plane(_) => Vec::new(),
|
||||
Artifact::Path(a) => vec![a.plane_id],
|
||||
Artifact::Segment(a) => vec![a.path_id],
|
||||
Artifact::Solid2d(a) => vec![a.path_id],
|
||||
Artifact::StartSketchOnFace { face_id, .. } => vec![face_id.into()],
|
||||
Artifact::StartSketchOnPlane { plane_id, .. } => vec![plane_id.into()],
|
||||
Artifact::Sweep(a) => vec![a.path_id],
|
||||
Artifact::Wall(a) => vec![a.seg_id, a.sweep_id],
|
||||
Artifact::Cap(a) => vec![a.sweep_id],
|
||||
Artifact::SweepEdge(a) => vec![a.seg_id, a.sweep_id],
|
||||
Artifact::EdgeCut(a) => vec![a.consumed_edge_id],
|
||||
Artifact::EdgeCutEdge(a) => vec![a.edge_cut_id],
|
||||
}
|
||||
}
|
||||
|
||||
/// The child IDs of this artifact, used to do a depth-first traversal of
|
||||
/// the graph.
|
||||
pub(crate) fn child_ids(&self) -> Vec<ArtifactId> {
|
||||
match self {
|
||||
Artifact::Plane(a) => a.path_ids.clone(),
|
||||
Artifact::Path(a) => {
|
||||
// Note: Don't include these since they're parents: plane_id.
|
||||
let mut ids = a.seg_ids.clone();
|
||||
if let Some(sweep_id) = a.sweep_id {
|
||||
ids.push(sweep_id);
|
||||
}
|
||||
if let Some(solid2d_id) = a.solid2d_id {
|
||||
ids.push(solid2d_id);
|
||||
}
|
||||
ids
|
||||
}
|
||||
Artifact::Segment(a) => {
|
||||
// Note: Don't include these since they're parents: path_id.
|
||||
let mut ids = Vec::new();
|
||||
if let Some(surface_id) = a.surface_id {
|
||||
ids.push(surface_id);
|
||||
}
|
||||
ids.extend(&a.edge_ids);
|
||||
if let Some(edge_cut_id) = a.edge_cut_id {
|
||||
ids.push(edge_cut_id);
|
||||
}
|
||||
ids
|
||||
}
|
||||
Artifact::Solid2d(_) => {
|
||||
// Note: Don't include these since they're parents: path_id.
|
||||
Vec::new()
|
||||
}
|
||||
Artifact::StartSketchOnFace { .. } => Vec::new(),
|
||||
Artifact::StartSketchOnPlane { .. } => Vec::new(),
|
||||
Artifact::Sweep(a) => {
|
||||
// Note: Don't include these since they're parents: path_id.
|
||||
let mut ids = Vec::new();
|
||||
ids.extend(&a.surface_ids);
|
||||
ids.extend(&a.edge_ids);
|
||||
ids
|
||||
}
|
||||
Artifact::Wall(a) => {
|
||||
// Note: Don't include these since they're parents: seg_id,
|
||||
// sweep_id.
|
||||
let mut ids = Vec::new();
|
||||
ids.extend(&a.edge_cut_edge_ids);
|
||||
ids.extend(&a.path_ids);
|
||||
ids
|
||||
}
|
||||
Artifact::Cap(a) => {
|
||||
// Note: Don't include these since they're parents: sweep_id.
|
||||
let mut ids = Vec::new();
|
||||
ids.extend(&a.edge_cut_edge_ids);
|
||||
ids.extend(&a.path_ids);
|
||||
ids
|
||||
}
|
||||
Artifact::SweepEdge(_) => {
|
||||
// Note: Don't include these since they're parents: seg_id,
|
||||
// sweep_id.
|
||||
Vec::new()
|
||||
}
|
||||
Artifact::EdgeCut(a) => {
|
||||
// Note: Don't include these since they're parents:
|
||||
// consumed_edge_id.
|
||||
let mut ids = Vec::new();
|
||||
ids.extend(&a.edge_ids);
|
||||
if let Some(surface_id) = a.surface_id {
|
||||
ids.push(surface_id);
|
||||
}
|
||||
ids
|
||||
}
|
||||
Artifact::EdgeCutEdge(a) => {
|
||||
// Note: Don't include these since they're parents: edge_cut_id.
|
||||
vec![a.surface_id]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArtifactGraph {
|
||||
/// Output the Mermaid flowchart for the artifact graph.
|
||||
pub(crate) fn to_mermaid_flowchart(&self) -> Result<String, std::fmt::Error> {
|
||||
let mut output = String::new();
|
||||
output.push_str("```mermaid\n");
|
||||
output.push_str("flowchart LR\n");
|
||||
|
||||
let mut next_id = 1_u32;
|
||||
let mut stable_id_map = FnvHashMap::default();
|
||||
for id in self.map.keys() {
|
||||
stable_id_map.insert(*id, next_id);
|
||||
next_id = next_id.checked_add(1).unwrap();
|
||||
}
|
||||
|
||||
// Output all nodes first since edge order can change how Mermaid
|
||||
// lays out nodes. This is also where we output more details about
|
||||
// the nodes, like their labels.
|
||||
self.flowchart_nodes(&mut output, &stable_id_map, " ")?;
|
||||
self.flowchart_edges(&mut output, &stable_id_map, " ")?;
|
||||
|
||||
output.push_str("```\n");
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Output the Mermaid flowchart nodes, one for each artifact.
|
||||
fn flowchart_nodes<W: Write>(
|
||||
&self,
|
||||
output: &mut W,
|
||||
stable_id_map: &FnvHashMap<ArtifactId, NodeId>,
|
||||
prefix: &str,
|
||||
) -> std::fmt::Result {
|
||||
// Artifact ID of the path is the key. The value is a list of
|
||||
// artifact IDs in that group.
|
||||
let mut groups = IndexMap::new();
|
||||
let mut ungrouped = Vec::new();
|
||||
|
||||
for artifact in self.map.values() {
|
||||
let id = artifact.id();
|
||||
|
||||
let grouped = match artifact {
|
||||
Artifact::Plane(_) => false,
|
||||
Artifact::Path(_) => {
|
||||
groups.entry(id).or_insert_with(Vec::new).push(id);
|
||||
true
|
||||
}
|
||||
Artifact::Segment(segment) => {
|
||||
let path_id = segment.path_id;
|
||||
groups.entry(path_id).or_insert_with(Vec::new).push(id);
|
||||
true
|
||||
}
|
||||
Artifact::Solid2d(solid2d) => {
|
||||
let path_id = solid2d.path_id;
|
||||
groups.entry(path_id).or_insert_with(Vec::new).push(id);
|
||||
true
|
||||
}
|
||||
Artifact::StartSketchOnFace { .. }
|
||||
| Artifact::StartSketchOnPlane { .. }
|
||||
| Artifact::Sweep(_)
|
||||
| Artifact::Wall(_)
|
||||
| Artifact::Cap(_)
|
||||
| Artifact::SweepEdge(_)
|
||||
| Artifact::EdgeCut(_)
|
||||
| Artifact::EdgeCutEdge(_) => false,
|
||||
};
|
||||
if !grouped {
|
||||
ungrouped.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
for (group_id, artifact_ids) in groups {
|
||||
let group_id = *stable_id_map.get(&group_id).unwrap();
|
||||
writeln!(output, "{prefix}subgraph path{group_id} [Path]")?;
|
||||
let indented = format!("{} ", prefix);
|
||||
for artifact_id in artifact_ids {
|
||||
let artifact = self.map.get(&artifact_id).unwrap();
|
||||
let id = *stable_id_map.get(&artifact_id).unwrap();
|
||||
self.flowchart_node(output, artifact, id, &indented)?;
|
||||
}
|
||||
writeln!(output, "{prefix}end")?;
|
||||
}
|
||||
|
||||
for artifact_id in ungrouped {
|
||||
let artifact = self.map.get(&artifact_id).unwrap();
|
||||
let id = *stable_id_map.get(&artifact_id).unwrap();
|
||||
self.flowchart_node(output, artifact, id, prefix)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flowchart_node<W: Write>(
|
||||
&self,
|
||||
output: &mut W,
|
||||
artifact: &Artifact,
|
||||
id: NodeId,
|
||||
prefix: &str,
|
||||
) -> std::fmt::Result {
|
||||
// For now, only showing the source range.
|
||||
fn code_ref_display(code_ref: &CodeRef) -> [usize; 3] {
|
||||
range_display(code_ref.range)
|
||||
}
|
||||
fn range_display(range: SourceRange) -> [usize; 3] {
|
||||
[range.start(), range.end(), range.module_id().as_usize()]
|
||||
}
|
||||
|
||||
match artifact {
|
||||
Artifact::Plane(plane) => {
|
||||
writeln!(
|
||||
output,
|
||||
"{prefix}{}[\"Plane<br>{:?}\"]",
|
||||
id,
|
||||
code_ref_display(&plane.code_ref)
|
||||
)?;
|
||||
}
|
||||
Artifact::Path(path) => {
|
||||
writeln!(
|
||||
output,
|
||||
"{prefix}{}[\"Path<br>{:?}\"]",
|
||||
id,
|
||||
code_ref_display(&path.code_ref)
|
||||
)?;
|
||||
}
|
||||
Artifact::Segment(segment) => {
|
||||
writeln!(
|
||||
output,
|
||||
"{prefix}{}[\"Segment<br>{:?}\"]",
|
||||
id,
|
||||
code_ref_display(&segment.code_ref)
|
||||
)?;
|
||||
}
|
||||
Artifact::Solid2d(_solid2d) => {
|
||||
writeln!(output, "{prefix}{}[Solid2d]", id)?;
|
||||
}
|
||||
Artifact::StartSketchOnFace { source_range, .. } => {
|
||||
writeln!(
|
||||
output,
|
||||
"{prefix}{}[\"StartSketchOnFace<br>{:?}\"]",
|
||||
id,
|
||||
range_display(*source_range)
|
||||
)?;
|
||||
}
|
||||
Artifact::StartSketchOnPlane { source_range, .. } => {
|
||||
writeln!(
|
||||
output,
|
||||
"{prefix}{}[\"StartSketchOnPlane<br>{:?}\"]",
|
||||
id,
|
||||
range_display(*source_range)
|
||||
)?;
|
||||
}
|
||||
Artifact::Sweep(sweep) => {
|
||||
writeln!(
|
||||
output,
|
||||
"{prefix}{}[\"Sweep {:?}<br>{:?}\"]",
|
||||
id,
|
||||
sweep.sub_type,
|
||||
code_ref_display(&sweep.code_ref)
|
||||
)?;
|
||||
}
|
||||
Artifact::Wall(_wall) => {
|
||||
writeln!(output, "{prefix}{}[Wall]", id)?;
|
||||
}
|
||||
Artifact::Cap(cap) => {
|
||||
writeln!(output, "{prefix}{}[\"Cap {:?}\"]", id, cap.sub_type)?;
|
||||
}
|
||||
Artifact::SweepEdge(sweep_edge) => {
|
||||
writeln!(output, "{prefix}{}[\"SweepEdge {:?}\"]", id, sweep_edge.sub_type)?;
|
||||
}
|
||||
Artifact::EdgeCut(edge_cut) => {
|
||||
writeln!(
|
||||
output,
|
||||
"{prefix}{}[\"EdgeCut {:?}<br>{:?}\"]",
|
||||
id,
|
||||
edge_cut.sub_type,
|
||||
code_ref_display(&edge_cut.code_ref)
|
||||
)?;
|
||||
}
|
||||
Artifact::EdgeCutEdge(_edge_cut_edge) => {
|
||||
writeln!(output, "{prefix}{}[EdgeCutEdge]", id)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flowchart_edges<W: Write>(
|
||||
&self,
|
||||
output: &mut W,
|
||||
stable_id_map: &FnvHashMap<ArtifactId, NodeId>,
|
||||
prefix: &str,
|
||||
) -> Result<(), std::fmt::Error> {
|
||||
// Mermaid will display two edges in either direction, even using
|
||||
// the `---` edge type. So we need to deduplicate them.
|
||||
fn add_unique_edge(edges: &mut Edges, source_id: NodeId, target_id: NodeId, flow: EdgeFlow, kind: EdgeKind) {
|
||||
if source_id == target_id {
|
||||
// Self edge. Skip it.
|
||||
return;
|
||||
}
|
||||
// The key is the node IDs in canonical order.
|
||||
let a = source_id.min(target_id);
|
||||
let b = source_id.max(target_id);
|
||||
let new_direction = if a == source_id {
|
||||
EdgeDirection::Forward
|
||||
} else {
|
||||
EdgeDirection::Backward
|
||||
};
|
||||
let initial_flow = if a == source_id { flow } else { flow.reverse() };
|
||||
let edge = edges.entry((a, b)).or_insert(EdgeInfo {
|
||||
direction: new_direction,
|
||||
flow: initial_flow,
|
||||
kind,
|
||||
});
|
||||
// Merge with existing edge.
|
||||
edge.direction = edge.direction.merge(new_direction);
|
||||
}
|
||||
|
||||
// Collect all edges to deduplicate them.
|
||||
let mut edges = IndexMap::default();
|
||||
for artifact in self.map.values() {
|
||||
let source_id = *stable_id_map.get(&artifact.id()).unwrap();
|
||||
// In Mermaid, the textual order defines the rank, even though the
|
||||
// edge arrow can go in either direction.
|
||||
//
|
||||
// Back edges: parent <- self
|
||||
// Child edges: self -> child
|
||||
for (target_id, flow) in artifact
|
||||
.back_edges()
|
||||
.into_iter()
|
||||
.zip(std::iter::repeat(EdgeFlow::TargetToSource))
|
||||
.chain(
|
||||
artifact
|
||||
.child_ids()
|
||||
.into_iter()
|
||||
.zip(std::iter::repeat(EdgeFlow::SourceToTarget)),
|
||||
)
|
||||
{
|
||||
let Some(target) = self.map.get(&target_id) else {
|
||||
continue;
|
||||
};
|
||||
let edge_kind = match (artifact, target) {
|
||||
(Artifact::Path(_), Artifact::Sweep(_)) | (Artifact::Sweep(_), Artifact::Path(_)) => {
|
||||
EdgeKind::PathToSweep
|
||||
}
|
||||
_ => EdgeKind::Other,
|
||||
};
|
||||
let target_id = *stable_id_map.get(&target_id).unwrap();
|
||||
add_unique_edge(&mut edges, source_id, target_id, flow, edge_kind);
|
||||
}
|
||||
}
|
||||
|
||||
// Output the edges.
|
||||
for ((source_id, target_id), edge) in edges {
|
||||
let extra = match edge.kind {
|
||||
// Extra length. This is needed to make the graph layout more
|
||||
// legible. Without it, the sweep will be at the same rank as
|
||||
// the path's segments, and the sweep's edges overlap with the
|
||||
// segment edges a lot.
|
||||
EdgeKind::PathToSweep => "-",
|
||||
EdgeKind::Other => "",
|
||||
};
|
||||
match edge.flow {
|
||||
EdgeFlow::SourceToTarget => match edge.direction {
|
||||
EdgeDirection::Forward => {
|
||||
writeln!(output, "{prefix}{source_id} x{}--> {}", extra, target_id)?;
|
||||
}
|
||||
EdgeDirection::Backward => {
|
||||
writeln!(output, "{prefix}{source_id} <{}--x {}", extra, target_id)?;
|
||||
}
|
||||
EdgeDirection::Bidirectional => {
|
||||
writeln!(output, "{prefix}{source_id} {}--- {}", extra, target_id)?;
|
||||
}
|
||||
},
|
||||
EdgeFlow::TargetToSource => match edge.direction {
|
||||
EdgeDirection::Forward => {
|
||||
writeln!(output, "{prefix}{target_id} x{}--> {}", extra, source_id)?;
|
||||
}
|
||||
EdgeDirection::Backward => {
|
||||
writeln!(output, "{prefix}{target_id} <{}--x {}", extra, source_id)?;
|
||||
}
|
||||
EdgeDirection::Bidirectional => {
|
||||
writeln!(output, "{prefix}{target_id} {}--- {}", extra, source_id)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Output the Mermaid mind map for the artifact graph.
|
||||
///
|
||||
/// This is sometimes easier to read than the flowchart. But since it
|
||||
/// does a depth-first traversal starting from all the planes, it may
|
||||
/// not include all the artifacts. It also doesn't show edge direction.
|
||||
/// It's useful for a high-level overview of the graph, not for
|
||||
/// including all the information.
|
||||
pub(crate) fn to_mermaid_mind_map(&self) -> Result<String, std::fmt::Error> {
|
||||
let mut output = String::new();
|
||||
output.push_str("```mermaid\n");
|
||||
output.push_str("mindmap\n");
|
||||
output.push_str(" root\n");
|
||||
|
||||
for (_, artifact) in &self.map {
|
||||
// Only the planes are roots.
|
||||
let Artifact::Plane(_) = artifact else {
|
||||
continue;
|
||||
};
|
||||
self.mind_map_artifact(&mut output, artifact, " ")?;
|
||||
}
|
||||
|
||||
output.push_str("```\n");
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn mind_map_artifact<W: Write>(&self, output: &mut W, artifact: &Artifact, prefix: &str) -> std::fmt::Result {
|
||||
match artifact {
|
||||
Artifact::Plane(_plane) => {
|
||||
writeln!(output, "{prefix}Plane")?;
|
||||
}
|
||||
Artifact::Path(_path) => {
|
||||
writeln!(output, "{prefix}Path")?;
|
||||
}
|
||||
Artifact::Segment(_segment) => {
|
||||
writeln!(output, "{prefix}Segment")?;
|
||||
}
|
||||
Artifact::Solid2d(_solid2d) => {
|
||||
writeln!(output, "{prefix}Solid2d")?;
|
||||
}
|
||||
Artifact::StartSketchOnFace { .. } => {
|
||||
writeln!(output, "{prefix}StartSketchOnFace")?;
|
||||
}
|
||||
Artifact::StartSketchOnPlane { .. } => {
|
||||
writeln!(output, "{prefix}StartSketchOnPlane")?;
|
||||
}
|
||||
Artifact::Sweep(sweep) => {
|
||||
writeln!(output, "{prefix}Sweep {:?}", sweep.sub_type)?;
|
||||
}
|
||||
Artifact::Wall(_wall) => {
|
||||
writeln!(output, "{prefix}Wall")?;
|
||||
}
|
||||
Artifact::Cap(cap) => {
|
||||
writeln!(output, "{prefix}Cap {:?}", cap.sub_type)?;
|
||||
}
|
||||
Artifact::SweepEdge(sweep_edge) => {
|
||||
writeln!(output, "{prefix}SweepEdge {:?}", sweep_edge.sub_type,)?;
|
||||
}
|
||||
Artifact::EdgeCut(edge_cut) => {
|
||||
writeln!(output, "{prefix}EdgeCut {:?}", edge_cut.sub_type)?;
|
||||
}
|
||||
Artifact::EdgeCutEdge(_edge_cut_edge) => {
|
||||
writeln!(output, "{prefix}EdgeCutEdge")?;
|
||||
}
|
||||
}
|
||||
|
||||
for child_id in artifact.child_ids() {
|
||||
let Some(child_artifact) = self.map.get(&child_id) else {
|
||||
continue;
|
||||
};
|
||||
self.mind_map_artifact(output, child_artifact, &format!("{} ", prefix))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ use crate::{
|
||||
};
|
||||
|
||||
/// Information for the caching an AST and smartly re-executing it if we can.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CacheInformation {
|
||||
/// The old information.
|
||||
pub old: Option<OldAstState>,
|
||||
@ -17,7 +17,7 @@ pub struct CacheInformation {
|
||||
}
|
||||
|
||||
/// The old ast and program memory.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct OldAstState {
|
||||
/// The ast.
|
||||
pub ast: Node<Program>,
|
||||
|
@ -3,6 +3,7 @@
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use artifact::build_artifact_graph;
|
||||
use async_recursion::async_recursion;
|
||||
use indexmap::IndexMap;
|
||||
use kcmc::{
|
||||
@ -11,8 +12,8 @@ use kcmc::{
|
||||
websocket::{ModelingSessionData, OkWebSocketResponseData},
|
||||
ImageFormat, ModelingCmd,
|
||||
};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
use kittycad_modeling_cmds::length_unit::LengthUnit;
|
||||
use kittycad_modeling_cmds::{self as kcmc, websocket::WebSocketResponse};
|
||||
use parse_display::{Display, FromStr};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -49,18 +50,18 @@ use crate::{
|
||||
};
|
||||
|
||||
// Re-exports.
|
||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactId, ArtifactInner};
|
||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
|
||||
pub use cad_op::Operation;
|
||||
|
||||
/// State for executing a program.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExecState {
|
||||
pub global: GlobalState,
|
||||
pub mod_local: ModuleState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GlobalState {
|
||||
/// The stable artifact ID generator.
|
||||
@ -75,6 +76,13 @@ pub struct GlobalState {
|
||||
/// These are accumulated in the [`ExecutorContext`] but moved here for
|
||||
/// convenience of the execution cache.
|
||||
pub artifact_commands: Vec<ArtifactCommand>,
|
||||
/// Responses from the engine for `artifact_commands`. We need to cache
|
||||
/// this so that we can build the artifact graph. These are accumulated in
|
||||
/// the [`ExecutorContext`] but moved here for convenience of the execution
|
||||
/// cache.
|
||||
pub artifact_responses: IndexMap<Uuid, WebSocketResponse>,
|
||||
/// Output artifact graph.
|
||||
pub artifact_graph: ArtifactGraph,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
@ -113,6 +121,8 @@ pub struct ExecOutcome {
|
||||
pub artifacts: IndexMap<ArtifactId, Artifact>,
|
||||
/// Output commands to allow building the artifact graph by the caller.
|
||||
pub artifact_commands: Vec<ArtifactCommand>,
|
||||
/// Output artifact graph.
|
||||
pub artifact_graph: ArtifactGraph,
|
||||
}
|
||||
|
||||
impl ExecState {
|
||||
@ -149,6 +159,7 @@ impl ExecState {
|
||||
operations: self.mod_local.operations,
|
||||
artifacts: self.global.artifacts,
|
||||
artifact_commands: self.global.artifact_commands,
|
||||
artifact_graph: self.global.artifact_graph,
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,7 +176,7 @@ impl ExecState {
|
||||
}
|
||||
|
||||
pub fn add_artifact(&mut self, artifact: Artifact) {
|
||||
let id = artifact.id;
|
||||
let id = artifact.id();
|
||||
self.global.artifacts.insert(id, artifact);
|
||||
}
|
||||
|
||||
@ -216,6 +227,8 @@ impl GlobalState {
|
||||
module_infos: Default::default(),
|
||||
artifacts: Default::default(),
|
||||
artifact_commands: Default::default(),
|
||||
artifact_responses: Default::default(),
|
||||
artifact_graph: Default::default(),
|
||||
};
|
||||
|
||||
// TODO(#4434): Use the top-level file's path.
|
||||
@ -2239,22 +2252,56 @@ impl ExecutorContext {
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
self.inner_execute(&cache_result.program, exec_state, crate::execution::BodyType::Root)
|
||||
self.execute_and_build_graph(&cache_result.program, exec_state)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
KclErrorWithOutputs::new(
|
||||
e,
|
||||
exec_state.mod_local.operations.clone(),
|
||||
self.engine.take_artifact_commands(),
|
||||
exec_state.global.artifact_commands.clone(),
|
||||
exec_state.global.artifact_graph.clone(),
|
||||
)
|
||||
})?;
|
||||
// Move the artifact commands to simplify cache management.
|
||||
|
||||
let session_data = self.engine.get_session_data();
|
||||
Ok(session_data)
|
||||
}
|
||||
|
||||
/// Execute an AST's program and build auxiliary outputs like the artifact
|
||||
/// graph.
|
||||
async fn execute_and_build_graph<'a>(
|
||||
&self,
|
||||
program: NodeRef<'a, crate::parsing::ast::types::Program>,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
// Don't early return! We need to build other outputs regardless of
|
||||
// whether execution failed.
|
||||
let exec_result = self
|
||||
.inner_execute(program, exec_state, crate::execution::BodyType::Root)
|
||||
.await;
|
||||
// Move the artifact commands and responses to simplify cache management
|
||||
// and error creation.
|
||||
exec_state
|
||||
.global
|
||||
.artifact_commands
|
||||
.extend(self.engine.take_artifact_commands());
|
||||
let session_data = self.engine.get_session_data();
|
||||
Ok(session_data)
|
||||
exec_state.global.artifact_responses.extend(self.engine.responses());
|
||||
// Build the artifact graph.
|
||||
match build_artifact_graph(
|
||||
&exec_state.global.artifact_commands,
|
||||
&exec_state.global.artifact_responses,
|
||||
program,
|
||||
&exec_state.global.artifacts,
|
||||
) {
|
||||
Ok(artifact_graph) => {
|
||||
exec_state.global.artifact_graph = artifact_graph;
|
||||
exec_result
|
||||
}
|
||||
Err(err) => {
|
||||
// Prefer the exec error.
|
||||
exec_result.and(Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute an AST's program.
|
||||
|
@ -91,12 +91,12 @@ async fn execute(test_name: &str, render_to_png: bool) {
|
||||
)
|
||||
.await;
|
||||
match exec_res {
|
||||
Ok((program_memory, ops, artifact_commands, png)) => {
|
||||
Ok((exec_state, png)) => {
|
||||
if render_to_png {
|
||||
twenty_twenty::assert_image(format!("tests/{test_name}/rendered_model.png"), &png, 0.99);
|
||||
}
|
||||
assert_snapshot(test_name, "Program memory after executing", || {
|
||||
insta::assert_json_snapshot!("program_memory", program_memory, {
|
||||
insta::assert_json_snapshot!("program_memory", exec_state.mod_local.memory, {
|
||||
".environments[].**[].from[]" => rounded_redaction(4),
|
||||
".environments[].**[].to[]" => rounded_redaction(4),
|
||||
".environments[].**[].x[]" => rounded_redaction(4),
|
||||
@ -105,15 +105,35 @@ async fn execute(test_name: &str, render_to_png: bool) {
|
||||
});
|
||||
});
|
||||
assert_snapshot(test_name, "Operations executed", || {
|
||||
insta::assert_json_snapshot!("ops", ops);
|
||||
insta::assert_json_snapshot!("ops", exec_state.mod_local.operations);
|
||||
});
|
||||
assert_snapshot(test_name, "Artifact commands", || {
|
||||
insta::assert_json_snapshot!("artifact_commands", artifact_commands, {
|
||||
insta::assert_json_snapshot!("artifact_commands", exec_state.global.artifact_commands, {
|
||||
"[].command.segment.*.x" => rounded_redaction(4),
|
||||
"[].command.segment.*.y" => rounded_redaction(4),
|
||||
"[].command.segment.*.z" => rounded_redaction(4),
|
||||
});
|
||||
});
|
||||
assert_snapshot(test_name, "Artifact graph flowchart", || {
|
||||
let flowchart = exec_state
|
||||
.global
|
||||
.artifact_graph
|
||||
.to_mermaid_flowchart()
|
||||
.unwrap_or_else(|e| format!("Failed to convert artifact graph to mind map: {e}"));
|
||||
// Change the snapshot suffix so that it is rendered as a
|
||||
// Markdown file in GitHub.
|
||||
insta::assert_binary_snapshot!("artifact_graph_flowchart.md", flowchart.as_bytes().to_owned());
|
||||
});
|
||||
assert_snapshot(test_name, "Artifact graph mind map", || {
|
||||
let mind_map = exec_state
|
||||
.global
|
||||
.artifact_graph
|
||||
.to_mermaid_mind_map()
|
||||
.unwrap_or_else(|e| format!("Failed to convert artifact graph to mind map: {e}"));
|
||||
// Change the snapshot suffix so that it is rendered as a
|
||||
// Markdown file in GitHub.
|
||||
insta::assert_binary_snapshot!("artifact_graph_mind_map.md", mind_map.as_bytes().to_owned());
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
match e.error {
|
||||
@ -177,6 +197,90 @@ mod cube {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod artifact_graph_example_code1 {
|
||||
const TEST_NAME: &str = "artifact_graph_example_code1";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[test]
|
||||
fn unparse() {
|
||||
super::unparse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod artifact_graph_example_code_no_3d {
|
||||
const TEST_NAME: &str = "artifact_graph_example_code_no_3d";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[test]
|
||||
fn unparse() {
|
||||
super::unparse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod artifact_graph_example_code_offset_planes {
|
||||
const TEST_NAME: &str = "artifact_graph_example_code_offset_planes";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[test]
|
||||
fn unparse() {
|
||||
super::unparse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod artifact_graph_sketch_on_face_etc {
|
||||
const TEST_NAME: &str = "artifact_graph_sketch_on_face_etc";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[test]
|
||||
fn unparse() {
|
||||
super::unparse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod helix_ccw {
|
||||
const TEST_NAME: &str = "helix_ccw";
|
||||
|
||||
|
@ -23,6 +23,13 @@ impl ModuleId {
|
||||
}
|
||||
}
|
||||
|
||||
/// The first two items are the start and end points (byte offsets from the start of the file).
|
||||
/// The third item is whether the source range belongs to the 'main' file, i.e., the file currently
|
||||
/// being rendered/displayed in the editor.
|
||||
//
|
||||
// Don't use a doc comment for the below since the above goes in the website docs.
|
||||
// @see isTopLevelModule() in wasm.ts.
|
||||
// TODO we need to handle modules better in the frontend.
|
||||
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
|
||||
#[ts(export, type = "[number, number, number]")]
|
||||
pub struct SourceRange([usize; 3]);
|
||||
@ -58,6 +65,12 @@ impl SourceRange {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// True if this is a source range that doesn't correspond to any source
|
||||
/// code.
|
||||
pub fn is_synthetic(&self) -> bool {
|
||||
self.start() == 0 && self.end() == 0
|
||||
}
|
||||
|
||||
/// Get the start of the range.
|
||||
pub fn start(&self) -> usize {
|
||||
self.0[0]
|
||||
|
@ -11,7 +11,7 @@ use parse_display::{Display, FromStr};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::execution::{Artifact, ArtifactId, ArtifactInner};
|
||||
use crate::execution::{Artifact, ArtifactId};
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
@ -1079,9 +1079,9 @@ async fn inner_start_sketch_on(
|
||||
SketchData::Plane(plane) => {
|
||||
// Create artifact used only by the UI, not the engine.
|
||||
let id = exec_state.next_uuid();
|
||||
exec_state.add_artifact(Artifact {
|
||||
exec_state.add_artifact(Artifact::StartSketchOnPlane {
|
||||
id: ArtifactId::from(id),
|
||||
inner: ArtifactInner::StartSketchOnPlane { plane_id: plane.id },
|
||||
plane_id: plane.id,
|
||||
source_range: args.source_range,
|
||||
});
|
||||
|
||||
@ -1098,9 +1098,9 @@ async fn inner_start_sketch_on(
|
||||
|
||||
// Create artifact used only by the UI, not the engine.
|
||||
let id = exec_state.next_uuid();
|
||||
exec_state.add_artifact(Artifact {
|
||||
exec_state.add_artifact(Artifact::StartSketchOnFace {
|
||||
id: ArtifactId::from(id),
|
||||
inner: ArtifactInner::StartSketchOnFace { face_id: face.id },
|
||||
face_id: face.id,
|
||||
source_range: args.source_range,
|
||||
});
|
||||
|
||||
|
@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||
|
||||
use crate::{
|
||||
errors::ExecErrorWithState,
|
||||
execution::{new_zoo_client, ArtifactCommand, ExecutorContext, ExecutorSettings, Operation, ProgramMemory},
|
||||
execution::{new_zoo_client, ExecutorContext, ExecutorSettings},
|
||||
settings::types::UnitLength,
|
||||
ConnectionError, ExecError, ExecState, KclErrorWithOutputs, Program,
|
||||
};
|
||||
@ -39,16 +39,9 @@ pub async fn execute_and_snapshot_ast(
|
||||
ast: Program,
|
||||
units: UnitLength,
|
||||
project_directory: Option<PathBuf>,
|
||||
) -> Result<(ProgramMemory, Vec<Operation>, Vec<ArtifactCommand>, image::DynamicImage), ExecErrorWithState> {
|
||||
) -> Result<(ExecState, image::DynamicImage), ExecErrorWithState> {
|
||||
let ctx = new_context(units, true, project_directory).await?;
|
||||
let res = do_execute_and_snapshot(&ctx, ast).await.map(|(state, snap)| {
|
||||
(
|
||||
state.mod_local.memory,
|
||||
state.mod_local.operations,
|
||||
state.global.artifact_commands,
|
||||
snap,
|
||||
)
|
||||
});
|
||||
let res = do_execute_and_snapshot(&ctx, ast).await;
|
||||
ctx.close().await;
|
||||
res
|
||||
}
|
||||
@ -71,7 +64,7 @@ pub async fn execute_and_snapshot_no_auth(
|
||||
async fn do_execute_and_snapshot(
|
||||
ctx: &ExecutorContext,
|
||||
program: Program,
|
||||
) -> Result<(crate::execution::ExecState, image::DynamicImage), ExecErrorWithState> {
|
||||
) -> Result<(ExecState, image::DynamicImage), ExecErrorWithState> {
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
let snapshot_png_bytes = ctx
|
||||
.execute_and_prepare_snapshot(&program, &mut exec_state)
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart add_lots.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,3 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph mind map add_lots.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
```mermaid
|
||||
mindmap
|
||||
root
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart angled_line.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,82 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph path2 [Path]
|
||||
2["Path<br>[35, 67, 0]"]
|
||||
3["Segment<br>[73, 94, 0]"]
|
||||
4["Segment<br>[100, 130, 0]"]
|
||||
5["Segment<br>[136, 159, 0]"]
|
||||
6["Segment<br>[165, 202, 0]"]
|
||||
7["Segment<br>[208, 232, 0]"]
|
||||
8["Segment<br>[238, 246, 0]"]
|
||||
9[Solid2d]
|
||||
end
|
||||
1["Plane<br>[10, 29, 0]"]
|
||||
10["Sweep Extrusion<br>[252, 265, 0]"]
|
||||
11[Wall]
|
||||
12[Wall]
|
||||
13[Wall]
|
||||
14[Wall]
|
||||
15[Wall]
|
||||
16[Wall]
|
||||
17["Cap Start"]
|
||||
18["Cap End"]
|
||||
19["SweepEdge Opposite"]
|
||||
20["SweepEdge Adjacent"]
|
||||
21["SweepEdge Opposite"]
|
||||
22["SweepEdge Adjacent"]
|
||||
23["SweepEdge Opposite"]
|
||||
24["SweepEdge Adjacent"]
|
||||
25["SweepEdge Opposite"]
|
||||
26["SweepEdge Adjacent"]
|
||||
27["SweepEdge Opposite"]
|
||||
28["SweepEdge Adjacent"]
|
||||
29["SweepEdge Opposite"]
|
||||
30["SweepEdge Adjacent"]
|
||||
1 --- 2
|
||||
2 --- 3
|
||||
2 --- 4
|
||||
2 --- 5
|
||||
2 --- 6
|
||||
2 --- 7
|
||||
2 --- 8
|
||||
2 ---- 10
|
||||
2 --- 9
|
||||
3 --- 16
|
||||
3 --- 29
|
||||
3 --- 30
|
||||
4 --- 15
|
||||
4 --- 27
|
||||
4 --- 28
|
||||
5 --- 14
|
||||
5 --- 25
|
||||
5 --- 26
|
||||
6 --- 13
|
||||
6 --- 23
|
||||
6 --- 24
|
||||
7 --- 12
|
||||
7 --- 21
|
||||
7 --- 22
|
||||
8 --- 11
|
||||
8 --- 19
|
||||
8 --- 20
|
||||
10 --- 11
|
||||
10 --- 12
|
||||
10 --- 13
|
||||
10 --- 14
|
||||
10 --- 15
|
||||
10 --- 16
|
||||
10 --- 17
|
||||
10 --- 18
|
||||
10 --- 19
|
||||
10 --- 20
|
||||
10 --- 21
|
||||
10 --- 22
|
||||
10 --- 23
|
||||
10 --- 24
|
||||
10 --- 25
|
||||
10 --- 26
|
||||
10 --- 27
|
||||
10 --- 28
|
||||
10 --- 29
|
||||
10 --- 30
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph mind map angled_line.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,52 @@
|
||||
```mermaid
|
||||
mindmap
|
||||
root
|
||||
Plane
|
||||
Path
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Sweep Extrusion
|
||||
Wall
|
||||
Wall
|
||||
Wall
|
||||
Wall
|
||||
Wall
|
||||
Wall
|
||||
Cap Start
|
||||
Cap End
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Solid2d
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart array_elem_pop.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,3 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph mind map array_elem_pop.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
```mermaid
|
||||
mindmap
|
||||
root
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart array_elem_push.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,3 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph mind map array_elem_push.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
```mermaid
|
||||
mindmap
|
||||
root
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart array_range_expr.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,3 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph mind map array_range_expr.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
```mermaid
|
||||
mindmap
|
||||
root
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart array_range_negative_expr.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,3 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph mind map array_range_negative_expr.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
```mermaid
|
||||
mindmap
|
||||
root
|
||||
```
|
@ -0,0 +1,937 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact commands artifact_graph_example_code1.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
[
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "make_plane",
|
||||
"origin": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"x_axis": {
|
||||
"x": 1.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"y_axis": {
|
||||
"x": 0.0,
|
||||
"y": 1.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"size": 100.0,
|
||||
"clobber": false,
|
||||
"hide": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "plane_set_color",
|
||||
"plane_id": "[uuid]",
|
||||
"color": {
|
||||
"r": 0.7,
|
||||
"g": 0.28,
|
||||
"b": 0.28,
|
||||
"a": 0.4
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "make_plane",
|
||||
"origin": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"x_axis": {
|
||||
"x": 0.0,
|
||||
"y": 1.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"y_axis": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
},
|
||||
"size": 100.0,
|
||||
"clobber": false,
|
||||
"hide": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "plane_set_color",
|
||||
"plane_id": "[uuid]",
|
||||
"color": {
|
||||
"r": 0.28,
|
||||
"g": 0.7,
|
||||
"b": 0.28,
|
||||
"a": 0.4
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "make_plane",
|
||||
"origin": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"x_axis": {
|
||||
"x": 1.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"y_axis": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
},
|
||||
"size": 100.0,
|
||||
"clobber": false,
|
||||
"hide": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "plane_set_color",
|
||||
"plane_id": "[uuid]",
|
||||
"color": {
|
||||
"r": 0.28,
|
||||
"g": 0.28,
|
||||
"b": 0.7,
|
||||
"a": 0.4
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "make_plane",
|
||||
"origin": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"x_axis": {
|
||||
"x": -1.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"y_axis": {
|
||||
"x": 0.0,
|
||||
"y": 1.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"size": 100.0,
|
||||
"clobber": false,
|
||||
"hide": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "make_plane",
|
||||
"origin": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"x_axis": {
|
||||
"x": 0.0,
|
||||
"y": -1.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"y_axis": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
},
|
||||
"size": 100.0,
|
||||
"clobber": false,
|
||||
"hide": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "make_plane",
|
||||
"origin": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"x_axis": {
|
||||
"x": -1.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"y_axis": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
},
|
||||
"size": 100.0,
|
||||
"clobber": false,
|
||||
"hide": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "edge_lines_visible",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "set_scene_units",
|
||||
"unit": "mm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "object_visible",
|
||||
"object_id": "[uuid]",
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "object_visible",
|
||||
"object_id": "[uuid]",
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
12,
|
||||
31,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "make_plane",
|
||||
"origin": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"x_axis": {
|
||||
"x": 1.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"y_axis": {
|
||||
"x": 0.0,
|
||||
"y": 1.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"size": 60.0,
|
||||
"clobber": false,
|
||||
"hide": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
37,
|
||||
64,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "enable_sketch_mode",
|
||||
"entity_id": "[uuid]",
|
||||
"ortho": false,
|
||||
"animated": false,
|
||||
"adjust_camera": false,
|
||||
"planar_normal": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
37,
|
||||
64,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "start_path"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
37,
|
||||
64,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "move_path_pen",
|
||||
"path": "[uuid]",
|
||||
"to": {
|
||||
"x": -5.0,
|
||||
"y": -5.0,
|
||||
"z": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
70,
|
||||
86,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "extend_path",
|
||||
"path": "[uuid]",
|
||||
"segment": {
|
||||
"type": "line",
|
||||
"end": {
|
||||
"x": 0.0,
|
||||
"y": 10.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"relative": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
92,
|
||||
119,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "extend_path",
|
||||
"path": "[uuid]",
|
||||
"segment": {
|
||||
"type": "line",
|
||||
"end": {
|
||||
"x": 10.55,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"relative": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
125,
|
||||
150,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "extend_path",
|
||||
"path": "[uuid]",
|
||||
"segment": {
|
||||
"type": "line",
|
||||
"end": {
|
||||
"x": 0.0,
|
||||
"y": -10.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"relative": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
156,
|
||||
203,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "extend_path",
|
||||
"path": "[uuid]",
|
||||
"segment": {
|
||||
"type": "line",
|
||||
"end": {
|
||||
"x": -5.0,
|
||||
"y": -5.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"relative": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
209,
|
||||
217,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "close_path",
|
||||
"path_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
209,
|
||||
217,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "sketch_mode_disable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "enable_sketch_mode",
|
||||
"entity_id": "[uuid]",
|
||||
"ortho": false,
|
||||
"animated": false,
|
||||
"adjust_camera": false,
|
||||
"planar_normal": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "extrude",
|
||||
"target": "[uuid]",
|
||||
"distance": -10.0,
|
||||
"faces": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "sketch_mode_disable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "object_bring_to_front",
|
||||
"object_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_extrusion_face_info",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_opposite_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_next_adjacent_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_opposite_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_next_adjacent_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_opposite_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_next_adjacent_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_opposite_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
231,
|
||||
254,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_next_adjacent_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
260,
|
||||
301,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_fillet_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"radius": 5.0,
|
||||
"tolerance": 0.0000001,
|
||||
"cut_type": "fillet",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
352,
|
||||
379,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "enable_sketch_mode",
|
||||
"entity_id": "[uuid]",
|
||||
"ortho": false,
|
||||
"animated": false,
|
||||
"adjust_camera": false,
|
||||
"planar_normal": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
352,
|
||||
379,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "start_path"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
352,
|
||||
379,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "move_path_pen",
|
||||
"path": "[uuid]",
|
||||
"to": {
|
||||
"x": -2.0,
|
||||
"y": -6.0,
|
||||
"z": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
385,
|
||||
400,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "extend_path",
|
||||
"path": "[uuid]",
|
||||
"segment": {
|
||||
"type": "line",
|
||||
"end": {
|
||||
"x": 2.0,
|
||||
"y": 3.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"relative": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
406,
|
||||
422,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "extend_path",
|
||||
"path": "[uuid]",
|
||||
"segment": {
|
||||
"type": "line",
|
||||
"end": {
|
||||
"x": 2.0,
|
||||
"y": -3.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"relative": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
428,
|
||||
475,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "extend_path",
|
||||
"path": "[uuid]",
|
||||
"segment": {
|
||||
"type": "line",
|
||||
"end": {
|
||||
"x": -2.0,
|
||||
"y": -6.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"relative": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
481,
|
||||
489,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "close_path",
|
||||
"path_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
503,
|
||||
524,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "enable_sketch_mode",
|
||||
"entity_id": "[uuid]",
|
||||
"ortho": false,
|
||||
"animated": false,
|
||||
"adjust_camera": false,
|
||||
"planar_normal": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
503,
|
||||
524,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "extrude",
|
||||
"target": "[uuid]",
|
||||
"distance": 5.0,
|
||||
"faces": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
503,
|
||||
524,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "sketch_mode_disable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
503,
|
||||
524,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "object_bring_to_front",
|
||||
"object_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
503,
|
||||
524,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_extrusion_face_info",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
503,
|
||||
524,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_opposite_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
503,
|
||||
524,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_next_adjacent_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
503,
|
||||
524,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_opposite_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
503,
|
||||
524,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_next_adjacent_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
503,
|
||||
524,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_opposite_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
503,
|
||||
524,
|
||||
0
|
||||
],
|
||||
"command": {
|
||||
"type": "solid3d_get_next_adjacent_edge",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]",
|
||||
"face_id": "[uuid]"
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart artifact_graph_example_code1.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,109 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph path2 [Path]
|
||||
2["Path<br>[37, 64, 0]"]
|
||||
3["Segment<br>[70, 86, 0]"]
|
||||
4["Segment<br>[92, 119, 0]"]
|
||||
5["Segment<br>[125, 150, 0]"]
|
||||
6["Segment<br>[156, 203, 0]"]
|
||||
7["Segment<br>[209, 217, 0]"]
|
||||
8[Solid2d]
|
||||
end
|
||||
subgraph path25 [Path]
|
||||
25["Path<br>[352, 379, 0]"]
|
||||
26["Segment<br>[385, 400, 0]"]
|
||||
27["Segment<br>[406, 422, 0]"]
|
||||
28["Segment<br>[428, 475, 0]"]
|
||||
29["Segment<br>[481, 489, 0]"]
|
||||
30[Solid2d]
|
||||
end
|
||||
1["Plane<br>[12, 31, 0]"]
|
||||
9["Sweep Extrusion<br>[231, 254, 0]"]
|
||||
10[Wall]
|
||||
11[Wall]
|
||||
12[Wall]
|
||||
13[Wall]
|
||||
14["Cap Start"]
|
||||
15["Cap End"]
|
||||
16["SweepEdge Opposite"]
|
||||
17["SweepEdge Adjacent"]
|
||||
18["SweepEdge Opposite"]
|
||||
19["SweepEdge Adjacent"]
|
||||
20["SweepEdge Opposite"]
|
||||
21["SweepEdge Adjacent"]
|
||||
22["SweepEdge Opposite"]
|
||||
23["SweepEdge Adjacent"]
|
||||
24["EdgeCut Fillet<br>[260, 301, 0]"]
|
||||
31["Sweep Extrusion<br>[503, 524, 0]"]
|
||||
32[Wall]
|
||||
33[Wall]
|
||||
34[Wall]
|
||||
35["Cap End"]
|
||||
36["SweepEdge Opposite"]
|
||||
37["SweepEdge Adjacent"]
|
||||
38["SweepEdge Opposite"]
|
||||
39["SweepEdge Adjacent"]
|
||||
40["SweepEdge Opposite"]
|
||||
41["SweepEdge Adjacent"]
|
||||
1 --- 2
|
||||
2 --- 3
|
||||
2 --- 4
|
||||
2 --- 5
|
||||
2 --- 6
|
||||
2 --- 7
|
||||
2 ---- 9
|
||||
2 --- 8
|
||||
3 --- 13
|
||||
3 --- 22
|
||||
3 --- 23
|
||||
4 --- 12
|
||||
4 --- 20
|
||||
4 --- 21
|
||||
4 --- 24
|
||||
5 --- 11
|
||||
5 --- 18
|
||||
5 --- 19
|
||||
6 --- 10
|
||||
6 --- 16
|
||||
6 --- 17
|
||||
9 --- 10
|
||||
9 --- 11
|
||||
9 --- 12
|
||||
9 --- 13
|
||||
9 --- 14
|
||||
9 --- 15
|
||||
9 --- 16
|
||||
9 --- 17
|
||||
9 --- 18
|
||||
9 --- 19
|
||||
9 --- 20
|
||||
9 --- 21
|
||||
9 --- 22
|
||||
9 --- 23
|
||||
11 --- 25
|
||||
25 --- 26
|
||||
25 --- 27
|
||||
25 --- 28
|
||||
25 --- 29
|
||||
25 ---- 31
|
||||
25 --- 30
|
||||
26 --- 34
|
||||
26 --- 40
|
||||
26 --- 41
|
||||
27 --- 33
|
||||
27 --- 38
|
||||
27 --- 39
|
||||
28 --- 32
|
||||
28 --- 36
|
||||
28 --- 37
|
||||
31 --- 32
|
||||
31 --- 33
|
||||
31 --- 34
|
||||
31 --- 35
|
||||
31 --- 36
|
||||
31 --- 37
|
||||
31 --- 38
|
||||
31 --- 39
|
||||
31 --- 40
|
||||
31 --- 41
|
||||
```
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Artifact graph mind map artifact_graph_example_code1.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -0,0 +1,92 @@
|
||||
```mermaid
|
||||
mindmap
|
||||
root
|
||||
Plane
|
||||
Path
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
EdgeCut Fillet
|
||||
Segment
|
||||
Wall
|
||||
Path
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Sweep Extrusion
|
||||
Wall
|
||||
Wall
|
||||
Wall
|
||||
Cap End
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Solid2d
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Sweep Extrusion
|
||||
Wall
|
||||
Wall
|
||||
Path
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Wall
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Segment
|
||||
Sweep Extrusion
|
||||
Wall
|
||||
Wall
|
||||
Wall
|
||||
Cap End
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Solid2d
|
||||
Wall
|
||||
Wall
|
||||
Cap Start
|
||||
Cap End
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
SweepEdge Opposite
|
||||
SweepEdge Adjacent
|
||||
Solid2d
|
||||
```
|
837
src/wasm-lib/kcl/tests/artifact_graph_example_code1/ast.snap
Normal file
837
src/wasm-lib/kcl/tests/artifact_graph_example_code1/ast.snap
Normal file
@ -0,0 +1,837 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Result of parsing artifact_graph_example_code1.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
{
|
||||
"Ok": {
|
||||
"body": [
|
||||
{
|
||||
"declaration": {
|
||||
"end": 217,
|
||||
"id": {
|
||||
"end": 9,
|
||||
"name": "sketch001",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"init": {
|
||||
"body": [
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"end": 30,
|
||||
"raw": "'XY'",
|
||||
"start": 26,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": "XY"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 25,
|
||||
"name": "startSketchOn",
|
||||
"start": 12,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 31,
|
||||
"start": 12,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"argument": {
|
||||
"end": 55,
|
||||
"raw": "5",
|
||||
"start": 54,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 5.0
|
||||
},
|
||||
"end": 55,
|
||||
"operator": "-",
|
||||
"start": 53,
|
||||
"type": "UnaryExpression",
|
||||
"type": "UnaryExpression"
|
||||
},
|
||||
{
|
||||
"argument": {
|
||||
"end": 59,
|
||||
"raw": "5",
|
||||
"start": 58,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 5.0
|
||||
},
|
||||
"end": 59,
|
||||
"operator": "-",
|
||||
"start": 57,
|
||||
"type": "UnaryExpression",
|
||||
"type": "UnaryExpression"
|
||||
}
|
||||
],
|
||||
"end": 60,
|
||||
"start": 52,
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression"
|
||||
},
|
||||
{
|
||||
"end": 63,
|
||||
"start": 62,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 51,
|
||||
"name": "startProfileAt",
|
||||
"start": 37,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 64,
|
||||
"start": 37,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"end": 77,
|
||||
"raw": "0",
|
||||
"start": 76,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 0.0
|
||||
},
|
||||
{
|
||||
"end": 81,
|
||||
"raw": "10",
|
||||
"start": 79,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 10.0
|
||||
}
|
||||
],
|
||||
"end": 82,
|
||||
"start": 75,
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression"
|
||||
},
|
||||
{
|
||||
"end": 85,
|
||||
"start": 84,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 74,
|
||||
"name": "line",
|
||||
"start": 70,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 86,
|
||||
"start": 70,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"end": 103,
|
||||
"raw": "10.55",
|
||||
"start": 98,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 10.55
|
||||
},
|
||||
{
|
||||
"end": 106,
|
||||
"raw": "0",
|
||||
"start": 105,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 0.0
|
||||
}
|
||||
],
|
||||
"end": 107,
|
||||
"start": 97,
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression"
|
||||
},
|
||||
{
|
||||
"end": 110,
|
||||
"start": 109,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
},
|
||||
{
|
||||
"end": 118,
|
||||
"start": 112,
|
||||
"type": "TagDeclarator",
|
||||
"type": "TagDeclarator",
|
||||
"value": "seg01"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 96,
|
||||
"name": "line",
|
||||
"start": 92,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 119,
|
||||
"start": 92,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"end": 132,
|
||||
"raw": "0",
|
||||
"start": 131,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 0.0
|
||||
},
|
||||
{
|
||||
"argument": {
|
||||
"end": 137,
|
||||
"raw": "10",
|
||||
"start": 135,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 10.0
|
||||
},
|
||||
"end": 137,
|
||||
"operator": "-",
|
||||
"start": 134,
|
||||
"type": "UnaryExpression",
|
||||
"type": "UnaryExpression"
|
||||
}
|
||||
],
|
||||
"end": 138,
|
||||
"start": 130,
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression"
|
||||
},
|
||||
{
|
||||
"end": 141,
|
||||
"start": 140,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
},
|
||||
{
|
||||
"end": 149,
|
||||
"start": 143,
|
||||
"type": "TagDeclarator",
|
||||
"type": "TagDeclarator",
|
||||
"value": "seg02"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 129,
|
||||
"name": "line",
|
||||
"start": 125,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 150,
|
||||
"start": 125,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"end": 179,
|
||||
"start": 178,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 177,
|
||||
"name": "profileStartX",
|
||||
"start": 164,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 180,
|
||||
"start": 164,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"end": 197,
|
||||
"start": 196,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 195,
|
||||
"name": "profileStartY",
|
||||
"start": 182,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 198,
|
||||
"start": 182,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
}
|
||||
],
|
||||
"end": 199,
|
||||
"start": 163,
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression"
|
||||
},
|
||||
{
|
||||
"end": 202,
|
||||
"start": 201,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 162,
|
||||
"name": "lineTo",
|
||||
"start": 156,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 203,
|
||||
"start": 156,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"end": 216,
|
||||
"start": 215,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 214,
|
||||
"name": "close",
|
||||
"start": 209,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 217,
|
||||
"start": 209,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
}
|
||||
],
|
||||
"end": 217,
|
||||
"start": 12,
|
||||
"type": "PipeExpression",
|
||||
"type": "PipeExpression"
|
||||
},
|
||||
"start": 0,
|
||||
"type": "VariableDeclarator"
|
||||
},
|
||||
"end": 217,
|
||||
"kind": "const",
|
||||
"start": 0,
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration"
|
||||
},
|
||||
{
|
||||
"declaration": {
|
||||
"end": 301,
|
||||
"id": {
|
||||
"end": 228,
|
||||
"name": "extrude001",
|
||||
"start": 218,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"init": {
|
||||
"body": [
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"argument": {
|
||||
"end": 242,
|
||||
"raw": "10",
|
||||
"start": 240,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 10.0
|
||||
},
|
||||
"end": 242,
|
||||
"operator": "-",
|
||||
"start": 239,
|
||||
"type": "UnaryExpression",
|
||||
"type": "UnaryExpression"
|
||||
},
|
||||
{
|
||||
"end": 253,
|
||||
"name": "sketch001",
|
||||
"start": 244,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 238,
|
||||
"name": "extrude",
|
||||
"start": 231,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 254,
|
||||
"start": 231,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"end": 297,
|
||||
"properties": [
|
||||
{
|
||||
"end": 279,
|
||||
"key": {
|
||||
"end": 275,
|
||||
"name": "radius",
|
||||
"start": 269,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"start": 269,
|
||||
"type": "ObjectProperty",
|
||||
"value": {
|
||||
"end": 279,
|
||||
"raw": "5",
|
||||
"start": 278,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"end": 295,
|
||||
"key": {
|
||||
"end": 285,
|
||||
"name": "tags",
|
||||
"start": 281,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"start": 281,
|
||||
"type": "ObjectProperty",
|
||||
"value": {
|
||||
"elements": [
|
||||
{
|
||||
"end": 294,
|
||||
"name": "seg01",
|
||||
"start": 289,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
}
|
||||
],
|
||||
"end": 295,
|
||||
"start": 288,
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression"
|
||||
}
|
||||
}
|
||||
],
|
||||
"start": 267,
|
||||
"type": "ObjectExpression",
|
||||
"type": "ObjectExpression"
|
||||
},
|
||||
{
|
||||
"end": 300,
|
||||
"start": 299,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 266,
|
||||
"name": "fillet",
|
||||
"start": 260,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 301,
|
||||
"start": 260,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
}
|
||||
],
|
||||
"end": 301,
|
||||
"start": 231,
|
||||
"type": "PipeExpression",
|
||||
"type": "PipeExpression"
|
||||
},
|
||||
"start": 218,
|
||||
"type": "VariableDeclarator"
|
||||
},
|
||||
"end": 301,
|
||||
"kind": "const",
|
||||
"start": 218,
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration"
|
||||
},
|
||||
{
|
||||
"declaration": {
|
||||
"end": 489,
|
||||
"id": {
|
||||
"end": 311,
|
||||
"name": "sketch002",
|
||||
"start": 302,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"init": {
|
||||
"body": [
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"end": 338,
|
||||
"name": "extrude001",
|
||||
"start": 328,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
},
|
||||
{
|
||||
"end": 345,
|
||||
"name": "seg02",
|
||||
"start": 340,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 327,
|
||||
"name": "startSketchOn",
|
||||
"start": 314,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 346,
|
||||
"start": 314,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"argument": {
|
||||
"end": 370,
|
||||
"raw": "2",
|
||||
"start": 369,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 2.0
|
||||
},
|
||||
"end": 370,
|
||||
"operator": "-",
|
||||
"start": 368,
|
||||
"type": "UnaryExpression",
|
||||
"type": "UnaryExpression"
|
||||
},
|
||||
{
|
||||
"argument": {
|
||||
"end": 374,
|
||||
"raw": "6",
|
||||
"start": 373,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 6.0
|
||||
},
|
||||
"end": 374,
|
||||
"operator": "-",
|
||||
"start": 372,
|
||||
"type": "UnaryExpression",
|
||||
"type": "UnaryExpression"
|
||||
}
|
||||
],
|
||||
"end": 375,
|
||||
"start": 367,
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression"
|
||||
},
|
||||
{
|
||||
"end": 378,
|
||||
"start": 377,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 366,
|
||||
"name": "startProfileAt",
|
||||
"start": 352,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 379,
|
||||
"start": 352,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"end": 392,
|
||||
"raw": "2",
|
||||
"start": 391,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 2.0
|
||||
},
|
||||
{
|
||||
"end": 395,
|
||||
"raw": "3",
|
||||
"start": 394,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 3.0
|
||||
}
|
||||
],
|
||||
"end": 396,
|
||||
"start": 390,
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression"
|
||||
},
|
||||
{
|
||||
"end": 399,
|
||||
"start": 398,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 389,
|
||||
"name": "line",
|
||||
"start": 385,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 400,
|
||||
"start": 385,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"end": 413,
|
||||
"raw": "2",
|
||||
"start": 412,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 2.0
|
||||
},
|
||||
{
|
||||
"argument": {
|
||||
"end": 417,
|
||||
"raw": "3",
|
||||
"start": 416,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 3.0
|
||||
},
|
||||
"end": 417,
|
||||
"operator": "-",
|
||||
"start": 415,
|
||||
"type": "UnaryExpression",
|
||||
"type": "UnaryExpression"
|
||||
}
|
||||
],
|
||||
"end": 418,
|
||||
"start": 411,
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression"
|
||||
},
|
||||
{
|
||||
"end": 421,
|
||||
"start": 420,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 410,
|
||||
"name": "line",
|
||||
"start": 406,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 422,
|
||||
"start": 406,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"end": 451,
|
||||
"start": 450,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 449,
|
||||
"name": "profileStartX",
|
||||
"start": 436,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 452,
|
||||
"start": 436,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"end": 469,
|
||||
"start": 468,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 467,
|
||||
"name": "profileStartY",
|
||||
"start": 454,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 470,
|
||||
"start": 454,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
}
|
||||
],
|
||||
"end": 471,
|
||||
"start": 435,
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression"
|
||||
},
|
||||
{
|
||||
"end": 474,
|
||||
"start": 473,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 434,
|
||||
"name": "lineTo",
|
||||
"start": 428,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 475,
|
||||
"start": 428,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
{
|
||||
"end": 488,
|
||||
"start": 487,
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 486,
|
||||
"name": "close",
|
||||
"start": 481,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 489,
|
||||
"start": 481,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
}
|
||||
],
|
||||
"end": 489,
|
||||
"start": 314,
|
||||
"type": "PipeExpression",
|
||||
"type": "PipeExpression"
|
||||
},
|
||||
"start": 302,
|
||||
"type": "VariableDeclarator"
|
||||
},
|
||||
"end": 489,
|
||||
"kind": "const",
|
||||
"start": 302,
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration"
|
||||
},
|
||||
{
|
||||
"declaration": {
|
||||
"end": 524,
|
||||
"id": {
|
||||
"end": 500,
|
||||
"name": "extrude002",
|
||||
"start": 490,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"init": {
|
||||
"arguments": [
|
||||
{
|
||||
"end": 512,
|
||||
"raw": "5",
|
||||
"start": 511,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": 5.0
|
||||
},
|
||||
{
|
||||
"end": 523,
|
||||
"name": "sketch002",
|
||||
"start": 514,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
}
|
||||
],
|
||||
"callee": {
|
||||
"end": 510,
|
||||
"name": "extrude",
|
||||
"start": 503,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"end": 524,
|
||||
"start": 503,
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression"
|
||||
},
|
||||
"start": 490,
|
||||
"type": "VariableDeclarator"
|
||||
},
|
||||
"end": 524,
|
||||
"kind": "const",
|
||||
"start": 490,
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration"
|
||||
}
|
||||
],
|
||||
"end": 525,
|
||||
"start": 0
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10.55, 0], %, $seg01)
|
||||
|> line([0, -10], %, $seg02)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(-10, sketch001)
|
||||
|> fillet({ radius = 5, tags = [seg01] }, %)
|
||||
sketch002 = startSketchOn(extrude001, seg02)
|
||||
|> startProfileAt([-2, -6], %)
|
||||
|> line([2, 3], %)
|
||||
|> line([2, -3], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude002 = extrude(5, sketch002)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user