Compare commits

...

12 Commits

Author SHA1 Message Date
f8f44743fa Bug fix: open project command lists project names (#5176)
* Amend project open test to show failing case

* Fix command config to use live context value

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Update src/lib/commandBarConfigs/projectsCommandConfig.ts

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-01-31 16:04:34 +00:00
f262eda12a Turn off snapshot commit bot (#5197)
Our team is actively investigating a recent spike in flaky WebRTC
connection behavior, which has impacted the web-only Playwright snapshot
tests particularly hard. The bot squashes other GH Actions in it's wake,
so I think we should turn it off so we can see the other E2E tests more
clearly
2025-01-31 10:57:52 -05:00
9e1136195a Fix: Center rectangle now works again, works with new LiteralValue structure (#5172)
* fix: new Literal data structure update

* fix: updated the LiteralValue dereferencing and added some type narrowing helpers

* fix: updating formatting

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* fix: implementing a safer dereference method until we update createLiteraly()

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* why is this fallback here

* fix: updating the type narrowing function

* fix: restore this... see if snapshots trigger again

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* bump

* bump

* Add number with units formatting as KCL (#5195)

* Add number with units formatting as KCL

* Change type assertion helper to check what we need

* Fix rectangle unit test

* fix: adding a wait for execution to prevent clicking before lines are rendered

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-01-31 09:45:39 -06:00
4ff07ddaee Fix: Clearing sketch DOM elements after redirecting to the home page (#4965)
* fix: clear the previous DOM elements after page redirect

* fix: removed await delay since it can take awhile to destroy the sketch

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* fix: added E2E test which actually caught a logic bug, moved the logic to the correct location

* fix: removing unused import

* fix: push main back...

* fix: restoring code to old state

* fix: moved cleanup code

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-31 07:01:51 -06:00
1e565379a7 allow setting units in subfiles/ turn on execution in modules (#5178)
* allow setting units in subfiles

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* turn on execution in modules

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates to tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanup bad snaps

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* change some snapshots;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-01-30 00:26:49 +00:00
76e34ac4da Ignore cloned kcl-samples (#5179) 2025-01-29 17:20:03 -05:00
4cd427bf91 fix: Remove link from code editor docs (#5170)
Remove link from the stdlib import() docs
2025-01-29 16:00:00 -05:00
f321ecdff0 add more unit names/abreevs (#5173)
* add more unit names

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* typo

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-01-29 12:09:27 +11:00
d114ab798c fix subdirs (opening kcl-samples from kcl-samples dir) (#5171)
* WIP: Add the KCL file path into the executor

* reuse the stuff that works with settings.project_directory

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes on both sides

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes on both sides

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* helper method

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* update kcl-samples tests to not change dirs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* update kcl-samples tests to not change dirs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fmt

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-01-28 23:43:39 +00:00
69fec37107 prepare change in fillet api (#5168)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* bump version

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* more snaps

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-01-28 22:22:19 +00:00
8ca8c49cc3 Fix to not error when using imported whole modules (#5161)
* Fix to not error when using imported whole modules

* Add TODO about adding a warning
2025-01-28 20:17:27 +00:00
b25fc302fd Support using import statements for non-KCL files (#5122)
* Improve checking of import paths

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Import path kinds in AST

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Use import statement for foreign files

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-01-29 08:28:32 +13:00
103 changed files with 7116 additions and 894 deletions

View File

@ -126,20 +126,20 @@ jobs:
- name: build electron
shell: bash
run: yarn tron:package
- name: Run ubuntu/chrome snapshots
if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
shell: bash
# TODO: break this in its own job, for now it's not slowing down the overall execution as ubuntu is the quickest,
# but we could do better. This forces a large 1/1 shard of all 20 snapshot tests that runs in about 3 minutes.
run: |
PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=1/1
env:
CI: true
NODE_ENV: development
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
# - name: Run ubuntu/chrome snapshots
# if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
# shell: bash
# # TODO: break this in its own job, for now it's not slowing down the overall execution as ubuntu is the quickest,
# # but we could do better. This forces a large 1/1 shard of all 20 snapshot tests that runs in about 3 minutes.
# run: |
# PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=1/1
# env:
# CI: true
# NODE_ENV: development
# VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
# VITE_KC_SKIP_AUTH: true
# token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
# snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }}
with:
@ -162,20 +162,20 @@ jobs:
then echo "modified=true" >> $GITHUB_OUTPUT
else echo "modified=false" >> $GITHUB_OUTPUT
fi
- name: Commit changes, if any
if: steps.git-check.outputs.modified == 'true'
shell: bash
run: |
git add .
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
git push
git push origin ${{ github.head_ref }}
# - name: Commit changes, if any
# if: steps.git-check.outputs.modified == 'true'
# shell: bash
# run: |
# git add .
# git config --local user.email "github-actions[bot]@users.noreply.github.com"
# git config --local user.name "github-actions[bot]"
# git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
# git fetch origin
# echo ${{ github.head_ref }}
# git checkout ${{ github.head_ref }}
# git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
# git push
# git push origin ${{ github.head_ref }}
# only upload artifacts if there's actually changes
- uses: actions/upload-artifact@v4
if: steps.git-check.outputs.modified == 'true'

2
.gitignore vendored
View File

@ -44,7 +44,7 @@ e2e/playwright/temp3.png
e2e/playwright/export-snapshots/*
!e2e/playwright/export-snapshots/*.png
/kcl-samples
/test-results/
/playwright-report/
/blob-report/

View File

@ -4,14 +4,16 @@ excerpt: "Import a CAD file."
layout: manual
---
**WARNING:** This function is deprecated.
Import a CAD file.
**DEPRECATED** Prefer to use import statements.
For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.
Note: The import command currently only works when using the native Modeling App.
For importing KCL functions using the `import` statement, see the docs on [KCL modules](/docs/kcl/modules).
```js
import(file_path: String, options?: ImportFormat) -> ImportedGeometry
```

View File

@ -51,7 +51,6 @@ layout: manual
* [`helixRevolutions`](kcl/helixRevolutions)
* [`hole`](kcl/hole)
* [`hollow`](kcl/hollow)
* [`import`](kcl/import)
* [`inch`](kcl/inch)
* [`lastSegX`](kcl/lastSegX)
* [`lastSegY`](kcl/lastSegY)

View File

@ -92765,7 +92765,7 @@
{
"name": "import",
"summary": "Import a CAD file.",
"description": "For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.\n\nNote: The import command currently only works when using the native Modeling App.\n\nFor importing KCL functions using the `import` statement, see the docs on [KCL modules](/docs/kcl/modules).",
"description": "**DEPRECATED** Prefer to use import statements.\n\nFor formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.\n\nNote: The import command currently only works when using the native Modeling App.",
"tags": [],
"keywordArguments": false,
"args": [
@ -93168,7 +93168,7 @@
"labelRequired": true
},
"unpublished": false,
"deprecated": false,
"deprecated": true,
"examples": [
"model = import(\"tests/inputs/cube.obj\")",
"model = import(\"tests/inputs/cube.obj\", { format = \"obj\", units = \"m\" })",

View File

@ -4,7 +4,6 @@ import { expect } from '@playwright/test'
type CmdBarSerialised =
| {
stage: 'commandBarClosed'
// TODO no more properties needed but needs to be implemented in _serialiseCmdBar
}
| {
stage: 'pickCommand'
@ -37,6 +36,9 @@ export class CmdBarFixture {
}
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
return { stage: 'commandBarClosed' }
}
const reviewForm = this.page.locator('#review-form')
const getHeaderArgs = async () => {
const inputs = await this.page.getByTestId('cmd-bar-input-tab').all()

View File

@ -1525,7 +1525,7 @@ extrude001 = extrude(200, sketch001)`)
test(
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
async ({ context, page, cmdBar, homePage }, testInfo) => {
await context.folderSetupFn(async (dir) => {
await Promise.all([
fsp.mkdir(path.join(dir, 'router-template-slate'), { recursive: true }),
@ -1563,11 +1563,38 @@ test(
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await test.step('Opening the bracket project via command palette should load the stream', async () => {
await homePage.expectState({
projectCards: [
{
title: 'bracket',
fileCount: 1,
},
{
title: 'router-template-slate',
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
await page.getByText('bracket').click()
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('open project')
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Open project',
currentArgKey: 'name',
currentArgValue: '',
headerArguments: {
Name: '',
},
highlightedHeaderArg: 'name',
})
await cmdBar.argumentInput.fill('brac')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'commandBarClosed',
})
await u.waitForPageLoad()
@ -1588,7 +1615,7 @@ test(
await expect(page.getByText('Create project')).toBeVisible()
})
await test.step('Opening the router-template project should load the stream', async () => {
await test.step('Opening the router-template project via link should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('router-template-slate')).toBeVisible()
@ -1605,16 +1632,26 @@ test(
.toBeLessThan(15)
})
await test.step('Opening the router-template project should load the stream', async () => {
await test.step('The projects on the home page should still be normal', async () => {
await page.getByTestId('project-sidebar-toggle').click()
await expect(
page.getByRole('button', { name: 'Go to Home' })
).toBeVisible()
await page.getByRole('button', { name: 'Go to Home' }).click()
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('router-template-slate')).toBeVisible()
await expect(page.getByText('Create project')).toBeVisible()
await homePage.expectState({
projectCards: [
{
title: 'bracket',
fileCount: 1,
},
{
title: 'router-template-slate',
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
})
}
)

View File

@ -34,7 +34,7 @@ test.describe('Sketch tests', () => {
screwRadius = 3
wireRadius = 2
wireOffset = 0.5
screwHole = startSketchOn('XY')
${startProfileAt1}
|> arc({
@ -42,7 +42,7 @@ test.describe('Sketch tests', () => {
angleStart = 0,
angleEnd = 360
}, %)
part001 = startSketchOn('XY')
${startProfileAt2}
|> xLine(width * .5, %)
@ -51,7 +51,7 @@ test.describe('Sketch tests', () => {
|> close(%)
|> hole(screwHole, %)
|> extrude(thickness, %)
part002 = startSketchOn('-XZ')
${startProfileAt3}
|> xLine(width / 4, %)
@ -99,6 +99,7 @@ test.describe('Sketch tests', () => {
test('Can delete most of a sketch and the line tool will still work', async ({
page,
homePage,
scene,
}) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
@ -112,12 +113,13 @@ test.describe('Sketch tests', () => {
})
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
await expect(async () => {
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
).toBeEnabled({ timeout: 1000 })
).toBeEnabled({ timeout: 2000 })
await page.getByRole('button', { name: 'Edit Sketch' }).click()
}).toPass({ timeout: 40_000, intervals: [1_000] })
@ -1063,7 +1065,7 @@ test.describe('Sketch tests', () => {
`lugHeadLength = 0.25
lugDiameter = 0.5
lugLength = 2
fn lug = (origin, length, diameter, plane) => {
lugSketch = startSketchOn(plane)
|> startProfileAt([origin[0] + lugDiameter / 2, origin[1]], %)
@ -1072,10 +1074,10 @@ test.describe('Sketch tests', () => {
|> yLineTo(0, %)
|> close(%)
|> revolve({ axis = "Y" }, %)
return lugSketch
}
lug([0, 0], 10, .5, "XY")`
)
})
@ -1127,14 +1129,14 @@ test.describe('Sketch tests', () => {
`fn in2mm = (inches) => {
return inches * 25.4
}
const railTop = in2mm(.748)
const railSide = in2mm(.024)
const railBaseWidth = in2mm(.612)
const railWideWidth = in2mm(.835)
const railBaseLength = in2mm(.200)
const railClampable = in2mm(.200)
const rail = startSketchOn('XZ')
|> startProfileAt([
-railTop / 2,
@ -1405,3 +1407,46 @@ test.describe(`Click based selection don't brick the app when clicked out of ran
})
})
})
// Regression test for https://github.com/KittyCAD/modeling-app/issues/4372
test.describe('Redirecting to home page and back to the original file should clear sketch DOM elements', () => {
test('Can redirect to home page and back to original file and have a cleared DOM', async ({
context,
page,
scene,
toolbar,
editor,
homePage,
}) => {
// We seed the scene with a single offset plane
await context.addInitScript(() => {
localStorage.setItem(
'persistCode',
` sketch001 = startSketchOn('XZ')
|> startProfileAt([256.85, 14.41], %)
|> lineTo([0, 211.07], %)
`
)
})
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
const [objClick] = scene.makeMouseHelpers(634, 274)
await objClick()
// Enter sketch mode
await toolbar.editSketch()
await expect(page.getByText('323.49')).toBeVisible()
// Open navigation side bar
await page.getByTestId('project-sidebar-toggle').click()
const goToHome = page.getByRole('button', {
name: 'Go to Home',
})
await goToHome.click()
await homePage.openProject('testDefault')
await expect(page.getByText('323.49')).not.toBeVisible()
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -124,6 +124,14 @@ export const ClientSideScene = ({
'mouseup',
toSync(sceneInfra.onMouseUp, reportRejection)
)
sceneEntitiesManager
.tearDownSketch()
.then(() => {
// no op
})
.catch((e) => {
console.error(e)
})
}
}, [])

View File

@ -98,6 +98,7 @@ export const CommandBar = () => {
'fixed inset-0 z-50 overflow-y-auto pb-4 pt-1 ' +
(isSelectionArgument ? 'pointer-events-none' : '')
}
data-testid="command-bar-wrapper"
>
<Transition.Child
enter="duration-100 ease-out"

View File

@ -9,6 +9,7 @@ export function RouteProvider({ children }: { children: ReactNode }) {
const [first, setFirstState] = useState(true)
const navigation = useNavigation()
const location = useLocation()
useEffect(() => {
// On initialization, the react-router-dom does not send a 'loading' state event.
// it sends an idle event first.

View File

@ -322,6 +322,7 @@ export class KclManager {
await this.ensureWasmInit()
const { logs, errors, execState, isInterrupted } = await executeAst({
ast,
path: codeManager.currentFilePath || undefined,
engineCommandManager: this.engineCommandManager,
})

View File

@ -80,6 +80,10 @@ export default class CodeManager {
}))
}
get currentFilePath(): string | null {
return this._currentFilePath
}
updateCurrentFilePath(path: string) {
this._currentFilePath = path
}

View File

@ -52,27 +52,22 @@ afterAll(async () => {
} catch (e) {}
})
afterEach(() => {
process.chdir('..')
})
// The tests have to be sequential because we need to change directories
// to support `import` working properly.
// @ts-expect-error
describe.sequential('Test KCL Samples from public Github repository', () => {
// @ts-expect-error
describe.sequential('when performing enginelessExecutor', () => {
describe('Test KCL Samples from public Github repository', () => {
describe('when performing enginelessExecutor', () => {
manifest.forEach((file: KclSampleFile) => {
// @ts-expect-error
it.sequential(
it(
`should execute ${file.title} (${file.file}) successfully`,
async () => {
const [dirProject, fileKcl] =
file.pathFromProjectDirectoryToFirstFile.split('/')
process.chdir(dirProject)
const code = await fs.readFile(fileKcl, 'utf-8')
const code = await fs.readFile(
file.pathFromProjectDirectoryToFirstFile,
'utf-8'
)
const ast = assertParse(code)
await enginelessExecutor(ast, programMemoryInit())
await enginelessExecutor(
ast,
programMemoryInit(),
file.pathFromProjectDirectoryToFirstFile
)
},
files.length * 1000
)

View File

@ -46,12 +46,14 @@ export const toolTips: Array<ToolTip> = [
export async function executeAst({
ast,
path,
engineCommandManager,
// If you set programMemoryOverride we assume you mean mock mode. Since that
// is the only way to go about it.
programMemoryOverride,
}: {
ast: Node<Program>
path?: string
engineCommandManager: EngineCommandManager
programMemoryOverride?: ProgramMemory
isInterrupted?: boolean
@ -63,8 +65,8 @@ export async function executeAst({
}> {
try {
const execState = await (programMemoryOverride
? enginelessExecutor(ast, programMemoryOverride)
: executor(ast, engineCommandManager))
? enginelessExecutor(ast, programMemoryOverride, path)
: executor(ast, engineCommandManager, path))
await engineCommandManager.waitForAllCommands()

View File

@ -5,6 +5,8 @@ import {
Identifier,
SourceRange,
topLevelRange,
LiteralValue,
Literal,
} from './wasm'
import {
createLiteral,
@ -37,10 +39,26 @@ beforeAll(async () => {
})
describe('Testing createLiteral', () => {
it('should create a literal', () => {
it('should create a literal number without units', () => {
const result = createLiteral(5)
expect(result.type).toBe('Literal')
expect((result as any).value.value).toBe(5)
expect((result as any).value.suffix).toBe('None')
expect((result as Literal).raw).toBe('5')
})
it('should create a literal number with units', () => {
const lit: LiteralValue = { value: 5, suffix: 'Mm' }
const result = createLiteral(lit)
expect(result.type).toBe('Literal')
expect((result as any).value.value).toBe(5)
expect((result as any).value.suffix).toBe('Mm')
expect((result as Literal).raw).toBe('5mm')
})
it('should create a literal boolean', () => {
const result = createLiteral(false)
expect(result.type).toBe('Literal')
expect((result as Literal).value).toBe(false)
expect((result as Literal).raw).toBe('false')
})
})
describe('Testing createIdentifier', () => {

View File

@ -20,6 +20,7 @@ import {
SourceRange,
sketchFromKclValue,
isPathToNodeNumber,
formatNumber,
} from './wasm'
import {
isNodeSafeToReplacePath,
@ -743,11 +744,26 @@ export function splitPathAtPipeExpression(pathToNode: PathToNode): {
return splitPathAtPipeExpression(pathToNode.slice(0, -1))
}
/**
* Note: This depends on WASM, but it's not async. Callers are responsible for
* awaiting init of the WASM module.
*/
export function createLiteral(value: LiteralValue | number): Node<Literal> {
const raw = `${value}`
if (typeof value === 'number') {
value = { value, suffix: 'None' }
}
let raw: string
if (typeof value === 'string') {
// TODO: Should we handle escape sequences?
raw = `${value}`
} else if (typeof value === 'boolean') {
raw = `${value}`
} else if (typeof value.value === 'number' && value.suffix === 'None') {
// Fast path for numbers when there are no units.
raw = `${value.value}`
} else {
raw = formatNumber(value.value, value.suffix)
}
return {
type: 'Literal',
start: 0,

View File

@ -31,6 +31,9 @@ class FileSystemManager {
}
async join(dir: string, path: string): Promise<string> {
if (path.startsWith(dir)) {
path = path.slice(dir.length)
}
return Promise.resolve(window.electron.path.join(dir, path))
}

View File

@ -6,6 +6,8 @@ import {
ArrayExpression,
BinaryExpression,
ArtifactGraph,
LiteralValue,
NumericSuffix,
} from './wasm'
import { filterArtifacts } from 'lang/std/artifactGraph'
import { isOverlap } from 'lib/utils'
@ -69,3 +71,15 @@ export function isLiteral(e: any): e is Literal {
export function isBinaryExpression(e: any): e is BinaryExpression {
return e && e.type === 'BinaryExpression'
}
export function isLiteralValueNumber(
e: LiteralValue
): e is { value: number; suffix: NumericSuffix } {
return (
typeof e === 'object' &&
'value' in e &&
typeof e.value === 'number' &&
'suffix' in e &&
typeof e.suffix === 'string'
)
}

View File

@ -1,5 +1,5 @@
import { err } from 'lib/trap'
import { initPromise, parse, ParseResult } from './wasm'
import { formatNumber, initPromise, parse, ParseResult } from './wasm'
import { enginelessExecutor } from 'lib/testHelpers'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { Program } from '../wasm-lib/kcl/bindings/Program'
@ -20,3 +20,12 @@ it('can execute parsed AST', async () => {
expect(err(execState)).toEqual(false)
expect(execState.memory.get('x')?.value).toEqual(1)
})
it('formats numbers with units', () => {
expect(formatNumber(1, 'None')).toEqual('1')
expect(formatNumber(1, 'Count')).toEqual('1_')
expect(formatNumber(1, 'Mm')).toEqual('1mm')
expect(formatNumber(1, 'Inch')).toEqual('1in')
expect(formatNumber(0.5, 'Mm')).toEqual('0.5mm')
expect(formatNumber(-0.5, 'Mm')).toEqual('-0.5mm')
})

View File

@ -2,6 +2,7 @@ import {
init,
parse_wasm,
recast_wasm,
format_number,
execute,
kcl_lint,
modify_ast_for_sketch_wasm,
@ -54,6 +55,7 @@ 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 'lang/queryAstNodePathUtils'
import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
@ -90,6 +92,7 @@ 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 } from 'wasm-lib/kcl/bindings/SourceRange'
export type { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
export type SyntaxType =
| 'Program'
@ -566,9 +569,19 @@ export function sketchFromKclValue(
return result
}
/**
* Execute a KCL program.
* @param node The AST of the program to execute.
* @param path The full path of the file being executed. Use `null` for
* expressions that don't have a file, like expressions in the command bar.
* @param programMemoryOverride If this is not `null`, this will be used as the
* initial program memory, and the execution will be engineless (AKA mock
* execution).
*/
export const executor = async (
node: Node<Program>,
engineCommandManager: EngineCommandManager,
path?: string,
programMemoryOverride: ProgramMemory | Error | null = null
): Promise<ExecState> => {
if (programMemoryOverride !== null && err(programMemoryOverride))
@ -590,6 +603,7 @@ export const executor = async (
}
const execOutcome: RustExecOutcome = await execute(
JSON.stringify(node),
path,
JSON.stringify(programMemoryOverride?.toRaw() || null),
JSON.stringify({ settings: jsAppSettings }),
engineCommandManager,
@ -627,6 +641,13 @@ export const recast = (ast: Program): string | Error => {
return recast_wasm(JSON.stringify(ast))
}
/**
* Format a number with suffix as KCL.
*/
export function formatNumber(value: number, suffix: NumericSuffix): string {
return format_number(value, JSON.stringify(suffix))
}
export const makeDefaultPlanes = async (
engineCommandManager: EngineCommandManager
): Promise<DefaultPlanes> => {

View File

@ -41,12 +41,11 @@ export const projectsCommandBarConfig: StateMachineCommandSetConfig<
name: {
inputType: 'options',
required: true,
options: [],
optionsFromContext: (context) =>
context.projects.map((p) => ({
name: p.name!,
value: p.name!,
})),
options: (_, context) =>
context?.projects.map((p) => ({
name: p.name,
value: p.name,
})) || [],
},
},
},

View File

@ -0,0 +1,92 @@
import { expect } from 'vitest'
import {
recast,
assertParse,
topLevelRange,
VariableDeclaration,
initPromise,
} from 'lang/wasm'
import { updateCenterRectangleSketch } from './rectangleTool'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { getNodeFromPath } from 'lang/queryAst'
import { findUniqueName } from 'lang/modifyAst'
import { err, trap } from './trap'
beforeAll(async () => {
await initPromise
})
describe('library rectangleTool helper functions', () => {
describe('updateCenterRectangleSketch', () => {
// regression test for https://github.com/KittyCAD/modeling-app/issues/5157
test('should update AST and source code', async () => {
// Base source code that will be edited in place
const sourceCode = `sketch001 = startSketchOn('XZ')
|> startProfileAt([120.37, 162.76], %)
|> angledLine([0, 0], %, $rectangleSegmentA001)
|> angledLine([segAng(rectangleSegmentA001) + 90, 0], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
`
// Create ast
const _ast = assertParse(sourceCode)
let ast = structuredClone(_ast)
// Find some nodes and paths to reference
const sketchSnippet = `startProfileAt([120.37, 162.76], %)`
const sketchRange = topLevelRange(
sourceCode.indexOf(sketchSnippet),
sourceCode.indexOf(sketchSnippet) + sketchSnippet.length
)
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
const _node = getNodeFromPath<VariableDeclaration>(
ast,
sketchPathToNode || [],
'VariableDeclaration'
)
if (trap(_node)) return
const sketchInit = _node.node?.declaration.init
// Hard code inputs that a user would have taken with their mouse
const x = 40
const y = 60
const rectangleOrigin = [120, 180]
const tags: [string, string, string] = [
'rectangleSegmentA001',
'rectangleSegmentB001',
'rectangleSegmentC001',
]
// Update the ast
if (sketchInit.type === 'PipeExpression') {
updateCenterRectangleSketch(
sketchInit,
x,
y,
tags[0],
rectangleOrigin[0],
rectangleOrigin[1]
)
}
// ast is edited in place from the updateCenterRectangleSketch
const expectedSourceCode = `sketch001 = startSketchOn('XZ')
|> startProfileAt([80, 120], %)
|> angledLine([0, 80], %, $rectangleSegmentA001)
|> angledLine([segAng(rectangleSegmentA001) + 90, 120], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
`
const recasted = recast(ast)
expect(recasted).toEqual(expectedSourceCode)
})
})
})

View File

@ -8,13 +8,19 @@ import {
createTagDeclarator,
createUnaryExpression,
} from 'lang/modifyAst'
import { ArrayExpression, CallExpression, PipeExpression } from 'lang/wasm'
import {
ArrayExpression,
CallExpression,
PipeExpression,
recast,
} from 'lang/wasm'
import { roundOff } from 'lib/utils'
import {
isCallExpression,
isArrayExpression,
isLiteral,
isBinaryExpression,
isLiteralValueNumber,
} from 'lang/util'
/**
@ -140,10 +146,12 @@ export function updateCenterRectangleSketch(
if (isArrayExpression(arrayExpression)) {
const literal = arrayExpression.elements[0]
if (isLiteral(literal)) {
callExpression.arguments[0] = createArrayExpression([
createLiteral(literal.value),
createLiteral(Math.abs(twoX)),
])
if (isLiteralValueNumber(literal.value)) {
callExpression.arguments[0] = createArrayExpression([
createLiteral(literal.value),
createLiteral(Math.abs(twoX)),
])
}
}
}
}

View File

@ -80,7 +80,8 @@ class MockEngineCommandManager {
export async function enginelessExecutor(
ast: Node<Program>,
pmo: ProgramMemory | Error = ProgramMemory.empty()
pmo: ProgramMemory | Error = ProgramMemory.empty(),
path?: string
): Promise<ExecState> {
if (pmo !== null && err(pmo)) return Promise.reject(pmo)
@ -90,7 +91,7 @@ export async function enginelessExecutor(
}) as any as EngineCommandManager
// eslint-disable-next-line @typescript-eslint/no-floating-promises
mockEngineCommandManager.startNewSession()
const execState = await executor(ast, mockEngineCommandManager, pmo)
const execState = await executor(ast, mockEngineCommandManager, path, pmo)
await mockEngineCommandManager.waitForAllCommands()
return execState
}

View File

@ -10,6 +10,7 @@
import {
parse_wasm as ParseWasm,
recast_wasm as RecastWasm,
format_number as FormatNumber,
execute as Execute,
kcl_lint as KclLint,
modify_ast_for_sketch_wasm as ModifyAstForSketch,
@ -51,6 +52,9 @@ export const parse_wasm: typeof ParseWasm = (...args) => {
export const recast_wasm: typeof RecastWasm = (...args) => {
return getModule().recast_wasm(...args)
}
export const format_number: typeof FormatNumber = (...args) => {
return getModule().format_number(...args)
}
export const execute: typeof Execute = (...args) => {
return getModule().execute(...args)
}

View File

@ -1710,7 +1710,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.30"
version = "0.2.32"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1844,9 +1844,9 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds"
version = "0.2.92"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5fc91d0cdacd1c2ba906f564e58ce07c70a5c471f19b0f4c0b67e754077bfdc"
checksum = "67a993046541732e3c3ddd8a0364b55b7b138a9258beff353b6e7a043a41dce3"
dependencies = [
"anyhow",
"chrono",

View File

@ -76,7 +76,7 @@ members = [
[workspace.dependencies]
http = "1"
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.92", features = [
kittycad-modeling-cmds = { version = "0.2.93", features = [
"ts-rs",
"websocket",
] }

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.30"
version = "0.2.32"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -197,10 +197,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
source_range: SourceRange,
cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> {
let execution_kind = self.execution_kind();
if execution_kind.is_isolated() {
return Err(KclError::Semantic(KclErrorDetails { message: "Cannot send modeling commands while importing. Wrap your code in a function if you want to import the file.".to_owned(), source_ranges: vec![source_range] }));
}
let req = WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
cmd: cmd.clone(),
cmd_id: id.into(),

View File

@ -462,12 +462,14 @@ impl ArtifactGraph {
output.push_str("mindmap\n");
output.push_str(" root\n");
let mut ids_seen: fnv::FnvHashSet<ArtifactId> = Default::default();
for (_, artifact) in &self.map {
// Only the planes are roots.
let Artifact::Plane(_) = artifact else {
continue;
};
self.mind_map_artifact(&mut output, artifact, " ")?;
self.mind_map_artifact(&mut output, &mut ids_seen, artifact, " ")?;
}
output.push_str("```\n");
@ -475,9 +477,16 @@ impl ArtifactGraph {
Ok(output)
}
fn mind_map_artifact<W: Write>(&self, output: &mut W, artifact: &Artifact, prefix: &str) -> std::fmt::Result {
fn mind_map_artifact<W: Write>(
&self,
output: &mut W,
ids_seen: &mut fnv::FnvHashSet<ArtifactId>,
artifact: &Artifact,
prefix: &str,
) -> std::fmt::Result {
match artifact {
Artifact::Plane(_plane) => {
ids_seen.clear();
writeln!(output, "{prefix}Plane")?;
}
Artifact::Path(_path) => {
@ -515,11 +524,17 @@ impl ArtifactGraph {
}
}
if ids_seen.contains(&artifact.id()) {
return Ok(());
}
ids_seen.insert(artifact.id());
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))?;
self.mind_map_artifact(output, ids_seen, child_artifact, &format!("{} ", prefix))?;
}
Ok(())

View File

@ -0,0 +1,294 @@
use std::{ffi::OsStr, path::Path, str::FromStr};
use anyhow::Result;
use kcmc::{
coord::{Axis, AxisDirectionPair, Direction, System},
each_cmd as mcmd,
format::InputFormat,
ok_response::OkModelingCmdResponse,
shared::FileImportFormat,
units::UnitLength,
websocket::OkWebSocketResponseData,
ImportFile, ModelingCmd,
};
use kittycad_modeling_cmds as kcmc;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{ExecState, ImportedGeometry},
fs::FileSystem,
source_range::SourceRange,
};
use super::ExecutorContext;
// Zoo co-ordinate system.
//
// * Forward: -Y
// * Up: +Z
// * Handedness: Right
pub const ZOO_COORD_SYSTEM: System = System {
forward: AxisDirectionPair {
axis: Axis::Y,
direction: Direction::Negative,
},
up: AxisDirectionPair {
axis: Axis::Z,
direction: Direction::Positive,
},
};
pub async fn import_foreign(
file_path: &Path,
format: Option<InputFormat>,
exec_state: &mut ExecState,
ctxt: &ExecutorContext,
source_range: SourceRange,
) -> Result<PreImportedGeometry, KclError> {
// Make sure the file exists.
if !ctxt.fs.exists(file_path, source_range).await? {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("File `{}` does not exist.", file_path.display()),
source_ranges: vec![source_range],
}));
}
let ext_format = get_import_format_from_extension(file_path.extension().ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("No file extension found for `{}`", file_path.display()),
source_ranges: vec![source_range],
})
})?)
.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
// Get the format type from the extension of the file.
let format = if let Some(format) = format {
// Validate the given format with the extension format.
validate_extension_format(ext_format, format.clone()).map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
format
} else {
ext_format
};
// Get the file contents for each file path.
let file_contents = ctxt.fs.read(file_path, source_range).await.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
// We want the file_path to be without the parent.
let file_name = std::path::Path::new(&file_path)
.file_name()
.map(|p| p.to_string_lossy().to_string())
.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Could not get the file name from the path `{}`", file_path.display()),
source_ranges: vec![source_range],
})
})?;
let mut import_files = vec![kcmc::ImportFile {
path: file_name.to_string(),
data: file_contents.clone(),
}];
// In the case of a gltf importing a bin file we need to handle that! and figure out where the
// file is relative to our current file.
if let InputFormat::Gltf(..) = format {
// Check if the file is a binary gltf file, in that case we don't need to import the bin
// file.
if !file_contents.starts_with(b"glTF") {
let json = gltf_json::Root::from_slice(&file_contents).map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
// Read the gltf file and check if there is a bin file.
for buffer in json.buffers.iter() {
if let Some(uri) = &buffer.uri {
if !uri.starts_with("data:") {
// We want this path relative to the file_path given.
let bin_path = std::path::Path::new(&file_path)
.parent()
.map(|p| p.join(uri))
.map(|p| p.to_string_lossy().to_string())
.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!(
"Could not get the parent path of the file `{}`",
file_path.display()
),
source_ranges: vec![source_range],
})
})?;
let bin_contents = ctxt.fs.read(&bin_path, source_range).await.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
import_files.push(ImportFile {
path: uri.to_string(),
data: bin_contents,
});
}
}
}
}
}
Ok(PreImportedGeometry {
id: exec_state.next_uuid(),
source_range,
command: mcmd::ImportFiles {
files: import_files.clone(),
format,
},
})
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PreImportedGeometry {
id: Uuid,
command: mcmd::ImportFiles,
source_range: SourceRange,
}
pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) -> Result<ImportedGeometry, KclError> {
if ctxt.is_mock() {
return Ok(ImportedGeometry {
id: pre.id,
value: pre.command.files.iter().map(|f| f.path.to_string()).collect(),
meta: vec![pre.source_range.into()],
});
}
let resp = ctxt
.engine
.send_modeling_cmd(pre.id, pre.source_range, &ModelingCmd::from(pre.command.clone()))
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::ImportFiles(imported_files),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("ImportFiles response was not as expected: {:?}", resp),
source_ranges: vec![pre.source_range],
}));
};
Ok(ImportedGeometry {
id: imported_files.object_id,
value: pre.command.files.iter().map(|f| f.path.to_string()).collect(),
meta: vec![pre.source_range.into()],
})
}
/// Get the source format from the extension.
fn get_import_format_from_extension(ext: &OsStr) -> Result<InputFormat> {
let ext = ext
.to_str()
.ok_or_else(|| anyhow::anyhow!("Invalid file extension: `{ext:?}`"))?;
let format = match FileImportFormat::from_str(ext) {
Ok(format) => format,
Err(_) => {
if ext == "stp" {
FileImportFormat::Step
} else if ext == "glb" {
FileImportFormat::Gltf
} else {
anyhow::bail!("unknown source format for file extension: {ext}. Try setting the `--src-format` flag explicitly or use a valid format.")
}
}
};
// Make the default units millimeters.
let ul = UnitLength::Millimeters;
// Zoo co-ordinate system.
//
// * Forward: -Y
// * Up: +Z
// * Handedness: Right
match format {
FileImportFormat::Step => Ok(InputFormat::Step(kcmc::format::step::import::Options {
split_closed_faces: false,
})),
FileImportFormat::Stl => Ok(InputFormat::Stl(kcmc::format::stl::import::Options {
coords: ZOO_COORD_SYSTEM,
units: ul,
})),
FileImportFormat::Obj => Ok(InputFormat::Obj(kcmc::format::obj::import::Options {
coords: ZOO_COORD_SYSTEM,
units: ul,
})),
FileImportFormat::Gltf => Ok(InputFormat::Gltf(kcmc::format::gltf::import::Options {})),
FileImportFormat::Ply => Ok(InputFormat::Ply(kcmc::format::ply::import::Options {
coords: ZOO_COORD_SYSTEM,
units: ul,
})),
FileImportFormat::Fbx => Ok(InputFormat::Fbx(kcmc::format::fbx::import::Options {})),
FileImportFormat::Sldprt => Ok(InputFormat::Sldprt(kcmc::format::sldprt::import::Options {
split_closed_faces: false,
})),
}
}
fn validate_extension_format(ext: InputFormat, given: InputFormat) -> Result<()> {
if let InputFormat::Stl(_) = ext {
if let InputFormat::Stl(_) = given {
return Ok(());
}
}
if let InputFormat::Obj(_) = ext {
if let InputFormat::Obj(_) = given {
return Ok(());
}
}
if let InputFormat::Ply(_) = ext {
if let InputFormat::Ply(_) = given {
return Ok(());
}
}
if ext == given {
return Ok(());
}
anyhow::bail!(
"The given format does not match the file extension. Expected: `{}`, Given: `{}`",
get_name_of_format(ext),
get_name_of_format(given)
)
}
fn get_name_of_format(type_: InputFormat) -> &'static str {
match type_ {
InputFormat::Fbx(_) => "fbx",
InputFormat::Gltf(_) => "gltf",
InputFormat::Obj(_) => "obj",
InputFormat::Ply(_) => "ply",
InputFormat::Sldprt(_) => "sldprt",
InputFormat::Step(_) => "step",
InputFormat::Stl(_) => "stl",
}
}

View File

@ -23,6 +23,7 @@ type Point2D = kcmc::shared::Point2d<f64>;
type Point3D = kcmc::shared::Point3d<f64>;
pub use function_param::FunctionParam;
pub(crate) use import::{import_foreign, send_to_engine as send_import_to_engine, ZOO_COORD_SYSTEM};
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
use uuid::Uuid;
@ -32,6 +33,7 @@ pub(crate) mod cache;
mod cad_op;
mod exec_ast;
mod function_param;
mod import;
mod kcl_value;
use crate::{
@ -40,7 +42,7 @@ use crate::{
execution::cache::{CacheInformation, CacheResult},
fs::{FileManager, FileSystem},
parsing::ast::types::{
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, NonCodeValue,
BodyItem, Expr, FunctionExpression, ImportPath, ImportSelector, ItemVisibility, Node, NodeRef, NonCodeValue,
Program as AstProgram, TagDeclarator, TagNode,
},
settings::types::UnitLength,
@ -129,7 +131,7 @@ pub struct ExecOutcome {
impl ExecState {
pub fn new(exec_settings: &ExecutorSettings) -> Self {
ExecState {
global: GlobalState::new(),
global: GlobalState::new(exec_settings),
mod_local: ModuleState::new(exec_settings),
}
}
@ -140,7 +142,7 @@ impl ExecState {
// This is for the front end to keep track of the ids.
id_generator.next_id = 0;
let mut global = GlobalState::new();
let mut global = GlobalState::new(exec_settings);
global.id_generator = id_generator;
*self = ExecState {
@ -181,34 +183,15 @@ impl ExecState {
self.global.artifacts.insert(id, artifact);
}
async fn add_module(
&mut self,
path: std::path::PathBuf,
ctxt: &ExecutorContext,
source_range: SourceRange,
) -> Result<ModuleId, KclError> {
// Need to avoid borrowing self in the closure.
let new_module_id = ModuleId::from_usize(self.global.path_to_source_id.len());
let mut is_new = false;
let id = *self.global.path_to_source_id.entry(path.clone()).or_insert_with(|| {
is_new = true;
new_module_id
});
fn add_module(&mut self, id: ModuleId, path: std::path::PathBuf, repr: ModuleRepr) -> ModuleId {
debug_assert!(!self.global.path_to_source_id.contains_key(&path));
if is_new {
let source = ctxt.fs.read_to_string(&path, source_range).await?;
// TODO handle parsing errors properly
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?;
self.global.path_to_source_id.insert(path.clone(), id);
let module_info = ModuleInfo {
id,
path,
parsed: Some(parsed),
};
self.global.module_infos.insert(id, module_info);
}
let module_info = ModuleInfo { id, repr, path };
self.global.module_infos.insert(id, module_info);
Ok(id)
id
}
pub fn length_unit(&self) -> UnitLen {
@ -221,7 +204,7 @@ impl ExecState {
}
impl GlobalState {
fn new() -> Self {
fn new(settings: &ExecutorSettings) -> Self {
let mut global = GlobalState {
id_generator: Default::default(),
path_to_source_id: Default::default(),
@ -232,15 +215,14 @@ impl GlobalState {
artifact_graph: Default::default(),
};
// TODO(#4434): Use the top-level file's path.
let root_path = PathBuf::new();
let root_id = ModuleId::default();
let root_path = settings.current_file.clone().unwrap_or_default();
global.module_infos.insert(
root_id,
ModuleInfo {
id: root_id,
path: root_path.clone(),
parsed: None,
repr: ModuleRepr::Root,
},
);
global.path_to_source_id.insert(root_path, root_id);
@ -1253,7 +1235,15 @@ pub struct ModuleInfo {
id: ModuleId,
/// Absolute path of the module's source file.
path: std::path::PathBuf,
parsed: Option<Node<AstProgram>>,
repr: ModuleRepr,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum ModuleRepr {
Root,
Kcl(Node<AstProgram>),
Foreign(import::PreImportedGeometry),
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
@ -1761,6 +1751,9 @@ pub struct ExecutorSettings {
/// The directory of the current project. This is used for resolving import
/// paths. If None is given, the current working directory is used.
pub project_directory: Option<PathBuf>,
/// This is the path to the current file being executed.
/// We use this for preventing cyclic imports.
pub current_file: Option<PathBuf>,
}
impl Default for ExecutorSettings {
@ -1772,6 +1765,7 @@ impl Default for ExecutorSettings {
show_grid: false,
replay: None,
project_directory: None,
current_file: None,
}
}
}
@ -1785,6 +1779,7 @@ impl From<crate::settings::types::Configuration> for ExecutorSettings {
show_grid: config.settings.modeling.show_scale_grid,
replay: None,
project_directory: None,
current_file: None,
}
}
}
@ -1798,6 +1793,7 @@ impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSet
show_grid: config.settings.modeling.show_scale_grid,
replay: None,
project_directory: None,
current_file: None,
}
}
}
@ -1811,6 +1807,25 @@ impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
show_grid: modeling.show_scale_grid,
replay: None,
project_directory: None,
current_file: None,
}
}
}
impl ExecutorSettings {
/// Add the current file path to the executor settings.
pub fn with_current_file(&mut self, current_file: PathBuf) {
// We want the parent directory of the file.
if current_file.extension() == Some(std::ffi::OsStr::new("kcl")) {
self.current_file = Some(current_file.clone());
// Get the parent directory.
if let Some(parent) = current_file.parent() {
self.project_directory = Some(parent.to_path_buf());
} else {
self.project_directory = Some(std::path::PathBuf::from(""));
}
} else {
self.project_directory = Some(current_file.clone());
}
}
}
@ -2028,6 +2043,7 @@ impl ExecutorContext {
show_grid: false,
replay: None,
project_directory: None,
current_file: None,
},
None,
engine_addr,
@ -2511,33 +2527,68 @@ impl ExecutorContext {
async fn open_module(
&self,
path: &str,
path: &ImportPath,
exec_state: &mut ExecState,
source_range: SourceRange,
) -> Result<ModuleId, KclError> {
let resolved_path = if let Some(project_dir) = &self.settings.project_directory {
project_dir.join(path)
} else {
std::path::PathBuf::from(&path)
};
match path {
ImportPath::Kcl { filename } => {
let resolved_path = if let Some(project_dir) = &self.settings.project_directory {
project_dir.join(filename)
} else {
std::path::PathBuf::from(filename)
};
if exec_state.mod_local.import_stack.contains(&resolved_path) {
return Err(KclError::ImportCycle(KclErrorDetails {
message: format!(
"circular import of modules is not allowed: {} -> {}",
exec_state
.mod_local
.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
.collect::<Vec<_>>()
.join(" -> "),
resolved_path.to_string_lossy()
),
if exec_state.mod_local.import_stack.contains(&resolved_path) {
return Err(KclError::ImportCycle(KclErrorDetails {
message: format!(
"circular import of modules is not allowed: {} -> {}",
exec_state
.mod_local
.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
.collect::<Vec<_>>()
.join(" -> "),
resolved_path.to_string_lossy()
),
source_ranges: vec![source_range],
}));
}
if let Some(id) = exec_state.global.path_to_source_id.get(&resolved_path) {
return Ok(*id);
}
let source = self.fs.read_to_string(&resolved_path, source_range).await?;
let id = ModuleId::from_usize(exec_state.global.path_to_source_id.len());
// TODO handle parsing errors properly
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?;
let repr = ModuleRepr::Kcl(parsed);
Ok(exec_state.add_module(id, resolved_path, repr))
}
ImportPath::Foreign { path } => {
let resolved_path = if let Some(project_dir) = &self.settings.project_directory {
project_dir.join(path)
} else {
std::path::PathBuf::from(path)
};
if let Some(id) = exec_state.global.path_to_source_id.get(&resolved_path) {
return Ok(*id);
}
let geom = import::import_foreign(&resolved_path, None, exec_state, self, source_range).await?;
let repr = ModuleRepr::Foreign(geom);
let id = ModuleId::from_usize(exec_state.global.path_to_source_id.len());
Ok(exec_state.add_module(id, resolved_path, repr))
}
i => Err(KclError::Semantic(KclErrorDetails {
message: format!("Unsupported import: `{i}`"),
source_ranges: vec![source_range],
}));
})),
}
exec_state.add_module(resolved_path.clone(), self, source_range).await
}
async fn exec_module(
@ -2551,43 +2602,64 @@ impl ExecutorContext {
// TODO It sucks that we have to clone the whole module AST here
let info = exec_state.global.module_infos[&module_id].clone();
let mut local_state = ModuleState {
import_stack: exec_state.mod_local.import_stack.clone(),
..ModuleState::new(&self.settings)
};
local_state.import_stack.push(info.path.clone());
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
let original_execution = self.engine.replace_execution_kind(exec_kind);
match &info.repr {
ModuleRepr::Root => Err(KclError::ImportCycle(KclErrorDetails {
message: format!(
"circular import of modules is not allowed: {} -> {}",
exec_state
.mod_local
.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
.collect::<Vec<_>>()
.join(" -> "),
info.path.display()
),
source_ranges: vec![source_range],
})),
ModuleRepr::Kcl(program) => {
let mut local_state = ModuleState {
import_stack: exec_state.mod_local.import_stack.clone(),
..ModuleState::new(&self.settings)
};
local_state.import_stack.push(info.path.clone());
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
let original_execution = self.engine.replace_execution_kind(exec_kind);
// The unwrap here is safe since we only elide the AST for the top module.
let result = self
.inner_execute(&info.parsed.unwrap(), exec_state, crate::execution::BodyType::Root)
.await;
let result = self
.inner_execute(program, exec_state, crate::execution::BodyType::Root)
.await;
let new_units = exec_state.length_unit();
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
if new_units != old_units {
self.engine.set_units(old_units.into(), Default::default()).await?;
}
self.engine.replace_execution_kind(original_execution);
let new_units = exec_state.length_unit();
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
if new_units != old_units {
self.engine.set_units(old_units.into(), Default::default()).await?;
}
self.engine.replace_execution_kind(original_execution);
let result = result.map_err(|err| {
if let KclError::ImportCycle(_) = err {
// It was an import cycle. Keep the original message.
err.override_source_ranges(vec![source_range])
} else {
KclError::Semantic(KclErrorDetails {
message: format!(
"Error loading imported file. Open it to view more details. {}: {}",
info.path.display(),
err.message()
),
source_ranges: vec![source_range],
})
let result = result.map_err(|err| {
if let KclError::ImportCycle(_) = err {
// It was an import cycle. Keep the original message.
err.override_source_ranges(vec![source_range])
} else {
KclError::Semantic(KclErrorDetails {
message: format!(
"Error loading imported file. Open it to view more details. {}: {}",
info.path.display(),
err.message()
),
source_ranges: vec![source_range],
})
}
})?;
Ok((result, local_state.memory, local_state.module_exports))
}
})?;
Ok((result, local_state.memory, local_state.module_exports))
ModuleRepr::Foreign(geom) => {
let geom = send_import_to_engine(geom.clone(), self).await?;
Ok((Some(KclValue::ImportedGeometry(geom)), ProgramMemory::new(), Vec::new()))
}
}
}
#[async_recursion]
@ -2608,15 +2680,20 @@ impl ExecutorContext {
let (result, _, _) = self
.exec_module(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
.await?;
result.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!(
"Evaluating module `{}` as part of an assembly did not produce a result",
identifier.name
),
source_ranges: vec![metadata.source_range, meta[0].source_range],
})
})?
result.unwrap_or_else(|| {
// The module didn't have a return value. Currently,
// the only way to have a return value is with the final
// statement being an expression statement.
//
// TODO: Make a warning when we support them in the
// execution phase.
let mut new_meta = vec![metadata.to_owned()];
new_meta.extend(meta);
KclValue::KclNone {
value: Default::default(),
meta: new_meta,
}
})
} else {
value
}
@ -3421,6 +3498,16 @@ const inInches = 1.0 * inch()"#;
assert_eq!(1.0, mem_get_json(exec_state.memory(), "inInches").as_f64().unwrap());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_unit_overriden_in() {
let ast = r#"@settings(defaultLengthUnit = in)
const inMm = 25.4 * mm()
const inInches = 2.0 * inch()"#;
let (_, _, exec_state) = parse_execute(ast).await.unwrap();
assert_eq!(1.0, mem_get_json(exec_state.memory(), "inMm").as_f64().unwrap().round());
assert_eq!(2.0, mem_get_json(exec_state.memory(), "inInches").as_f64().unwrap());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_zero_param_fn() {
let ast = r#"const sigmaAllow = 35000 // psi

View File

@ -120,6 +120,11 @@ pub mod std_utils {
pub use crate::std::utils::{get_tangential_arc_to_info, is_points_ccw_wasm, TangentialArcInfoInput};
}
pub mod pretty {
pub use crate::parsing::token::NumericSuffix;
pub use crate::unparser::format_number;
}
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]

View File

@ -66,7 +66,8 @@ impl ImportStatement {
}
}
hasher.update(slf.visibility.digestable_id());
let path = slf.path.as_bytes();
let path = slf.path.to_string();
let path = path.as_bytes();
hasher.update(path.len().to_ne_bytes());
hasher.update(path);
});

View File

@ -1256,6 +1256,32 @@ impl ImportSelector {
ImportSelector::None { alias: Some(alias) } => alias.rename(old_name, new_name),
}
}
pub fn exposes_imported_name(&self) -> bool {
matches!(self, ImportSelector::None { alias: None })
}
pub fn imports_items(&self) -> bool {
!matches!(self, ImportSelector::None { .. })
}
}
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum ImportPath {
Kcl { filename: String },
Foreign { path: String },
Std,
}
impl fmt::Display for ImportPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ImportPath::Kcl { filename: s } | ImportPath::Foreign { path: s } => write!(f, "{s}"),
ImportPath::Std => write!(f, "std"),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
@ -1263,7 +1289,7 @@ impl ImportSelector {
#[serde(tag = "type")]
pub struct ImportStatement {
pub selector: ImportSelector,
pub path: String,
pub path: ImportPath,
#[serde(default, skip_serializing_if = "ItemVisibility::is_default")]
pub visibility: ItemVisibility,
@ -1312,12 +1338,15 @@ impl ImportStatement {
return Some(alias.name.clone());
}
let mut parts = self.path.split('.');
let mut parts = match &self.path {
ImportPath::Kcl { filename: s } | ImportPath::Foreign { path: s } => s.split('.'),
_ => return None,
};
let name = parts.next()?;
let ext = parts.next()?;
let _ext = parts.next()?;
let rest = parts.next();
if rest.is_some() || ext != "kcl" {
if rest.is_some() {
return None;
}

View File

@ -12,7 +12,10 @@ use winnow::{
token::{any, one_of, take_till},
};
use super::{ast::types::LabelledExpression, token::NumericSuffix};
use super::{
ast::types::{ImportPath, LabelledExpression},
token::NumericSuffix,
};
use crate::{
docs::StdLibFn,
errors::{CompilationError, Severity, Tag},
@ -1545,33 +1548,11 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
};
let mut end: usize = path.end;
let path_string = match path.inner.value {
LiteralValue::String(s) => s,
_ => unreachable!(),
};
if path_string.is_empty() {
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::new(path.start, path.end, path.module_id),
"import path cannot be empty",
)
.into(),
));
}
if path_string
.chars()
.any(|c| !c.is_ascii_alphanumeric() && c != '_' && c != '-' && c != '.')
{
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::new(path.start, path.end, path.module_id),
"import path may only contain alphanumeric characters, underscore, hyphen, and period. Files in other directories are not yet supported.",
)
.into(),
));
}
if let ImportSelector::None { alias: ref mut a } = selector {
if let ImportSelector::None {
alias: ref mut selector_alias,
} = selector
{
if let Some(alias) = opt(preceded(
(whitespace, import_as_keyword, whitespace),
identifier.context(expected("an identifier to alias the import")),
@ -1579,35 +1560,40 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
.parse_next(i)?
{
end = alias.end;
*a = Some(alias);
*selector_alias = Some(alias);
}
ParseContext::warn(CompilationError::err(
SourceRange::new(start, path.end, path.module_id),
"Importing a whole module is experimental, likely to be buggy, and likely to change",
));
}
if a.is_none()
&& (!path_string.ends_with(".kcl")
|| path_string.starts_with("_")
|| path_string.contains('-')
|| path_string[0..path_string.len() - 4].contains('.'))
{
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::new(path.start, path.end, path.module_id),
"import path is not a valid identifier and must be aliased.".to_owned(),
)
.into(),
));
}
let path_string = match path.inner.value {
LiteralValue::String(s) => s,
_ => unreachable!(),
};
let path = validate_path_string(
path_string,
selector.exposes_imported_name(),
SourceRange::new(path.start, path.end, path.module_id),
)?;
if matches!(path, ImportPath::Foreign { .. }) && selector.imports_items() {
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::new(start, end, module_id),
"individual items can only be imported from KCL files",
)
.into(),
));
}
Ok(Node::boxed(
ImportStatement {
selector,
visibility,
path: path_string,
path,
digest: None,
},
start,
@ -1616,6 +1602,72 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
))
}
const FOREIGN_IMPORT_EXTENSIONS: [&str; 8] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "step", "stl"];
/// Validates the path string in an `import` statement.
///
/// `var_name` is `true` if the path will be used as a variable name.
fn validate_path_string(path_string: String, var_name: bool, path_range: SourceRange) -> PResult<ImportPath> {
if path_string.is_empty() {
return Err(ErrMode::Cut(
CompilationError::fatal(path_range, "import path cannot be empty").into(),
));
}
if var_name
&& (path_string.starts_with("_")
|| path_string.contains('-')
|| path_string.chars().filter(|c| *c == '.').count() > 1)
{
return Err(ErrMode::Cut(
CompilationError::fatal(path_range, "import path is not a valid identifier and must be aliased.").into(),
));
}
let path = if path_string.ends_with(".kcl") {
if path_string
.chars()
.any(|c| !c.is_ascii_alphanumeric() && c != '_' && c != '-' && c != '.')
{
return Err(ErrMode::Cut(
CompilationError::fatal(
path_range,
"import path may only contain alphanumeric characters, underscore, hyphen, and period. KCL files in other directories are not yet supported.",
)
.into(),
));
}
ImportPath::Kcl { filename: path_string }
} else if path_string.starts_with("std") {
ParseContext::warn(CompilationError::err(
path_range,
"explicit imports from the standard library are experimental, likely to be buggy, and likely to change.",
));
ImportPath::Std
} else if path_string.contains('.') {
let extn = &path_string[path_string.rfind('.').unwrap() + 1..];
if !FOREIGN_IMPORT_EXTENSIONS.contains(&extn) {
ParseContext::warn(CompilationError::err(
path_range,
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")),
))
}
ImportPath::Foreign { path: path_string }
} else {
return Err(ErrMode::Cut(
CompilationError::fatal(
path_range,
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")),
)
.into(),
));
};
Ok(path)
}
fn import_item(i: &mut TokenSlice) -> PResult<Node<ImportItem>> {
let name = nameable_identifier
.context(expected("an identifier to import"))
@ -3611,7 +3663,11 @@ mySk1 = startSketchAt([0, 0])"#;
fn assert_err(p: &str, msg: &str, src_expected: [usize; 2]) {
let result = crate::parsing::top_level_parse(p);
let err = result.unwrap_errs().next().unwrap();
assert_eq!(err.message, msg);
assert!(
err.message.starts_with(msg),
"Found `{}`, expected `{msg}`",
err.message
);
let src_actual = [err.source_range.start(), err.source_range.end()];
assert_eq!(
src_expected,
@ -3977,7 +4033,7 @@ e
fn bad_imports() {
assert_err(
r#"import cube from "../cube.kcl""#,
"import path may only contain alphanumeric characters, underscore, hyphen, and period. Files in other directories are not yet supported.",
"import path may only contain alphanumeric characters, underscore, hyphen, and period. KCL files in other directories are not yet supported.",
[17, 30],
);
assert_err(
@ -3985,17 +4041,21 @@ e
"as is not the 'from' keyword",
[9, 11],
);
assert_err(r#"import a from "dsfs" as b"#, "Unexpected token: as", [21, 23]);
assert_err(r#"import * from "dsfs" as b"#, "Unexpected token: as", [21, 23]);
assert_err(
r#"import a from "dsfs" as b"#,
"unsupported import path format",
[14, 20],
);
assert_err(
r#"import * from "dsfs" as b"#,
"unsupported import path format",
[14, 20],
);
assert_err(r#"import a from b"#, "invalid string literal", [14, 15]);
assert_err(r#"import * "dsfs""#, "\"dsfs\" is not the 'from' keyword", [9, 15]);
assert_err(r#"import from "dsfs""#, "\"dsfs\" is not the 'from' keyword", [12, 18]);
assert_err(r#"import "dsfs.kcl" as *"#, "Unexpected token: as", [18, 20]);
assert_err(
r#"import "dsfs""#,
"import path is not a valid identifier and must be aliased.",
[7, 13],
);
assert_err(r#"import "dsfs""#, "unsupported import path format", [7, 13]);
assert_err(
r#"import "foo.bar.kcl""#,
"import path is not a valid identifier and must be aliased.",
@ -4017,7 +4077,19 @@ e
fn warn_import() {
let some_program_string = r#"import "foo.kcl""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 1);
assert_eq!(errs.len(), 1, "{errs:#?}");
let some_program_string = r#"import "foo.obj""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 1, "{errs:#?}");
let some_program_string = r#"import "foo.sldprt""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 1, "{errs:#?}");
let some_program_string = r#"import "foo.bad""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 2, "{errs:#?}");
}
#[test]

View File

@ -62,14 +62,14 @@ impl FromStr for NumericSuffix {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"_" => Ok(NumericSuffix::Count),
"mm" => Ok(NumericSuffix::Mm),
"cm" => Ok(NumericSuffix::Cm),
"m" => Ok(NumericSuffix::M),
"mm" | "millimeters" => Ok(NumericSuffix::Mm),
"cm" | "centimeters" => Ok(NumericSuffix::Cm),
"m" | "meters" => Ok(NumericSuffix::M),
"inch" | "in" => Ok(NumericSuffix::Inch),
"ft" => Ok(NumericSuffix::Ft),
"yd" => Ok(NumericSuffix::Yd),
"deg" => Ok(NumericSuffix::Deg),
"rad" => Ok(NumericSuffix::Rad),
"ft" | "feet" => Ok(NumericSuffix::Ft),
"yd" | "yards" => Ok(NumericSuffix::Yd),
"deg" | "degrees" => Ok(NumericSuffix::Deg),
"rad" | "radians" => Ok(NumericSuffix::Rad),
_ => Err(CompilationError::err(SourceRange::default(), "invalid unit of measure")),
}
}

View File

@ -87,7 +87,7 @@ async fn execute(test_name: &str, render_to_png: bool) {
let exec_res = crate::test_server::execute_and_snapshot_ast(
ast.into(),
crate::settings::types::UnitLength::Mm,
Some(Path::new("tests").join(test_name)),
Some(Path::new("tests").join(test_name).join("input.kcl").to_owned()),
)
.await;
match exec_res {
@ -767,6 +767,27 @@ mod import_cycle1 {
super::execute(TEST_NAME, false).await
}
}
mod import_function_not_sketch {
const TEST_NAME: &str = "import_function_not_sketch";
/// 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 import_constant {
const TEST_NAME: &str = "import_constant";
@ -872,6 +893,27 @@ mod import_side_effect {
super::execute(TEST_NAME, false).await
}
}
mod import_foreign {
const TEST_NAME: &str = "import_foreign";
/// 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, false).await
}
}
mod array_elem_push_fail {
const TEST_NAME: &str = "array_elem_push_fail";

View File

@ -143,10 +143,8 @@ async fn inner_chamfer(
radius: LengthUnit(data.length),
tolerance: LengthUnit(DEFAULT_TOLERANCE), // We can let the user set this in the future.
cut_type: CutType::Chamfer,
// We pass in the command id as the face id.
// So the resulting face of the fillet will be the same.
// This is because that's how most other endpoints work.
face_id: Some(id),
// We make this a none so that we can remove it in the future.
face_id: None,
}),
)
.await?;

View File

@ -152,10 +152,8 @@ async fn inner_fillet(
radius: LengthUnit(data.radius),
tolerance: LengthUnit(data.tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
cut_type: CutType::Fillet,
// We pass in the command id as the face id.
// So the resulting face of the fillet will be the same.
// This is because that's how most other endpoints work.
face_id: Some(id),
// We make this a none so that we can remove it in the future.
face_id: None,
}),
)
.await?;

View File

@ -1,44 +1,16 @@
//! Standard library functions involved in importing files.
use std::str::FromStr;
use anyhow::Result;
use derive_docs::stdlib;
use kcmc::{
coord::{Axis, AxisDirectionPair, Direction, System},
each_cmd as mcmd,
format::InputFormat,
ok_response::OkModelingCmdResponse,
shared::FileImportFormat,
units::UnitLength,
websocket::OkWebSocketResponseData,
ImportFile, ModelingCmd,
};
use kcmc::{coord::System, format::InputFormat, units::UnitLength};
use kittycad_modeling_cmds as kcmc;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{ExecState, ImportedGeometry, KclValue},
fs::FileSystem,
execution::{import_foreign, send_import_to_engine, ExecState, ImportedGeometry, KclValue, ZOO_COORD_SYSTEM},
std::Args,
};
// Zoo co-ordinate system.
//
// * Forward: -Y
// * Up: +Z
// * Handedness: Right
const ZOO_COORD_SYSTEM: System = System {
forward: AxisDirectionPair {
axis: Axis::Y,
direction: Direction::Negative,
},
up: AxisDirectionPair {
axis: Axis::Z,
direction: Direction::Positive,
},
};
/// Import format specifier
#[derive(serde :: Serialize, serde :: Deserialize, PartialEq, Debug, Clone, schemars :: JsonSchema)]
#[cfg_attr(feature = "tabled", derive(tabled::Tabled))]
@ -135,6 +107,8 @@ pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// Import a CAD file.
///
/// **DEPRECATED** Prefer to use import statements.
///
/// For formats lacking unit data (such as STL, OBJ, or PLY files), the default
/// unit of measurement is millimeters. Alternatively you may specify the unit
/// by passing your desired measurement unit in the options parameter. When
@ -144,9 +118,6 @@ pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// Note: The import command currently only works when using the native
/// Modeling App.
///
/// For importing KCL functions using the `import` statement, see the docs on
/// [KCL modules](/docs/kcl/modules).
///
/// ```no_run
/// model = import("tests/inputs/cube.obj")
/// ```
@ -178,6 +149,7 @@ pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
#[stdlib {
name = "import",
feature_tree_operation = true,
deprecated = true,
tags = [],
}]
async fn inner_import(
@ -193,232 +165,17 @@ async fn inner_import(
}));
}
// Make sure the file exists.
if !args.ctx.fs.exists(&file_path, args.source_range).await? {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("File `{}` does not exist.", file_path),
source_ranges: vec![args.source_range],
}));
}
let ext_format = get_import_format_from_extension(file_path.split('.').last().ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("No file extension found for `{}`", file_path),
source_ranges: vec![args.source_range],
})
})?)
.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![args.source_range],
})
})?;
// Get the format type from the extension of the file.
let format = if let Some(options) = options {
// Validate the given format with the extension format.
let format: InputFormat = options.into();
validate_extension_format(ext_format, format.clone()).map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![args.source_range],
})
})?;
format
} else {
ext_format
};
// Get the file contents for each file path.
let file_contents = args.ctx.fs.read(&file_path, args.source_range).await.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![args.source_range],
})
})?;
// We want the file_path to be without the parent.
let file_name = std::path::Path::new(&file_path)
.file_name()
.map(|p| p.to_string_lossy().to_string())
.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Could not get the file name from the path `{}`", file_path),
source_ranges: vec![args.source_range],
})
})?;
let mut import_files = vec![kcmc::ImportFile {
path: file_name.to_string(),
data: file_contents.clone(),
}];
// In the case of a gltf importing a bin file we need to handle that! and figure out where the
// file is relative to our current file.
if let InputFormat::Gltf(..) = format {
// Check if the file is a binary gltf file, in that case we don't need to import the bin
// file.
if !file_contents.starts_with(b"glTF") {
let json = gltf_json::Root::from_slice(&file_contents).map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![args.source_range],
})
})?;
// Read the gltf file and check if there is a bin file.
for buffer in json.buffers.iter() {
if let Some(uri) = &buffer.uri {
if !uri.starts_with("data:") {
// We want this path relative to the file_path given.
let bin_path = std::path::Path::new(&file_path)
.parent()
.map(|p| p.join(uri))
.map(|p| p.to_string_lossy().to_string())
.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Could not get the parent path of the file `{}`", file_path),
source_ranges: vec![args.source_range],
})
})?;
let bin_contents = args.ctx.fs.read(&bin_path, args.source_range).await.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![args.source_range],
})
})?;
import_files.push(ImportFile {
path: uri.to_string(),
data: bin_contents,
});
}
}
}
}
}
if args.ctx.is_mock() {
return Ok(ImportedGeometry {
id: exec_state.next_uuid(),
value: import_files.iter().map(|f| f.path.to_string()).collect(),
meta: vec![args.source_range.into()],
});
}
let id = exec_state.next_uuid();
let resp = args
.send_modeling_cmd(
id,
ModelingCmd::from(mcmd::ImportFiles {
files: import_files.clone(),
format,
}),
let format = options.map(InputFormat::from);
send_import_to_engine(
import_foreign(
std::path::Path::new(&file_path),
format,
exec_state,
&args.ctx,
args.source_range,
)
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::ImportFiles(imported_files),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("ImportFiles response was not as expected: {:?}", resp),
source_ranges: vec![args.source_range],
}));
};
Ok(ImportedGeometry {
id: imported_files.object_id,
value: import_files.iter().map(|f| f.path.to_string()).collect(),
meta: vec![args.source_range.into()],
})
}
/// Get the source format from the extension.
fn get_import_format_from_extension(ext: &str) -> Result<InputFormat> {
let format = match FileImportFormat::from_str(ext) {
Ok(format) => format,
Err(_) => {
if ext == "stp" {
FileImportFormat::Step
} else if ext == "glb" {
FileImportFormat::Gltf
} else {
anyhow::bail!("unknown source format for file extension: {}. Try setting the `--src-format` flag explicitly or use a valid format.", ext)
}
}
};
// Make the default units millimeters.
let ul = UnitLength::Millimeters;
// Zoo co-ordinate system.
//
// * Forward: -Y
// * Up: +Z
// * Handedness: Right
match format {
FileImportFormat::Step => Ok(InputFormat::Step(kcmc::format::step::import::Options {
split_closed_faces: false,
})),
FileImportFormat::Stl => Ok(InputFormat::Stl(kcmc::format::stl::import::Options {
coords: ZOO_COORD_SYSTEM,
units: ul,
})),
FileImportFormat::Obj => Ok(InputFormat::Obj(kcmc::format::obj::import::Options {
coords: ZOO_COORD_SYSTEM,
units: ul,
})),
FileImportFormat::Gltf => Ok(InputFormat::Gltf(kcmc::format::gltf::import::Options {})),
FileImportFormat::Ply => Ok(InputFormat::Ply(kcmc::format::ply::import::Options {
coords: ZOO_COORD_SYSTEM,
units: ul,
})),
FileImportFormat::Fbx => Ok(InputFormat::Fbx(kcmc::format::fbx::import::Options {})),
FileImportFormat::Sldprt => Ok(InputFormat::Sldprt(kcmc::format::sldprt::import::Options {
split_closed_faces: false,
})),
}
}
fn validate_extension_format(ext: InputFormat, given: InputFormat) -> Result<()> {
if let InputFormat::Stl(_) = ext {
if let InputFormat::Stl(_) = given {
return Ok(());
}
}
if let InputFormat::Obj(_) = ext {
if let InputFormat::Obj(_) = given {
return Ok(());
}
}
if let InputFormat::Ply(_) = ext {
if let InputFormat::Ply(_) = given {
return Ok(());
}
}
if ext == given {
return Ok(());
}
anyhow::bail!(
"The given format does not match the file extension. Expected: `{}`, Given: `{}`",
get_name_of_format(ext),
get_name_of_format(given)
.await?,
&args.ctx,
)
}
fn get_name_of_format(type_: InputFormat) -> &'static str {
match type_ {
InputFormat::Fbx(_) => "fbx",
InputFormat::Gltf(_) => "gltf",
InputFormat::Obj(_) => "obj",
InputFormat::Ply(_) => "ply",
InputFormat::Sldprt(_) => "sldprt",
InputFormat::Step(_) => "step",
InputFormat::Stl(_) => "stl",
}
.await
}

View File

@ -21,9 +21,9 @@ pub struct RequestBody {
pub async fn execute_and_snapshot(
code: &str,
units: UnitLength,
project_directory: Option<PathBuf>,
current_file: Option<PathBuf>,
) -> Result<image::DynamicImage, ExecError> {
let ctx = new_context(units, true, project_directory).await?;
let ctx = new_context(units, true, current_file).await?;
let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
let res = do_execute_and_snapshot(&ctx, program)
.await
@ -38,9 +38,9 @@ pub async fn execute_and_snapshot(
pub async fn execute_and_snapshot_ast(
ast: Program,
units: UnitLength,
project_directory: Option<PathBuf>,
current_file: Option<PathBuf>,
) -> Result<(ExecState, image::DynamicImage), ExecErrorWithState> {
let ctx = new_context(units, true, project_directory).await?;
let ctx = new_context(units, true, current_file).await?;
let res = do_execute_and_snapshot(&ctx, ast).await;
ctx.close().await;
res
@ -49,9 +49,9 @@ pub async fn execute_and_snapshot_ast(
pub async fn execute_and_snapshot_no_auth(
code: &str,
units: UnitLength,
project_directory: Option<PathBuf>,
current_file: Option<PathBuf>,
) -> Result<image::DynamicImage, ExecError> {
let ctx = new_context(units, false, project_directory).await?;
let ctx = new_context(units, false, current_file).await?;
let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
let res = do_execute_and_snapshot(&ctx, program)
.await
@ -88,7 +88,7 @@ async fn do_execute_and_snapshot(
pub async fn new_context(
units: UnitLength,
with_auth: bool,
project_directory: Option<PathBuf>,
current_file: Option<PathBuf>,
) -> Result<ExecutorContext, ConnectionError> {
let mut client = new_zoo_client(if with_auth { None } else { Some("bad_token".to_string()) }, None)
.map_err(ConnectionError::CouldNotMakeClient)?;
@ -99,18 +99,20 @@ pub async fn new_context(
client.set_base_url("https://api.zoo.dev".to_string());
}
let ctx = ExecutorContext::new(
&client,
ExecutorSettings {
units,
highlight_edges: true,
enable_ssao: false,
show_grid: false,
replay: None,
project_directory,
},
)
.await
.map_err(ConnectionError::Establishing)?;
let mut settings = ExecutorSettings {
units,
highlight_edges: true,
enable_ssao: false,
show_grid: false,
replay: None,
project_directory: None,
current_file: None,
};
if let Some(current_file) = current_file {
settings.with_current_file(current_file);
}
let ctx = ExecutorContext::new(&client, settings)
.await
.map_err(ConnectionError::Establishing)?;
Ok(ctx)
}

View File

@ -8,6 +8,7 @@ use crate::parsing::{
LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter,
PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration, VariableKind,
},
token::NumericSuffix,
PIPE_OPERATOR,
};
@ -370,6 +371,11 @@ impl VariableDeclaration {
}
}
// Used by TS.
pub fn format_number(value: f64, suffix: NumericSuffix) -> String {
format!("{value}{suffix}")
}
impl Literal {
fn recast(&self) -> String {
match self.value {

View File

@ -661,8 +661,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 5.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{

View File

@ -50,32 +50,6 @@ mindmap
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

View File

@ -72,32 +72,6 @@ mindmap
Wall
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
Cap End
SweepEdge Opposite
SweepEdge Adjacent
@ -186,32 +160,6 @@ mindmap
Wall
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
Cap End
SweepEdge Opposite
SweepEdge Adjacent
@ -282,32 +230,6 @@ mindmap
Wall
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
Cap End
SweepEdge Opposite
SweepEdge Adjacent

View File

@ -654,8 +654,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 2.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{
@ -671,8 +670,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 2.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
}
]

View File

@ -654,8 +654,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 2.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{
@ -671,8 +670,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 2.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
}
]

View File

@ -654,8 +654,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 2.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
}
]

View File

@ -654,8 +654,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 2.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
}
]

View File

@ -640,8 +640,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 2.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{
@ -657,8 +656,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 2.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
}
]

View File

@ -2487,8 +2487,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 1.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{
@ -2504,8 +2503,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 1.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{
@ -2521,8 +2519,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 1.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{
@ -2538,8 +2535,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 1.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{

View File

@ -1,13 +0,0 @@
---
source: kcl/src/simulation_tests.rs
description: Error from executing fillet-and-shell.kcl
snapshot_kind: text
---
KCL Engine error
× engine: Modeling command failed: websocket closed early
╭─[1:1]
1 │ rpizWidth = 30
· ▲
2 │ rpizLength = 65
╰────

View File

@ -7,7 +7,10 @@ description: Result of parsing import_constant.kcl
"body": [
{
"end": 39,
"path": "export_constant.kcl",
"path": {
"type": "Kcl",
"filename": "export_constant.kcl"
},
"selector": {
"type": "List",
"items": [

View File

@ -1,41 +1,45 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing import_cycle1.kcl
snapshot_kind: text
---
{
"Ok": {
"body": [
{
"end": 35,
"path": "import_cycle2.kcl",
"end": 69,
"path": {
"type": "Kcl",
"filename": "import_cycle2.kcl"
},
"selector": {
"type": "List",
"items": [
{
"alias": null,
"end": 10,
"end": 44,
"name": {
"end": 10,
"end": 44,
"name": "two",
"start": 7,
"start": 41,
"type": "Identifier"
},
"start": 7,
"start": 41,
"type": "ImportItem"
}
]
},
"start": 0,
"start": 34,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"declaration": {
"end": 75,
"end": 109,
"id": {
"end": 50,
"end": 84,
"name": "one",
"start": 47,
"start": 81,
"type": "Identifier"
},
"init": {
@ -43,25 +47,25 @@ description: Result of parsing import_cycle1.kcl
"body": [
{
"argument": {
"end": 73,
"end": 107,
"left": {
"arguments": [],
"callee": {
"end": 67,
"end": 101,
"name": "two",
"start": 64,
"start": 98,
"type": "Identifier"
},
"end": 69,
"start": 64,
"end": 103,
"start": 98,
"type": "CallExpression",
"type": "CallExpression"
},
"operator": "-",
"right": {
"end": 73,
"end": 107,
"raw": "1",
"start": 72,
"start": 106,
"type": "Literal",
"type": "Literal",
"value": {
@ -69,43 +73,43 @@ description: Result of parsing import_cycle1.kcl
"suffix": "None"
}
},
"start": 64,
"start": 98,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"end": 73,
"start": 57,
"end": 107,
"start": 91,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 75,
"start": 53
"end": 109,
"start": 87
},
"end": 75,
"end": 109,
"params": [],
"start": 50,
"start": 84,
"type": "FunctionExpression",
"type": "FunctionExpression"
},
"start": 47,
"start": 81,
"type": "VariableDeclarator"
},
"end": 75,
"end": 109,
"kind": "fn",
"start": 37,
"start": 71,
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"visibility": "export"
}
],
"end": 76,
"end": 110,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 37,
"start": 35,
"end": 71,
"start": 69,
"type": "NonCodeNode",
"value": {
"type": "newLine"
@ -113,7 +117,42 @@ description: Result of parsing import_cycle1.kcl
}
]
},
"startNodes": []
"startNodes": [
{
"end": 33,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "annotation",
"name": {
"end": 9,
"name": "settings",
"start": 1,
"type": "Identifier"
},
"properties": [
{
"end": 32,
"key": {
"end": 27,
"name": "defaultLengthUnit",
"start": 10,
"type": "Identifier"
},
"start": 10,
"type": "ObjectProperty",
"value": {
"end": 32,
"name": "in",
"start": 30,
"type": "Identifier",
"type": "Identifier"
}
}
]
}
}
]
},
"start": 0
}

View File

@ -1,14 +1,16 @@
---
source: kcl/src/simulation_tests.rs
description: Error from executing import_cycle1.kcl
snapshot_kind: text
---
KCL ImportCycle error
× import cycle: circular import of modules is not allowed: tests/
│ import_cycle1/import_cycle2.kcl -> tests/import_cycle1/import_cycle3.kcl
│ -> tests/import_cycle1/input.kcl -> tests/import_cycle1/import_cycle2.kcl
╭─[1:1]
1 │ import two from "import_cycle2.kcl"
│ -> tests/import_cycle1/input.kcl
╭─[2:1]
1 │ @settings(defaultLengthUnit = in)
2 │ import two from "import_cycle2.kcl"
· ───────────────────────────────────
2
3
╰────

View File

@ -1,3 +1,4 @@
@settings(defaultLengthUnit = mm)
import three from "import_cycle3.kcl"
export fn two = () => { return three() - 1 }

View File

@ -1,3 +1,4 @@
@settings(defaultLengthUnit = in)
import one from "input.kcl"
export fn three = () => { return one() + one() + one() }

View File

@ -1,3 +1,4 @@
@settings(defaultLengthUnit = in)
import two from "import_cycle2.kcl"
export fn one() {

View File

@ -7,7 +7,10 @@ description: Result of parsing import_export.kcl
"body": [
{
"end": 32,
"path": "export_1.kcl",
"path": {
"type": "Kcl",
"filename": "export_1.kcl"
},
"selector": {
"type": "List",
"items": [

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart import_glob.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map import_glob.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,71 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing import_foreign.kcl
---
{
"Ok": {
"body": [
{
"end": 36,
"path": {
"type": "Foreign",
"path": "../inputs/cube.gltf"
},
"selector": {
"type": "None",
"alias": {
"end": 36,
"name": "cube",
"start": 32,
"type": "Identifier"
}
},
"start": 0,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"declaration": {
"end": 50,
"id": {
"end": 43,
"name": "model",
"start": 38,
"type": "Identifier"
},
"init": {
"end": 50,
"name": "cube",
"start": 46,
"type": "Identifier",
"type": "Identifier"
},
"start": 38,
"type": "VariableDeclarator"
},
"end": 50,
"kind": "const",
"start": 38,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 51,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 38,
"start": 36,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -0,0 +1,3 @@
import "../inputs/cube.gltf" as cube
model = cube

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Operations executed import_glob.kcl
snapshot_kind: text
---
[]

View File

@ -0,0 +1,64 @@
---
source: kcl/src/simulation_tests.rs
description: Program memory after executing import_foreign.kcl
---
{
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "Number",
"value": 0.0,
"__meta": []
},
"cube": {
"type": "Module",
"value": 1,
"__meta": [
{
"sourceRange": [
0,
36,
0
]
}
]
},
"model": {
"type": "ImportedGeometry",
"id": "[uuid]",
"value": [
"cube.gltf"
],
"__meta": [
{
"sourceRange": [
0,
36,
0
]
}
]
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
}

View File

@ -0,0 +1,833 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact commands import_function_not_sketch.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": [
0,
33,
0
],
"command": {
"type": "set_scene_units",
"unit": "in"
}
},
{
"cmdId": "[uuid]",
"range": [
52,
71,
1
],
"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": [
77,
103,
1
],
"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": [
77,
103,
1
],
"command": {
"type": "start_path"
}
},
{
"cmdId": "[uuid]",
"range": [
77,
103,
1
],
"command": {
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": 4.0,
"y": 12.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [
109,
124,
1
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 2.0,
"y": 0.0,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [
130,
146,
1
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 0.0,
"y": -6.0,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [
152,
168,
1
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 4.0,
"y": -6.0,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [
174,
190,
1
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 0.0,
"y": -6.0,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [
196,
218,
1
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": -3.75,
"y": -4.5,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [
224,
242,
1
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 0.0,
"y": -5.5,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [
248,
264,
1
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": -2.0,
"y": 0.0,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [
270,
278,
1
],
"command": {
"type": "close_path",
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
270,
278,
1
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "revolve",
"target": "[uuid]",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"axis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"axis_is_2d": true,
"angle": {
"unit": "degrees",
"value": 360.0
},
"tolerance": 0.0000001
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "object_bring_to_front",
"object_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_extrusion_face_info",
"object_id": "[uuid]",
"edge_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_opposite_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
284,
310,
1
],
"command": {
"type": "solid3d_get_next_adjacent_edge",
"object_id": "[uuid]",
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "set_scene_units",
"unit": "in"
}
}
]

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart import_function_not_sketch.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,82 @@
```mermaid
flowchart LR
subgraph path2 [Path]
2["Path<br>[77, 103, 1]"]
3["Segment<br>[109, 124, 1]"]
4["Segment<br>[130, 146, 1]"]
5["Segment<br>[152, 168, 1]"]
6["Segment<br>[174, 190, 1]"]
7["Segment<br>[196, 218, 1]"]
8["Segment<br>[224, 242, 1]"]
9["Segment<br>[248, 264, 1]"]
10["Segment<br>[270, 278, 1]"]
11[Solid2d]
end
1["Plane<br>[52, 71, 1]"]
12["Sweep Revolve<br>[284, 310, 1]"]
13[Wall]
14[Wall]
15[Wall]
16[Wall]
17[Wall]
18[Wall]
19[Wall]
20[Wall]
21["SweepEdge Adjacent"]
22["SweepEdge Adjacent"]
23["SweepEdge Adjacent"]
24["SweepEdge Adjacent"]
25["SweepEdge Adjacent"]
26["SweepEdge Adjacent"]
27["SweepEdge Adjacent"]
1 --- 2
2 --- 3
2 --- 4
2 --- 5
2 --- 6
2 --- 7
2 --- 8
2 --- 9
2 --- 10
2 ---- 12
2 --- 11
3 --- 13
3 x--> 21
4 --- 14
4 --- 21
5 --- 15
5 --- 22
6 --- 16
6 --- 23
7 --- 17
7 --- 24
8 --- 18
8 --- 25
9 --- 19
9 --- 26
10 --- 20
10 --- 27
12 --- 13
12 --- 14
12 --- 15
12 --- 16
12 --- 17
12 --- 18
12 --- 19
12 --- 20
12 <--x 3
12 --- 21
12 <--x 4
12 <--x 5
12 --- 22
12 <--x 6
12 --- 23
12 <--x 7
12 --- 24
12 <--x 8
12 --- 25
12 <--x 9
12 --- 26
12 <--x 10
12 --- 27
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map import_function_not_sketch.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,63 @@
```mermaid
mindmap
root
Plane
Path
Segment
Wall
Segment
SweepEdge Adjacent
Segment
Wall
Segment
SweepEdge Adjacent
Segment
Wall
Segment
SweepEdge Adjacent
Segment
Wall
Segment
SweepEdge Adjacent
Segment
Wall
Segment
SweepEdge Adjacent
Segment
Wall
Segment
SweepEdge Adjacent
Segment
Wall
Segment
SweepEdge Adjacent
Segment
Wall
Segment
SweepEdge Adjacent
Sweep Revolve
Wall
Wall
Wall
Wall
Wall
Wall
Wall
Wall
Segment
SweepEdge Adjacent
Segment
Segment
SweepEdge Adjacent
Segment
SweepEdge Adjacent
Segment
SweepEdge Adjacent
Segment
SweepEdge Adjacent
Segment
SweepEdge Adjacent
Segment
SweepEdge Adjacent
Solid2d
```

View File

@ -0,0 +1,159 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing import_function_not_sketch.kcl
snapshot_kind: text
---
{
"Ok": {
"body": [
{
"end": 68,
"path": {
"type": "Kcl",
"filename": "my_functions.kcl"
},
"selector": {
"type": "List",
"items": [
{
"alias": null,
"end": 44,
"name": {
"end": 44,
"name": "two",
"start": 41,
"type": "Identifier"
},
"start": 41,
"type": "ImportItem"
}
]
},
"start": 34,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"declaration": {
"end": 108,
"id": {
"end": 83,
"name": "one",
"start": 80,
"type": "Identifier"
},
"init": {
"body": {
"body": [
{
"argument": {
"end": 106,
"left": {
"arguments": [],
"callee": {
"end": 100,
"name": "two",
"start": 97,
"type": "Identifier"
},
"end": 102,
"start": 97,
"type": "CallExpression",
"type": "CallExpression"
},
"operator": "-",
"right": {
"end": 106,
"raw": "1",
"start": 105,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
},
"start": 97,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"end": 106,
"start": 90,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 108,
"start": 86
},
"end": 108,
"params": [],
"start": 83,
"type": "FunctionExpression",
"type": "FunctionExpression"
},
"start": 80,
"type": "VariableDeclarator"
},
"end": 108,
"kind": "fn",
"start": 70,
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"visibility": "export"
}
],
"end": 109,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 70,
"start": 68,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": [
{
"end": 33,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "annotation",
"name": {
"end": 9,
"name": "settings",
"start": 1,
"type": "Identifier"
},
"properties": [
{
"end": 32,
"key": {
"end": 27,
"name": "defaultLengthUnit",
"start": 10,
"type": "Identifier"
},
"start": 10,
"type": "ObjectProperty",
"value": {
"end": 32,
"name": "in",
"start": 30,
"type": "Identifier",
"type": "Identifier"
}
}
]
}
}
]
},
"start": 0
}
}

View File

@ -0,0 +1,6 @@
@settings(defaultLengthUnit = in)
import two from "my_functions.kcl"
export fn one() {
return two() - 1
}

View File

@ -0,0 +1,15 @@
@settings(defaultLengthUnit = mm)
export part001 = startSketchOn('XY')
|> startProfileAt([4, 12], %)
|> line([2, 0], %)
|> line([0, -6], %)
|> line([4, -6], %)
|> line([0, -6], %)
|> line([-3.75, -4.5], %)
|> line([0, -5.5], %)
|> line([-2, 0], %)
|> close(%)
|> revolve({ axis = 'y' }, %) // default angle is 360
export fn two = () => { return 5 }

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Operations executed import_function_not_sketch.kcl
snapshot_kind: text
---
[]

View File

@ -0,0 +1,956 @@
---
source: kcl/src/simulation_tests.rs
description: Program memory after executing import_function_not_sketch.kcl
snapshot_kind: text
---
{
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "Number",
"value": 0.0,
"__meta": []
},
"one": {
"type": "Function",
"expression": {
"body": {
"body": [
{
"argument": {
"end": 106,
"left": {
"arguments": [],
"callee": {
"end": 100,
"name": "two",
"start": 97,
"type": "Identifier"
},
"end": 102,
"start": 97,
"type": "CallExpression",
"type": "CallExpression"
},
"operator": "-",
"right": {
"end": 106,
"raw": "1",
"start": 105,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
},
"start": 97,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"end": 106,
"start": 90,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 108,
"start": 86
},
"end": 108,
"params": [],
"start": 83,
"type": "FunctionExpression"
},
"memory": {
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "Number",
"value": 0.0,
"__meta": []
},
"two": {
"type": "Function",
"expression": {
"body": {
"body": [
{
"argument": {
"end": 368,
"moduleId": 1,
"raw": "5",
"start": 367,
"type": "Literal",
"type": "Literal",
"value": {
"value": 5.0,
"suffix": "None"
}
},
"end": 368,
"moduleId": 1,
"start": 360,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 370,
"moduleId": 1,
"start": 358
},
"end": 370,
"moduleId": 1,
"params": [],
"start": 352,
"type": "FunctionExpression"
},
"memory": {
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "Number",
"value": 0.0,
"__meta": []
},
"part001": {
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
109,
124,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
130,
146,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
152,
168,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
174,
190,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
196,
218,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
224,
242,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
248,
264,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
270,
278,
1
],
"tag": null,
"type": "extrudePlane"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
109,
124,
1
]
},
"from": [
4.0,
12.0
],
"tag": null,
"to": [
6.0,
12.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
130,
146,
1
]
},
"from": [
6.0,
12.0
],
"tag": null,
"to": [
6.0,
6.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
152,
168,
1
]
},
"from": [
6.0,
6.0
],
"tag": null,
"to": [
10.0,
0.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
174,
190,
1
]
},
"from": [
10.0,
0.0
],
"tag": null,
"to": [
10.0,
-6.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
196,
218,
1
]
},
"from": [
10.0,
-6.0
],
"tag": null,
"to": [
6.25,
-10.5
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
224,
242,
1
]
},
"from": [
6.25,
-10.5
],
"tag": null,
"to": [
6.25,
-16.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
248,
264,
1
]
},
"from": [
6.25,
-16.0
],
"tag": null,
"to": [
4.25,
-16.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
270,
278,
1
]
},
"from": [
4.25,
-16.0
],
"tag": null,
"to": [
4.0,
12.0
],
"type": "ToPoint"
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"units": {
"type": "Mm"
},
"__meta": []
},
"start": {
"from": [
4.0,
12.0
],
"to": [
4.0,
12.0
],
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
77,
103,
1
]
}
},
"units": {
"type": "Mm"
},
"__meta": [
{
"sourceRange": [
77,
103,
1
]
}
]
},
"height": 0.0,
"startCapId": null,
"endCapId": null,
"units": {
"type": "Mm"
},
"__meta": [
{
"sourceRange": [
77,
103,
1
]
}
]
}
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
},
"__meta": [
{
"sourceRange": [
352,
370,
1
]
}
]
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
},
"__meta": [
{
"sourceRange": [
83,
108,
0
]
}
]
},
"two": {
"type": "Function",
"expression": {
"body": {
"body": [
{
"argument": {
"end": 368,
"moduleId": 1,
"raw": "5",
"start": 367,
"type": "Literal",
"type": "Literal",
"value": {
"value": 5.0,
"suffix": "None"
}
},
"end": 368,
"moduleId": 1,
"start": 360,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 370,
"moduleId": 1,
"start": 358
},
"end": 370,
"moduleId": 1,
"params": [],
"start": 352,
"type": "FunctionExpression"
},
"memory": {
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "Number",
"value": 0.0,
"__meta": []
},
"part001": {
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
109,
124,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
130,
146,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
152,
168,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
174,
190,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
196,
218,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
224,
242,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
248,
264,
1
],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
270,
278,
1
],
"tag": null,
"type": "extrudePlane"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
109,
124,
1
]
},
"from": [
4.0,
12.0
],
"tag": null,
"to": [
6.0,
12.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
130,
146,
1
]
},
"from": [
6.0,
12.0
],
"tag": null,
"to": [
6.0,
6.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
152,
168,
1
]
},
"from": [
6.0,
6.0
],
"tag": null,
"to": [
10.0,
0.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
174,
190,
1
]
},
"from": [
10.0,
0.0
],
"tag": null,
"to": [
10.0,
-6.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
196,
218,
1
]
},
"from": [
10.0,
-6.0
],
"tag": null,
"to": [
6.25,
-10.5
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
224,
242,
1
]
},
"from": [
6.25,
-10.5
],
"tag": null,
"to": [
6.25,
-16.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
248,
264,
1
]
},
"from": [
6.25,
-16.0
],
"tag": null,
"to": [
4.25,
-16.0
],
"type": "ToPoint"
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
270,
278,
1
]
},
"from": [
4.25,
-16.0
],
"tag": null,
"to": [
4.0,
12.0
],
"type": "ToPoint"
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"units": {
"type": "Mm"
},
"__meta": []
},
"start": {
"from": [
4.0,
12.0
],
"to": [
4.0,
12.0
],
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
77,
103,
1
]
}
},
"units": {
"type": "Mm"
},
"__meta": [
{
"sourceRange": [
77,
103,
1
]
}
]
},
"height": 0.0,
"startCapId": null,
"endCapId": null,
"units": {
"type": "Mm"
},
"__meta": [
{
"sourceRange": [
77,
103,
1
]
}
]
}
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
},
"__meta": [
{
"sourceRange": [
352,
370,
1
]
}
]
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -7,7 +7,10 @@ description: Result of parsing import_glob.kcl
"body": [
{
"end": 35,
"path": "export_constant.kcl",
"path": {
"type": "Kcl",
"filename": "export_constant.kcl"
},
"selector": {
"end": 8,
"start": 7,

View File

@ -281,5 +281,123 @@ snapshot_kind: text
"object_id": "[uuid]",
"hidden": true
}
},
{
"cmdId": "[uuid]",
"range": [
81,
100,
1
],
"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": [
106,
149,
1
],
"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": [
106,
149,
1
],
"command": {
"type": "start_path"
}
},
{
"cmdId": "[uuid]",
"range": [
106,
149,
1
],
"command": {
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": 10.0,
"y": 0.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [
106,
149,
1
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "arc",
"center": {
"x": 0.0,
"y": 0.0
},
"radius": 10.0,
"start": {
"unit": "degrees",
"value": 0.0
},
"end": {
"unit": "degrees",
"value": 360.0
},
"relative": false
}
}
},
{
"cmdId": "[uuid]",
"range": [
106,
149,
1
],
"command": {
"type": "close_path",
"path_id": "[uuid]"
}
}
]

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart import_side_effect.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,12 @@
```mermaid
flowchart LR
subgraph path2 [Path]
2["Path<br>[106, 149, 1]"]
3["Segment<br>[106, 149, 1]"]
4[Solid2d]
end
1["Plane<br>[81, 100, 1]"]
1 --- 2
2 --- 3
2 --- 4
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map import_side_effect.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,8 @@
```mermaid
mindmap
root
Plane
Path
Segment
Solid2d
```

View File

@ -7,7 +7,10 @@ description: Result of parsing import_side_effect.kcl
"body": [
{
"end": 40,
"path": "export_side_effect.kcl",
"path": {
"type": "Kcl",
"filename": "export_side_effect.kcl"
},
"selector": {
"type": "List",
"items": [

View File

@ -1,14 +0,0 @@
---
source: kcl/src/simulation_tests.rs
description: Error from executing import_side_effect.kcl
---
KCL Semantic error
× semantic: Error loading imported file. Open it to view more details.
│ tests/import_side_effect/export_side_effect.kcl: Cannot send modeling
│ commands while importing. Wrap your code in a function if you want to
│ import the file.
╭────
1 │ import foo from "export_side_effect.kcl"
· ────────────────────────────────────────
╰────

View File

@ -2,4 +2,4 @@ export fn foo = () => { return 0 }
// This interacts with the engine.
part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> circle({ center = [0, 0], radius = 10 }, %)

View File

@ -0,0 +1,112 @@
---
source: kcl/src/simulation_tests.rs
description: Program memory after executing import_side_effect.kcl
snapshot_kind: text
---
{
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "Number",
"value": 0.0,
"__meta": []
},
"foo": {
"type": "Function",
"expression": {
"body": {
"body": [
{
"argument": {
"end": 32,
"moduleId": 1,
"raw": "0",
"start": 31,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
"end": 32,
"moduleId": 1,
"start": 24,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 34,
"moduleId": 1,
"start": 22
},
"end": 34,
"moduleId": 1,
"params": [],
"start": 16,
"type": "FunctionExpression"
},
"memory": {
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "Number",
"value": 0.0,
"__meta": []
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
},
"__meta": [
{
"sourceRange": [
16,
34,
1
]
}
]
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
}

View File

@ -7,7 +7,10 @@ description: Result of parsing import_whole.kcl
"body": [
{
"end": 66,
"path": "exported_mod.kcl",
"path": {
"type": "Kcl",
"filename": "exported_mod.kcl"
},
"selector": {
"type": "None",
"alias": {

View File

@ -1,13 +0,0 @@
---
source: kcl/src/simulation_tests.rs
description: Error from executing import_whole.kcl
snapshot_kind: text
---
KCL Engine error
× engine: Modeling command failed: websocket closed early
╭─[1:1]
1 │ import "exported_mod.kcl" as foo
· ▲
2 │
╰────

View File

@ -1,13 +0,0 @@
---
source: kcl/src/simulation_tests.rs
description: Error from executing kittycad_svg.kcl
snapshot_kind: text
---
KCL Engine error
× engine: Modeling command failed: websocket closed early
╭─[1:1]
1 │ svg = startSketchOn('XY')
· ▲
2 │ |> startProfileAt([0, 0], %)
╰────

View File

@ -782,8 +782,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 5.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{
@ -799,8 +798,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 5.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{
@ -1009,8 +1007,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 5.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{
@ -1026,8 +1023,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 5.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
}
]

View File

@ -43,34 +43,8 @@ mindmap
SweepEdge Adjacent
Sweep Extrusion
Wall
Path
Segment
Wall
SweepEdge Opposite
SweepEdge Adjacent
EdgeCut Fillet
Segment
Sweep Extrusion
Wall
Cap End
SweepEdge Opposite
SweepEdge Adjacent
Solid2d
Wall
Wall
Path
Segment
Wall
SweepEdge Opposite
SweepEdge Adjacent
EdgeCut Fillet
Segment
Sweep Extrusion
Wall
Cap End
SweepEdge Opposite
SweepEdge Adjacent
Solid2d
Cap Start
Cap End
SweepEdge Opposite

View File

@ -675,8 +675,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 20.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{
@ -692,8 +691,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 50.0,
"tolerance": 0.0000001,
"cut_type": "chamfer",
"face_id": "[uuid]"
"cut_type": "chamfer"
}
},
{
@ -709,8 +707,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 50.0,
"tolerance": 0.0000001,
"cut_type": "chamfer",
"face_id": "[uuid]"
"cut_type": "chamfer"
}
},
{

View File

@ -675,8 +675,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 20.0,
"tolerance": 0.0000001,
"cut_type": "fillet",
"face_id": "[uuid]"
"cut_type": "fillet"
}
},
{
@ -692,8 +691,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 50.0,
"tolerance": 0.0000001,
"cut_type": "chamfer",
"face_id": "[uuid]"
"cut_type": "chamfer"
}
},
{
@ -709,8 +707,7 @@ snapshot_kind: text
"edge_id": "[uuid]",
"radius": 50.0,
"tolerance": 0.0000001,
"cut_type": "chamfer",
"face_id": "[uuid]"
"cut_type": "chamfer"
}
},
{

View File

@ -57,39 +57,6 @@ mindmap
Wall
Wall
Wall
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
Sweep Extrusion
Wall
Wall
Wall
Wall
Cap Start
Cap End
SweepEdge Opposite
SweepEdge Adjacent
SweepEdge Opposite
SweepEdge Adjacent
SweepEdge Opposite
SweepEdge Adjacent
SweepEdge Opposite
SweepEdge Adjacent
Solid2d
Cap Start
Cap End
SweepEdge Opposite

Some files were not shown because too many files have changed in this diff Show More