Compare commits
31 Commits
jtran/fix-
...
nightly-v2
Author | SHA1 | Date | |
---|---|---|---|
c0817b00e4 | |||
4ea1d16fb6 | |||
d049bf33e8 | |||
7b11047d07 | |||
412e9568f2 | |||
9be208e5e1 | |||
842ef5ede9 | |||
3f855d7bad | |||
0a1a6e50cf | |||
d4e955289c | |||
c147a219f4 | |||
38513a1e25 | |||
c0c5c790ca | |||
8b60f75220 | |||
f91ad4331f | |||
59103a2118 | |||
9737c2550a | |||
bf9d01a8dd | |||
702e322f90 | |||
e82830754d | |||
7806377a5a | |||
859afa2fd8 | |||
0a5f3093fc | |||
b65f7939f6 | |||
c35dea5e07 | |||
fc66d4745f | |||
b313d26c2a | |||
00b94ead62 | |||
0531ea1ce9 | |||
5f9a4887c1 | |||
da7dfa16d8 |
16
.eslintrc
@ -6,16 +6,24 @@
|
||||
"plugins": [
|
||||
"css-modules",
|
||||
"jest",
|
||||
"jsx-a11y",
|
||||
"react",
|
||||
"react-hooks",
|
||||
"suggest-no-throw",
|
||||
"testing-library",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:css-modules/recommended"
|
||||
"plugin:css-modules/recommended",
|
||||
"plugin:jsx-a11y/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"jsx-a11y/click-events-have-key-events": "off",
|
||||
"jsx-a11y/no-autofocus": "off",
|
||||
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
||||
"no-restricted-globals": [
|
||||
"error",
|
||||
{
|
||||
@ -33,6 +41,9 @@
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
|
||||
"extends": [
|
||||
"plugin:testing-library/react"
|
||||
],
|
||||
"rules": {
|
||||
"suggest-no-throw/suggest-no-throw": "off",
|
||||
"testing-library/prefer-screen-queries": "off",
|
||||
@ -41,6 +52,9 @@
|
||||
},
|
||||
{
|
||||
"files": ["src/**/*.test.ts"],
|
||||
"extends": [
|
||||
"plugin:testing-library/react"
|
||||
],
|
||||
"rules": {
|
||||
"suggest-no-throw/suggest-no-throw": "off",
|
||||
}
|
||||
|
17
.github/dependabot.yml
vendored
@ -6,23 +6,29 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
directories:
|
||||
- '/'
|
||||
- '/packages/codemirror-lang-kcl/'
|
||||
- '/packages/codemirror-lsp-client/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
interval: weekly
|
||||
day: monday
|
||||
reviewers:
|
||||
- franknoirot
|
||||
- irev-dev
|
||||
- package-ecosystem: 'github-actions' # See documentation for possible values
|
||||
directory: '/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
interval: weekly
|
||||
day: monday
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||
directory: '/src/wasm-lib/' # Location of package manifests
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
interval: weekly
|
||||
day: monday
|
||||
reviewers:
|
||||
- adamchalmers
|
||||
- jessfraz
|
||||
@ -30,3 +36,6 @@ updates:
|
||||
serde-dependencies:
|
||||
patterns:
|
||||
- "serde*"
|
||||
wasm-bindgen-deps:
|
||||
patterns:
|
||||
- "wasm-bindgen*"
|
||||
|
2
.github/workflows/build-and-store-wasm.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
|
||||
# Upload the WASM bundle as an artifact
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wasm-bundle
|
||||
path: src/wasm-lib/pkg
|
||||
|
8
.github/workflows/build-apps.yml
vendored
@ -126,7 +126,13 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn' # Set this to npm, yarn or pnpm.
|
||||
|
||||
- run: yarn install
|
||||
- name: yarn install
|
||||
# Windows is picky sometimes and fails on fetch. Step takes about ~30s
|
||||
uses: nick-fields/retry@v3.0.0
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: yarn install
|
||||
|
||||
- run: yarn tronb:vite
|
||||
|
||||
|
32
.github/workflows/codemirror-lang-kcl.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
name: CodeMirror Lang KCL
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
yarn-unit-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
working-directory: packages/codemirror-lang-kcl
|
||||
|
||||
- run: yarn tsc
|
||||
working-directory: packages/codemirror-lang-kcl
|
||||
|
||||
- name: run unit tests
|
||||
run: yarn test
|
||||
working-directory: packages/codemirror-lang-kcl
|
@ -24,5 +24,3 @@ once fixed in engine will just start working here with no language changes.
|
||||
chamfer cases work currently.
|
||||
|
||||
- **Appearance**: Changing the appearance on a loft does not work.
|
||||
|
||||
- **Helix**: Currently sweeping a helix does not work.
|
||||
|
@ -53,7 +53,6 @@ layout: manual
|
||||
* [`hollow`](kcl/hollow)
|
||||
* [`import`](kcl/import)
|
||||
* [`inch`](kcl/inch)
|
||||
* [`int`](kcl/int)
|
||||
* [`lastSegX`](kcl/lastSegX)
|
||||
* [`lastSegY`](kcl/lastSegY)
|
||||
* [`legAngX`](kcl/legAngX)
|
||||
|
@ -4,6 +4,8 @@ excerpt: "Convert a number to an integer."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Convert a number to an integer.
|
||||
|
||||
DEPRECATED use floor(), ceil(), or round().
|
||||
|
@ -75843,7 +75843,6 @@
|
||||
"required": [
|
||||
"angleStart",
|
||||
"axis",
|
||||
"length",
|
||||
"radius",
|
||||
"revolutions"
|
||||
],
|
||||
@ -75864,9 +75863,10 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"length": {
|
||||
"description": "Length of the helix.",
|
||||
"description": "Length of the helix. This is not necessary if the helix is created around an edge. If not given the length of the edge is used.",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
"format": "double",
|
||||
"nullable": true
|
||||
},
|
||||
"radius": {
|
||||
"description": "Radius of the helix.",
|
||||
@ -76961,8 +76961,9 @@
|
||||
"unpublished": false,
|
||||
"deprecated": false,
|
||||
"examples": [
|
||||
"// Create a helix around the Z axis.\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 16,\n length = 10,\n radius = 5,\n axis = 'Z'\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('YZ')\n |> circle({ center = [0, 0], radius = 1 }, %)\n// |> sweep({ path = helixPath }, %)",
|
||||
""
|
||||
"// Create a helix around the Z axis.\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 5,\n length = 10,\n radius = 5,\n axis = 'Z'\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('YZ')\n |> circle({ center = [0, 0], radius = 0.5 }, %)\n |> sweep({ path = helixPath }, %)",
|
||||
"// Create a helix around an edge.\nhelper001 = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %, $edge001)\n\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 5,\n length = 10,\n radius = 5,\n axis = edge001\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 0.5 }, %)\n |> sweep({ path = helixPath }, %)",
|
||||
"// Create a helix around a custom axis.\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 5,\n length = 10,\n radius = 5,\n axis = {\n custom = {\n axis = [0, 0, 1.0],\n origin = [0, 0.25, 0]\n }\n }\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 1 }, %)\n |> sweep({ path = helixPath }, %)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -87203,7 +87204,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
"unpublished": false,
|
||||
"deprecated": false,
|
||||
"deprecated": true,
|
||||
"examples": [
|
||||
"n = int(ceil(5 / 2))\nassertEqual(n, 3, 0.0001, \"5/2 = 2.5, rounded up makes 3\")\n// Draw n cylinders.\nstartSketchOn('XZ')\n |> circle({ center = [0, 0], radius = 2 }, %)\n |> extrude(5, %)\n |> patternTransform(n, fn(id) {\n return { translate = [4 * id, 0, 0] }\n }, %)"
|
||||
]
|
||||
@ -193683,7 +193684,7 @@
|
||||
"deprecated": false,
|
||||
"examples": [
|
||||
"// Create a pipe using a sweep.\n\n\n// Create a path for the sweep.\nsweepPath = startSketchOn('XZ')\n |> startProfileAt([0.05, 0.05], %)\n |> line([0, 7], %)\n |> tangentialArc({ offset = 90, radius = 5 }, %)\n |> line([-3, 0], %)\n |> tangentialArc({ offset = -90, radius = 5 }, %)\n |> line([0, 7], %)\n\n// Create a hole for the pipe.\npipeHole = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 1.5 }, %)\n\nsweepSketch = startSketchOn('XY')\n |> circle({ center = [0, 0], radius = 2 }, %)\n |> hole(pipeHole, %)\n |> sweep({ path = sweepPath }, %)",
|
||||
"// Create a spring by sweeping around a helix path.\n\n\n// Create a helix around the Z axis.\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 16,\n length = 10,\n radius = 5,\n axis = 'Z'\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('YZ')\n |> circle({ center = [0, 0], radius = 1 }, %)\n// |> sweep({ path = helixPath }, %)"
|
||||
"// Create a spring by sweeping around a helix path.\n\n\n// Create a helix around the Z axis.\nhelixPath = helix({\n angleStart = 0,\n ccw = true,\n revolutions = 4,\n length = 10,\n radius = 5,\n axis = 'Z'\n})\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn('YZ')\n |> circle({ center = [0, 0], radius = 1 }, %)\n |> sweep({ path = helixPath }, %)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ Data for a helix.
|
||||
| `revolutions` |`number`| Number of revolutions. | No |
|
||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No |
|
||||
| `length` |`number`| Length of the helix. | No |
|
||||
| `length` |`number`| Length of the helix. This is not necessary if the helix is created around an edge. If not given the length of the edge is used. | No |
|
||||
| `radius` |`number`| Radius of the helix. | No |
|
||||
| `axis` |[`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference)| Axis to use as mirror. | No |
|
||||
|
||||
|
@ -121,18 +121,23 @@ export class AuthenticatedTronApp {
|
||||
|
||||
export const fixtures = {
|
||||
cmdBar: async ({ page }: { page: Page }, use: any) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use(new CmdBarFixture(page))
|
||||
},
|
||||
editor: async ({ page }: { page: Page }, use: any) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use(new EditorFixture(page))
|
||||
},
|
||||
toolbar: async ({ page }: { page: Page }, use: any) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use(new ToolbarFixture(page))
|
||||
},
|
||||
scene: async ({ page }: { page: Page }, use: any) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use(new SceneFixture(page))
|
||||
},
|
||||
homePage: async ({ page }: { page: Page }, use: any) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use(new HomePageFixture(page))
|
||||
},
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ export class ToolbarFixture {
|
||||
extrudeButton!: Locator
|
||||
loftButton!: Locator
|
||||
sweepButton!: Locator
|
||||
chamferButton!: Locator
|
||||
shellButton!: Locator
|
||||
offsetPlaneButton!: Locator
|
||||
startSketchBtn!: Locator
|
||||
@ -42,6 +43,7 @@ export class ToolbarFixture {
|
||||
this.extrudeButton = page.getByTestId('extrude')
|
||||
this.loftButton = page.getByTestId('loft')
|
||||
this.sweepButton = page.getByTestId('sweep')
|
||||
this.chamferButton = page.getByTestId('chamfer3d')
|
||||
this.shellButton = page.getByTestId('shell')
|
||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
|
@ -829,12 +829,6 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
||||
})
|
||||
await selectSketches()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: { Selection: '2 faces' },
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
} else {
|
||||
await test.step(`Preselect the two sketches`, async () => {
|
||||
@ -844,12 +838,6 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
||||
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
|
||||
await toolbar.loftButton.click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: { Selection: '2 faces' },
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
}
|
||||
|
||||
@ -1032,6 +1020,222 @@ sketch002 = startSketchOn('XZ')
|
||||
})
|
||||
})
|
||||
|
||||
test(`Chamfer point-and-click`, async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
// TODO: fix this test on windows after the electron migration
|
||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||
|
||||
// Code samples
|
||||
const initialCode = `sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-12, -6], %)
|
||||
|> line([0, 12], %)
|
||||
|> line([24, 0], %)
|
||||
|> line([0, -12], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(-12, sketch001)
|
||||
`
|
||||
const firstChamferDeclaration = 'chamfer({ length = 5, tags = [seg01] }, %)'
|
||||
const secondChamferDeclaration =
|
||||
'chamfer({ length = 5, tags = [getOppositeEdge(seg01)] }, %)'
|
||||
|
||||
// Locators
|
||||
const firstEdgeLocation = { x: 600, y: 193 }
|
||||
const secondEdgeLocation = { x: 600, y: 383 }
|
||||
const bodyLocation = { x: 630, y: 290 }
|
||||
const [clickOnFirstEdge] = scene.makeMouseHelpers(
|
||||
firstEdgeLocation.x,
|
||||
firstEdgeLocation.y
|
||||
)
|
||||
const [clickOnSecondEdge] = scene.makeMouseHelpers(
|
||||
secondEdgeLocation.x,
|
||||
secondEdgeLocation.y
|
||||
)
|
||||
|
||||
// Colors
|
||||
const edgeColorWhite: [number, number, number] = [248, 248, 248]
|
||||
const edgeColorYellow: [number, number, number] = [251, 251, 40] // Mac:B=67 Ubuntu:B=12
|
||||
const bodyColor: [number, number, number] = [155, 155, 155]
|
||||
const chamferColor: [number, number, number] = [168, 168, 168]
|
||||
const backgroundColor: [number, number, number] = [30, 30, 30]
|
||||
const lowTolerance = 20
|
||||
const highTolerance = 40
|
||||
|
||||
// Setup
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await test.step(`Verify scene is loaded`, async () => {
|
||||
// verify modeling scene is loaded
|
||||
await scene.expectPixelColor(
|
||||
backgroundColor,
|
||||
secondEdgeLocation,
|
||||
lowTolerance
|
||||
)
|
||||
|
||||
// wait for stream to load
|
||||
await scene.expectPixelColor(bodyColor, bodyLocation, highTolerance)
|
||||
})
|
||||
|
||||
// Test 1: Command bar flow with preselected edges
|
||||
await test.step(`Select first edge`, async () => {
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
firstEdgeLocation,
|
||||
lowTolerance
|
||||
)
|
||||
await clickOnFirstEdge()
|
||||
await scene.expectPixelColor(
|
||||
edgeColorYellow,
|
||||
firstEdgeLocation,
|
||||
highTolerance // Ubuntu color mismatch can require high tolerance
|
||||
)
|
||||
})
|
||||
|
||||
await test.step(`Apply chamfer to the preselected edge`, async () => {
|
||||
await toolbar.chamferButton.click()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Chamfer',
|
||||
highlightedHeaderArg: 'selection',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Length: '',
|
||||
},
|
||||
stage: 'arguments',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Chamfer',
|
||||
highlightedHeaderArg: 'length',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: '5',
|
||||
headerArguments: {
|
||||
Selection: '1 face',
|
||||
Length: '',
|
||||
},
|
||||
stage: 'arguments',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Chamfer',
|
||||
headerArguments: {
|
||||
Selection: '1 face',
|
||||
Length: '5',
|
||||
},
|
||||
stage: 'review',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Confirm code is added to the editor`, async () => {
|
||||
await editor.expectEditor.toContain(firstChamferDeclaration)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: ['|>chamfer({length=5,tags=[seg01]},%)'],
|
||||
highlightedCode: '',
|
||||
})
|
||||
})
|
||||
|
||||
await test.step(`Confirm scene has changed`, async () => {
|
||||
await scene.expectPixelColor(chamferColor, firstEdgeLocation, lowTolerance)
|
||||
})
|
||||
|
||||
// Test 2: Command bar flow without preselected edges
|
||||
await test.step(`Open chamfer UI without selecting edges`, async () => {
|
||||
await toolbar.chamferButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Length: '',
|
||||
},
|
||||
highlightedHeaderArg: 'selection',
|
||||
commandName: 'Chamfer',
|
||||
})
|
||||
})
|
||||
|
||||
await test.step(`Select second edge`, async () => {
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
secondEdgeLocation,
|
||||
lowTolerance
|
||||
)
|
||||
await clickOnSecondEdge()
|
||||
await scene.expectPixelColor(
|
||||
edgeColorYellow,
|
||||
secondEdgeLocation,
|
||||
highTolerance // Ubuntu color mismatch can require high tolerance
|
||||
)
|
||||
})
|
||||
|
||||
await test.step(`Apply chamfer to the second edge`, async () => {
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Chamfer',
|
||||
highlightedHeaderArg: 'selection',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Length: '',
|
||||
},
|
||||
stage: 'arguments',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Chamfer',
|
||||
highlightedHeaderArg: 'length',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: '5',
|
||||
headerArguments: {
|
||||
Selection: '1 sweepEdge',
|
||||
Length: '',
|
||||
},
|
||||
stage: 'arguments',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Chamfer',
|
||||
headerArguments: {
|
||||
Selection: '1 sweepEdge',
|
||||
Length: '5',
|
||||
},
|
||||
stage: 'review',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Confirm code is added to the editor`, async () => {
|
||||
await editor.expectEditor.toContain(secondChamferDeclaration)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: ['length=5,'],
|
||||
highlightedCode: '',
|
||||
})
|
||||
})
|
||||
|
||||
await test.step(`Confirm scene has changed`, async () => {
|
||||
await scene.expectPixelColor(
|
||||
backgroundColor,
|
||||
secondEdgeLocation,
|
||||
lowTolerance
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
const shellPointAndClickCapCases = [
|
||||
{ shouldPreselect: true },
|
||||
{ shouldPreselect: false },
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
@ -8,8 +8,8 @@ import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||
|
||||
test.describe('Testing in-app sample loading', () => {
|
||||
/**
|
||||
* Note this test implicitly depends on the KCL sample "car-wheel.kcl",
|
||||
* its title, and its units settings. https://github.com/KittyCAD/kcl-samples/blob/main/car-wheel/car-wheel.kcl
|
||||
* Note this test implicitly depends on the KCL sample "a-parametric-bearing-pillow-block",
|
||||
* its title, and its units settings. https://github.com/KittyCAD/kcl-samples/blob/main/a-parametric-bearing-pillow-block/main.kcl
|
||||
*/
|
||||
test('Web: should overwrite current code, cannot create new file', async ({
|
||||
editor,
|
||||
@ -29,8 +29,8 @@ test.describe('Testing in-app sample loading', () => {
|
||||
|
||||
// Locators and constants
|
||||
const newSample = {
|
||||
file: 'car-wheel' + FILE_EXT,
|
||||
title: 'Car Wheel',
|
||||
file: 'a-parametric-bearing-pillow-block' + FILE_EXT,
|
||||
title: 'A Parametric Bearing Pillow Block',
|
||||
}
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const samplesCommandOption = page.getByRole('option', {
|
||||
@ -75,8 +75,8 @@ test.describe('Testing in-app sample loading', () => {
|
||||
|
||||
/**
|
||||
* Note this test implicitly depends on the KCL samples:
|
||||
* "car-wheel.kcl": https://github.com/KittyCAD/kcl-samples/blob/main/car-wheel/car-wheel.kcl
|
||||
* "gear-rack.kcl": https://github.com/KittyCAD/kcl-samples/blob/main/gear-rack/gear-rack.kcl
|
||||
* "a-parametric-bearing-pillow-block": https://github.com/KittyCAD/kcl-samples/blob/main/a-parametric-bearing-pillow-block/main.kcl
|
||||
* "gear-rack": https://github.com/KittyCAD/kcl-samples/blob/main/gear-rack/main.kcl
|
||||
*/
|
||||
test(
|
||||
'Desktop: should create new file by default, optionally overwrite',
|
||||
@ -93,8 +93,8 @@ test.describe('Testing in-app sample loading', () => {
|
||||
|
||||
// Locators and constants
|
||||
const sampleOne = {
|
||||
file: 'car-wheel' + FILE_EXT,
|
||||
title: 'Car Wheel',
|
||||
file: 'a-parametric-bearing-pillow-block' + FILE_EXT,
|
||||
title: 'A Parametric Bearing Pillow Block',
|
||||
}
|
||||
const sampleTwo = {
|
||||
file: 'gear-rack' + FILE_EXT,
|
||||
|
@ -906,53 +906,6 @@ test.describe('Testing selections', () => {
|
||||
).not.toBeDisabled()
|
||||
})
|
||||
|
||||
test('Fillet button states test', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const selectSegment = () => page.getByText(`line([10, 0], %)`).click()
|
||||
const selectClose = () => page.getByText(`close(%)`).click()
|
||||
const clickEmpty = () => page.mouse.click(950, 100)
|
||||
|
||||
// Now that we don't disable toolbar buttons based on selection,
|
||||
// but rather based on a "selection" step in the command palette,
|
||||
// the fillet button should always be enabled with a good network connection.
|
||||
// I'm not sure if this test is actually useful anymore.
|
||||
await selectSegment()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||
await clickEmpty()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||
|
||||
// test fillet button with the body in the scene
|
||||
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||
extrude001 = extrude(10, sketch001)`
|
||||
await u.codeLocator.clear()
|
||||
await u.codeLocator.fill(codeToAdd)
|
||||
await selectSegment()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||
await selectClose()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||
await clickEmpty()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||
})
|
||||
|
||||
const removeAfterFirstParenthesis = (inputString: string) => {
|
||||
const index = inputString.indexOf('(')
|
||||
if (index !== -1) {
|
||||
|
13
package.json
@ -65,7 +65,7 @@
|
||||
"vscode-languageserver-protocol": "^3.17.5",
|
||||
"vscode-uri": "^3.0.8",
|
||||
"web-vitals": "^3.5.2",
|
||||
"xstate": "^5.17.4",
|
||||
"xstate": "^5.19.2",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
@ -91,8 +91,8 @@
|
||||
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
|
||||
"lint-fix": "eslint --fix src/**/*.ts src/**/*.tsx e2e/**/*.ts packages/codemirror-lsp-client/src/**/*.ts",
|
||||
"lint": "eslint --max-warnings 0 src/**/*.ts src/**/*.tsx e2e/**/*.ts packages/codemirror-lsp-client/src/**/*.ts",
|
||||
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
|
||||
"lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
|
||||
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||
"files:set-notes": "./scripts/set-files-notes.sh",
|
||||
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
|
||||
@ -149,7 +149,7 @@
|
||||
"@electron-forge/plugin-vite": "7.4.0",
|
||||
"@electron/fuses": "1.8.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@lezer/generator": "^1.7.1",
|
||||
"@lezer/generator": "^1.7.2",
|
||||
"@nabla/vite-plugin-eslint": "^2.0.5",
|
||||
"@playwright/test": "^1.49.0",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
@ -183,8 +183,11 @@
|
||||
"eslint-plugin-css-modules": "^2.12.0",
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
"eslint-plugin-jest": "^28.10.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.3",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||
"eslint-plugin-testing-library": "^7.1.1",
|
||||
"happy-dom": "^16.3.0",
|
||||
"http-server": "^14.1.1",
|
||||
"husky": "^9.1.5",
|
||||
@ -198,7 +201,7 @@
|
||||
"setimmediate": "^1.0.5",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.19.1",
|
||||
"vite": "^5.4.6",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
|
1
packages/codemirror-lang-kcl/.gitignore
vendored
@ -4,4 +4,5 @@ dist
|
||||
tsconfig.tsbuildinfo
|
||||
*.d.ts
|
||||
*.js
|
||||
!postcss.config.js
|
||||
!rollup.config.js
|
||||
|
@ -28,6 +28,7 @@
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"rollup": "^4.29.1",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"files": [
|
||||
|
1
packages/codemirror-lang-kcl/postcss.config.js
Normal file
@ -0,0 +1 @@
|
||||
// This is here to prevent using the one in the root of the project.
|
@ -398,7 +398,7 @@ check-error@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc"
|
||||
integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==
|
||||
|
||||
debug@^4.3.7:
|
||||
debug@^4.1.1, debug@^4.3.7:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
|
||||
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
|
||||
@ -471,6 +471,11 @@ function-bind@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
||||
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
||||
|
||||
globrex@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
|
||||
integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
|
||||
|
||||
hasown@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
|
||||
@ -647,6 +652,11 @@ tinyspy@^3.0.2:
|
||||
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a"
|
||||
integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==
|
||||
|
||||
tsconfck@^3.0.3:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.4.tgz#de01a15334962e2feb526824339b51be26712229"
|
||||
integrity sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==
|
||||
|
||||
typescript@^5.7.2:
|
||||
version "5.7.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
|
||||
@ -663,6 +673,15 @@ vite-node@2.1.8:
|
||||
pathe "^1.1.2"
|
||||
vite "^5.0.0"
|
||||
|
||||
vite-tsconfig-paths@^4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz#321f02e4b736a90ff62f9086467faf4e2da857a9"
|
||||
integrity sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
globrex "^0.1.2"
|
||||
tsconfck "^3.0.3"
|
||||
|
||||
vite@^5.0.0:
|
||||
version "5.4.11"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5"
|
||||
|
@ -29,7 +29,7 @@
|
||||
"vscode-uri": "^3.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.14.9",
|
||||
"@types/node": "^22.10.6",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
|
@ -109,12 +109,12 @@
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
|
||||
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
|
||||
|
||||
"@types/node@^20.14.9":
|
||||
version "20.14.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420"
|
||||
integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==
|
||||
"@types/node@^22.10.6":
|
||||
version "22.10.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.6.tgz#5c6795e71635876039f853cbccd59f523d9e4239"
|
||||
integrity sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
undici-types "~6.20.0"
|
||||
|
||||
acorn-walk@^8.1.1:
|
||||
version "8.3.3"
|
||||
@ -187,10 +187,10 @@ typescript@^5.7.2:
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
|
||||
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
|
||||
|
||||
undici-types@~5.26.4:
|
||||
version "5.26.5"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||
undici-types@~6.20.0:
|
||||
version "6.20.0"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
|
||||
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
|
||||
|
||||
v8-compile-cache-lib@^3.0.1:
|
||||
version "3.0.1"
|
||||
|
@ -1,172 +1,212 @@
|
||||
[
|
||||
{
|
||||
"file": "80-20-rail.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "80-20-rail/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "80/20 Rail",
|
||||
"description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position"
|
||||
},
|
||||
{
|
||||
"file": "a-parametric-bearing-pillow-block.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "a-parametric-bearing-pillow-block/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "A Parametric Bearing Pillow Block",
|
||||
"description": "A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads."
|
||||
},
|
||||
{
|
||||
"file": "ball-bearing.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "ball-bearing/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Ball Bearing",
|
||||
"description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads."
|
||||
},
|
||||
{
|
||||
"file": "bracket.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "bracket/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Shelf Bracket",
|
||||
"description": "This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided."
|
||||
},
|
||||
{
|
||||
"file": "brake-caliper.kcl",
|
||||
"title": "Brake Caliper",
|
||||
"description": "Brake calipers are used to squeeze the brake pads against the rotor, causing larger and larger amounts of friction depending on how hard the brakes are pressed."
|
||||
},
|
||||
{
|
||||
"file": "car-wheel.kcl",
|
||||
"title": "Car Wheel",
|
||||
"description": "A sports car wheel with a circular lug pattern and spokes."
|
||||
},
|
||||
{
|
||||
"file": "car-wheel-assembly.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl",
|
||||
"multipleFiles": true,
|
||||
"title": "Car Wheel Assembly",
|
||||
"description": "A car wheel assembly with a rotor, tire, and lug nuts."
|
||||
},
|
||||
{
|
||||
"file": "dodecahedron.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "dodecahedron/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Hollow Dodecahedron",
|
||||
"description": "A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the individual faces of the dodecahedron and extruding inwards."
|
||||
},
|
||||
{
|
||||
"file": "enclosure.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "enclosure/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Enclosure",
|
||||
"description": "An enclosure body and sealing lid for storing items"
|
||||
},
|
||||
{
|
||||
"file": "flange-with-patterns.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "flange-with-patterns/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Flange",
|
||||
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
||||
},
|
||||
{
|
||||
"file": "flange-xy.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "flange-xy/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Flange with XY coordinates",
|
||||
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
||||
},
|
||||
{
|
||||
"file": "focusrite-scarlett-mounting-bracket.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "focusrite-scarlett-mounting-bracket/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "A mounting bracket for the Focusrite Scarlett Solo audio interface",
|
||||
"description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material"
|
||||
},
|
||||
{
|
||||
"file": "food-service-spatula.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "food-service-spatula/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Food Service Spatula",
|
||||
"description": "Use these spatulas for mixing, flipping, and scraping."
|
||||
},
|
||||
{
|
||||
"file": "french-press.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "french-press/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "French Press",
|
||||
"description": "A french press immersion coffee maker"
|
||||
},
|
||||
{
|
||||
"file": "gear.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "gear/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Spur Gear",
|
||||
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear."
|
||||
},
|
||||
{
|
||||
"file": "gear-rack.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "gear-rack/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "100mm Gear Rack",
|
||||
"description": "A flat bar or rail that is engraved with teeth along its length. These teeth are designed to mesh with the teeth of a gear, known as a pinion. When the pinion, a small cylindrical gear, rotates, its teeth engage with the teeth on the rack, causing the rack to move linearly. Conversely, linear motion applied to the rack will cause the pinion to rotate."
|
||||
},
|
||||
{
|
||||
"file": "hex-nut.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "hex-nut/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Hex nut",
|
||||
"description": "A hex nut is a type of fastener with a threaded hole and a hexagonal outer shape, used in a wide variety of applications to secure parts together. The hexagonal shape allows for a greater torque to be applied with wrenches or tools, making it one of the most common nut types in hardware."
|
||||
},
|
||||
{
|
||||
"file": "i-beam.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "i-beam/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "I-beam",
|
||||
"description": "A structural metal beam with an I shaped cross section. Often used in construction"
|
||||
},
|
||||
{
|
||||
"file": "kitt.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "kitt/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Kitt",
|
||||
"description": "The beloved KittyCAD mascot in a voxelized style."
|
||||
},
|
||||
{
|
||||
"file": "lego.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "lego/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Lego Brick",
|
||||
"description": "A standard Lego brick. This is a small, plastic construction block toy that can be interlocked with other blocks to build various structures, models, and figures. There are a lot of hacks used in this code."
|
||||
},
|
||||
{
|
||||
"file": "lug-nut.kcl",
|
||||
"title": "Lug Nut",
|
||||
"description": "lug Nuts are essential components used to create secure connections, whether for electrical purposes, like terminating wires or grounding, or for mechanical purposes, such as providing mounting points or reinforcing structural joints."
|
||||
},
|
||||
{
|
||||
"file": "mounting-plate.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "mounting-plate/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Mounting Plate",
|
||||
"description": "A flat piece of material, often metal or plastic, that serves as a support or base for attaching, securing, or mounting various types of equipment, devices, or components."
|
||||
},
|
||||
{
|
||||
"file": "multi-axis-robot.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "multi-axis-robot/main.kcl",
|
||||
"multipleFiles": true,
|
||||
"title": "Robot Arm",
|
||||
"description": "A 4 axis robotic arm for industrial use. These machines can be used for assembly, packaging, organization of goods, and quality inspection processes"
|
||||
},
|
||||
{
|
||||
"file": "pipe.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "pipe/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Pipe",
|
||||
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
|
||||
},
|
||||
{
|
||||
"file": "pipe-flange-assembly.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "pipe-flange-assembly/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Pipe and Flange Assembly",
|
||||
"description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint."
|
||||
},
|
||||
{
|
||||
"file": "pipe-with-bend.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "pipe-with-bend/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Pipe with bend",
|
||||
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
|
||||
},
|
||||
{
|
||||
"file": "poopy-shoe.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "poopy-shoe/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Poopy Shoe",
|
||||
"description": "poop shute for bambu labs printer - optimized for printing."
|
||||
},
|
||||
{
|
||||
"file": "router-template-cross-bar.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "router-template-cross-bar/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Router template for a cross bar",
|
||||
"description": "A guide for routing a notch into a cross bar."
|
||||
},
|
||||
{
|
||||
"file": "router-template-slate.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "router-template-slate/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Router template for a slate",
|
||||
"description": "A guide for routing a slate for a cross bar."
|
||||
},
|
||||
{
|
||||
"file": "sheet-metal-bracket.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "sheet-metal-bracket/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Sheet Metal Bracket",
|
||||
"description": "A component typically made from flat sheet metal through various manufacturing processes such as bending, punching, cutting, and forming. These brackets are used to support, attach, or mount other hardware components, often providing a structural or functional base for assembly."
|
||||
},
|
||||
{
|
||||
"file": "socket-head-cap-screw.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "socket-head-cap-screw/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Socket Head Cap Screw",
|
||||
"description": "This is for a #10-24 screw that is 1.00 inches long. A socket head cap screw is a type of fastener that is widely used in a variety of applications requiring a high strength fastening solution. It is characterized by its cylindrical head and internal hexagonal drive, which allows for tightening with an Allen wrench or hex key."
|
||||
},
|
||||
{
|
||||
"file": "tire.kcl",
|
||||
"title": "Tire",
|
||||
"description": "A tire is a critical component of a vehicle that provides the necessary traction and grip between the car and the road. It supports the vehicle's weight and absorbs shocks from road irregularities."
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "walkie-talkie/main.kcl",
|
||||
"multipleFiles": true,
|
||||
"title": "Walkie Talkie",
|
||||
"description": "A portable, handheld two-way radio device that allows users to communicate wirelessly over short to medium distances. It operates on specific radio frequencies and features a push-to-talk button for transmitting messages, making it ideal for quick and reliable communication in outdoor, work, or emergency settings."
|
||||
},
|
||||
{
|
||||
"file": "washer.kcl",
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "washer/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Washer",
|
||||
"description": "A small, typically disk-shaped component with a hole in the middle, used in a wide range of applications, primarily in conjunction with fasteners like bolts and screws. Washers distribute the load of a fastener across a broader area. This is especially important when the fastening surface is soft or uneven, as it helps to prevent damage to the surface and ensures the load is evenly distributed, reducing the risk of the fastener becoming loose over time."
|
||||
},
|
||||
{
|
||||
"file": "wheel-rotor.kcl",
|
||||
"title": "Wheel rotor",
|
||||
"description": "A component of a disc brake system. It provides a surface for brake pads to press against, generating the friction needed to slow or stop the vehicle."
|
||||
}
|
||||
]
|
@ -108,6 +108,8 @@ export class CameraControls {
|
||||
interactionGuards: MouseGuard = cameraMouseDragGuards.Zoo
|
||||
isFovAnimationInProgress = false
|
||||
perspectiveFovBeforeOrtho = 45
|
||||
// NOTE: Duplicated state across Provider and singleton. Mapped from settingsMachine
|
||||
_setting_allowOrbitInSketchMode = false
|
||||
get isPerspective() {
|
||||
return this.camera instanceof PerspectiveCamera
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {
|
||||
BoxGeometry,
|
||||
Color,
|
||||
DoubleSide,
|
||||
Group,
|
||||
Intersection,
|
||||
@ -59,6 +60,7 @@ import {
|
||||
resultIsOk,
|
||||
SourceRange,
|
||||
} from 'lang/wasm'
|
||||
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
|
||||
import {
|
||||
engineCommandManager,
|
||||
kclManager,
|
||||
@ -70,7 +72,7 @@ import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { executeAst, ToolTip } from 'lang/langHelpers'
|
||||
import {
|
||||
createProfileStartHandle,
|
||||
createArcGeometry,
|
||||
createCircleGeometry,
|
||||
SegmentUtils,
|
||||
segmentUtils,
|
||||
} from './segments'
|
||||
@ -109,6 +111,8 @@ import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
||||
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
|
||||
import { SegmentInputs } from 'lang/std/stdTypes'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { LabeledArg } from 'wasm-lib/kcl/bindings/LabeledArg'
|
||||
import { Literal } from 'wasm-lib/kcl/bindings/Literal'
|
||||
import { radToDeg } from 'three/src/math/MathUtils'
|
||||
import { getArtifactFromRange, codeRefFromRange } from 'lang/std/artifactGraph'
|
||||
|
||||
@ -1261,110 +1265,98 @@ export class SceneEntities {
|
||||
const groupOfDrafts = new Group()
|
||||
groupOfDrafts.name = 'circle-3-point-group'
|
||||
groupOfDrafts.position.copy(sketchOrigin)
|
||||
|
||||
// lee: I'm keeping this here as a developer gotchya:
|
||||
// Do not reorient your surfaces to the intersection plane. Your points are
|
||||
// already in 3D space, not 2D. If you intersect say XZ, you want the points
|
||||
// to continue to live at the 3D intersection point, not be rotated to end
|
||||
// up elsewhere!
|
||||
// groupOfDrafts.setRotationFromQuaternion(orientation)
|
||||
// If you use 3D points, do not rotate anything.
|
||||
// If you use 2D points (easier to deal with, generally do this!), then
|
||||
// rotate the group just like this! Remember to rotate other groups too!
|
||||
groupOfDrafts.setRotationFromQuaternion(orientation)
|
||||
this.scene.add(groupOfDrafts)
|
||||
|
||||
const DRAFT_POINT_RADIUS = 6
|
||||
// How large the points on the circle will render as
|
||||
const DRAFT_POINT_RADIUS = 10 // px
|
||||
|
||||
const createPoint = (center: Vector3): number => {
|
||||
// The target of our dragging
|
||||
let target: Object3D | undefined = undefined
|
||||
|
||||
// The KCL this will generate.
|
||||
const kclCircle3Point = parse(`circleThreePoint(
|
||||
p1 = [0.0, 0.0],
|
||||
p2 = [0.0, 0.0],
|
||||
p3 = [0.0, 0.0],
|
||||
)`)
|
||||
|
||||
const createPoint = (
|
||||
center: Vector3,
|
||||
opts?: { noInteraction?: boolean }
|
||||
): Mesh => {
|
||||
const geometry = new SphereGeometry(DRAFT_POINT_RADIUS)
|
||||
const color = getThemeColorForThreeJs(sceneInfra._theme)
|
||||
const material = new MeshBasicMaterial({ color })
|
||||
|
||||
const material = new MeshBasicMaterial({
|
||||
color: opts?.noInteraction
|
||||
? sceneInfra._theme === 'light'
|
||||
? new Color(color).multiplyScalar(0.15)
|
||||
: new Color(0x010101).multiplyScalar(2000)
|
||||
: color,
|
||||
})
|
||||
|
||||
const mesh = new Mesh(geometry, material)
|
||||
mesh.userData = { type: CIRCLE_3_POINT_DRAFT_POINT }
|
||||
mesh.userData = {
|
||||
type: opts?.noInteraction ? 'ghost' : CIRCLE_3_POINT_DRAFT_POINT,
|
||||
}
|
||||
mesh.renderOrder = 1000
|
||||
mesh.layers.set(SKETCH_LAYER)
|
||||
mesh.position.copy(center)
|
||||
mesh.scale.set(scale, scale, scale)
|
||||
mesh.renderOrder = 100
|
||||
|
||||
groupOfDrafts.add(mesh)
|
||||
|
||||
return mesh.id
|
||||
return mesh
|
||||
}
|
||||
|
||||
const circle3Point = (
|
||||
points: Vector2[]
|
||||
): undefined | { center: Vector3; radius: number } => {
|
||||
// A 3-point circle is undefined if it doesn't have 3 points :)
|
||||
if (points.length !== 3) return undefined
|
||||
|
||||
// y = (i/j)(x-h) + b
|
||||
// i and j variables for the slopes
|
||||
const i = [points[1].x - points[0].x, points[2].x - points[1].x]
|
||||
const j = [points[1].y - points[0].y, points[2].y - points[1].y]
|
||||
|
||||
// Our / threejs coordinate system affects this a lot. If you take this
|
||||
// code into a different code base, you may have to adjust a/b to being
|
||||
// -1/a/b, b/a, etc! In this case, a/-b did the trick.
|
||||
const m = [i[0] / -j[0], i[1] / -j[1]]
|
||||
|
||||
const h = [
|
||||
(points[0].x + points[1].x) / 2,
|
||||
(points[1].x + points[2].x) / 2,
|
||||
]
|
||||
const b = [
|
||||
(points[0].y + points[1].y) / 2,
|
||||
(points[1].y + points[2].y) / 2,
|
||||
]
|
||||
|
||||
// Algebraically derived
|
||||
const x = (-m[0] * h[0] + b[0] - b[1] + m[1] * h[1]) / (m[1] - m[0])
|
||||
const y = m[0] * (x - h[0]) + b[0]
|
||||
|
||||
const center = new Vector3(x, y, 0)
|
||||
const radius = Math.sqrt((points[1].x - x) ** 2 + (points[1].y - y) ** 2)
|
||||
|
||||
return {
|
||||
center,
|
||||
radius,
|
||||
}
|
||||
}
|
||||
|
||||
// TO BE SHORT LIVED: unused function to draw the circle and lines.
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line
|
||||
const createCircle3Point = (points: Vector2[]) => {
|
||||
const circleParams = circle3Point(points)
|
||||
|
||||
// A circle cannot be created for these points.
|
||||
if (!circleParams) return
|
||||
const createCircle3PointGraphic = async (
|
||||
points: Vector2[],
|
||||
center: Vector2,
|
||||
radius: number
|
||||
) => {
|
||||
if (
|
||||
Number.isNaN(radius) ||
|
||||
Number.isNaN(center.x) ||
|
||||
Number.isNaN(center.y)
|
||||
)
|
||||
return
|
||||
|
||||
const color = getThemeColorForThreeJs(sceneInfra._theme)
|
||||
const geometryCircle = createArcGeometry({
|
||||
center: [circleParams.center.x, circleParams.center.y],
|
||||
radius: circleParams.radius,
|
||||
startAngle: 0,
|
||||
endAngle: Math.PI * 2,
|
||||
ccw: true,
|
||||
isDashed: true,
|
||||
scale,
|
||||
const lineCircle = createCircleGeometry({
|
||||
center: [center.x, center.y],
|
||||
radius,
|
||||
color,
|
||||
isDashed: false,
|
||||
scale: 1,
|
||||
})
|
||||
const materialCircle = new MeshBasicMaterial({ color })
|
||||
lineCircle.userData = { type: CIRCLE_3_POINT_DRAFT_CIRCLE }
|
||||
lineCircle.layers.set(SKETCH_LAYER)
|
||||
// devnote: it's a mistake to use these with EllipseCurve :)
|
||||
// lineCircle.position.set(center.x, center.y, 0)
|
||||
// lineCircle.scale.set(scale, scale, scale)
|
||||
|
||||
if (groupCircle) groupOfDrafts.remove(groupCircle)
|
||||
groupCircle = new Group()
|
||||
groupCircle.renderOrder = 1
|
||||
groupCircle.add(lineCircle)
|
||||
|
||||
const meshCircle = new Mesh(geometryCircle, materialCircle)
|
||||
meshCircle.userData = { type: CIRCLE_3_POINT_DRAFT_CIRCLE }
|
||||
meshCircle.layers.set(SKETCH_LAYER)
|
||||
meshCircle.position.set(circleParams.center.x, circleParams.center.y, 0)
|
||||
meshCircle.scale.set(scale, scale, scale)
|
||||
groupCircle.add(meshCircle)
|
||||
const pointMesh = createPoint(new Vector3(center.x, center.y, 0), {
|
||||
noInteraction: true,
|
||||
})
|
||||
groupCircle.add(pointMesh)
|
||||
|
||||
const geometryPolyLine = new BufferGeometry().setFromPoints([
|
||||
...points,
|
||||
points[0],
|
||||
...points.map((p) => new Vector3(p.x, p.y, 0)),
|
||||
new Vector3(points[0].x, points[0].y, 0),
|
||||
])
|
||||
const materialPolyLine = new LineDashedMaterial({
|
||||
color,
|
||||
scale,
|
||||
scale: 1 / scale,
|
||||
dashSize: 6,
|
||||
gapSize: 6,
|
||||
})
|
||||
@ -1375,13 +1367,146 @@ export class SceneEntities {
|
||||
groupOfDrafts.add(groupCircle)
|
||||
}
|
||||
|
||||
// The target of our dragging
|
||||
let target: Object3D | undefined = undefined
|
||||
const insertCircle3PointKclIntoAstSnapshot = (
|
||||
points: Vector2[]
|
||||
): Program => {
|
||||
if (err(kclCircle3Point) || kclCircle3Point.program === null)
|
||||
return kclManager.ast
|
||||
if (kclCircle3Point.program.body[0].type !== 'ExpressionStatement')
|
||||
return kclManager.ast
|
||||
if (
|
||||
kclCircle3Point.program.body[0].expression.type !== 'CallExpressionKw'
|
||||
)
|
||||
return kclManager.ast
|
||||
|
||||
const arg = (x: LabeledArg): Literal[] | undefined => {
|
||||
if (
|
||||
'arg' in x &&
|
||||
'elements' in x.arg &&
|
||||
x.arg.type === 'ArrayExpression'
|
||||
) {
|
||||
if (x.arg.elements.every((x) => x.type === 'Literal')) {
|
||||
return x.arg.elements
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const kclCircle3PointArgs =
|
||||
kclCircle3Point.program.body[0].expression.arguments
|
||||
|
||||
const arg0 = arg(kclCircle3PointArgs[0])
|
||||
if (!arg0) return kclManager.ast
|
||||
arg0[0].value = points[0].x
|
||||
arg0[0].raw = points[0].x.toString()
|
||||
arg0[1].value = points[0].y
|
||||
arg0[1].raw = points[0].y.toString()
|
||||
|
||||
const arg1 = arg(kclCircle3PointArgs[1])
|
||||
if (!arg1) return kclManager.ast
|
||||
arg1[0].value = points[1].x
|
||||
arg1[0].raw = points[1].x.toString()
|
||||
arg1[1].value = points[1].y
|
||||
arg1[1].raw = points[1].y.toString()
|
||||
|
||||
const arg2 = arg(kclCircle3PointArgs[2])
|
||||
if (!arg2) return kclManager.ast
|
||||
arg2[0].value = points[2].x
|
||||
arg2[0].raw = points[2].x.toString()
|
||||
arg2[1].value = points[2].y
|
||||
arg2[1].raw = points[2].y.toString()
|
||||
|
||||
const astSnapshot = structuredClone(kclManager.ast)
|
||||
const startSketchOnASTNode = getNodeFromPath<VariableDeclaration>(
|
||||
astSnapshot,
|
||||
startSketchOnASTNodePath,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(startSketchOnASTNode)) return astSnapshot
|
||||
|
||||
// It's possible we're already dealing with a PipeExpression.
|
||||
// Modify the current one.
|
||||
if (
|
||||
startSketchOnASTNode.node.declaration.init.type === 'PipeExpression' &&
|
||||
startSketchOnASTNode.node.declaration.init.body[1].type ===
|
||||
'CallExpressionKw' &&
|
||||
startSketchOnASTNode.node.declaration.init.body.length >= 2
|
||||
) {
|
||||
startSketchOnASTNode.node.declaration.init.body[1].arguments =
|
||||
kclCircle3Point.program.body[0].expression.arguments
|
||||
} else {
|
||||
// Clone a new node based on the old, and replace the old with the new.
|
||||
const clonedStartSketchOnASTNode = structuredClone(startSketchOnASTNode)
|
||||
startSketchOnASTNode.node.declaration.init = createPipeExpression([
|
||||
clonedStartSketchOnASTNode.node.declaration.init,
|
||||
kclCircle3Point.program.body[0].expression,
|
||||
])
|
||||
}
|
||||
|
||||
// Return the `Program`
|
||||
return astSnapshot
|
||||
}
|
||||
|
||||
const updateCircle3Point = async (opts?: { execute?: true }) => {
|
||||
const points_ = Array.from(points.values())
|
||||
const circleParams = calculate_circle_from_3_points(
|
||||
points_[0].x,
|
||||
points_[0].y,
|
||||
points_[1].x,
|
||||
points_[1].y,
|
||||
points_[2].x,
|
||||
points_[2].y
|
||||
)
|
||||
|
||||
if (Number.isNaN(circleParams.radius)) return
|
||||
|
||||
await createCircle3PointGraphic(
|
||||
points_,
|
||||
new Vector2(circleParams.center_x, circleParams.center_y),
|
||||
circleParams.radius
|
||||
)
|
||||
const astWithNewCode = insertCircle3PointKclIntoAstSnapshot(points_)
|
||||
const codeAsString = recast(astWithNewCode)
|
||||
if (err(codeAsString)) return
|
||||
codeManager.updateCodeStateEditor(codeAsString)
|
||||
}
|
||||
|
||||
const cleanupFn = () => {
|
||||
this.scene.remove(groupOfDrafts)
|
||||
}
|
||||
|
||||
// The AST node we extracted earlier may already have a circleThreePoint!
|
||||
// Use the points in the AST as starting points.
|
||||
const astSnapshot = structuredClone(kclManager.ast)
|
||||
const maybeVariableDeclaration = getNodeFromPath<VariableDeclaration>(
|
||||
astSnapshot,
|
||||
startSketchOnASTNodePath,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(maybeVariableDeclaration))
|
||||
return () => {
|
||||
done()
|
||||
}
|
||||
|
||||
const maybeCallExpressionKw = maybeVariableDeclaration.node.declaration.init
|
||||
if (
|
||||
maybeCallExpressionKw.type === 'PipeExpression' &&
|
||||
maybeCallExpressionKw.body[1].type === 'CallExpressionKw' &&
|
||||
maybeCallExpressionKw.body[1]?.callee.name === 'circleThreePoint'
|
||||
) {
|
||||
maybeCallExpressionKw?.body[1].arguments
|
||||
.map(
|
||||
({ arg }: any) =>
|
||||
new Vector2(arg.elements[0].value, arg.elements[1].value)
|
||||
)
|
||||
.forEach((point: Vector2) => {
|
||||
const pointMesh = createPoint(new Vector3(point.x, point.y, 0))
|
||||
groupOfDrafts.add(pointMesh)
|
||||
points.set(pointMesh.id, point)
|
||||
})
|
||||
void updateCircle3Point()
|
||||
}
|
||||
|
||||
sceneInfra.setCallbacks({
|
||||
async onDrag(args) {
|
||||
const draftPointsIntersected = args.intersects.filter(
|
||||
@ -1397,8 +1522,18 @@ export class SceneEntities {
|
||||
// The user was off their mark! Missed the object to select.
|
||||
if (!target) return
|
||||
|
||||
target.position.copy(args.intersectionPoint.threeD)
|
||||
target.position.copy(
|
||||
new Vector3(
|
||||
args.intersectionPoint.twoD.x,
|
||||
args.intersectionPoint.twoD.y,
|
||||
0
|
||||
)
|
||||
)
|
||||
points.set(target.id, args.intersectionPoint.twoD)
|
||||
|
||||
if (points.size <= 2) return
|
||||
|
||||
await updateCircle3Point()
|
||||
},
|
||||
async onDragEnd(_args) {
|
||||
target = undefined
|
||||
@ -1407,45 +1542,19 @@ export class SceneEntities {
|
||||
if (points.size >= 3) return
|
||||
if (!args.intersectionPoint) return
|
||||
|
||||
const id = createPoint(args.intersectionPoint.threeD)
|
||||
points.set(id, args.intersectionPoint.twoD)
|
||||
|
||||
if (points.size < 2) return
|
||||
|
||||
// We've now got 3 points, let's create our circle!
|
||||
const astSnapshot = structuredClone(kclManager.ast)
|
||||
let nodeQueryResult
|
||||
nodeQueryResult = getNodeFromPath<VariableDeclaration>(
|
||||
astSnapshot,
|
||||
startSketchOnASTNodePath,
|
||||
'VariableDeclaration'
|
||||
const pointMesh = createPoint(
|
||||
new Vector3(
|
||||
args.intersectionPoint.twoD.x,
|
||||
args.intersectionPoint.twoD.y,
|
||||
0
|
||||
)
|
||||
)
|
||||
if (err(nodeQueryResult)) return Promise.reject(nodeQueryResult)
|
||||
const startSketchOnASTNode = nodeQueryResult
|
||||
groupOfDrafts.add(pointMesh)
|
||||
points.set(pointMesh.id, args.intersectionPoint.twoD)
|
||||
|
||||
const circleParams = circle3Point(Array.from(points.values()))
|
||||
if (points.size <= 2) return
|
||||
|
||||
if (!circleParams) return
|
||||
|
||||
const kclCircle3Point = parse(`circle({
|
||||
center = [${circleParams.center.x}, ${circleParams.center.y}],
|
||||
radius = ${circleParams.radius},
|
||||
}, %)`)
|
||||
|
||||
if (err(kclCircle3Point) || kclCircle3Point.program === null) return
|
||||
if (kclCircle3Point.program.body[0].type !== 'ExpressionStatement')
|
||||
return
|
||||
|
||||
const clonedStartSketchOnASTNode = structuredClone(startSketchOnASTNode)
|
||||
startSketchOnASTNode.node.declaration.init = createPipeExpression([
|
||||
clonedStartSketchOnASTNode.node.declaration.init,
|
||||
kclCircle3Point.program.body[0].expression,
|
||||
])
|
||||
|
||||
await kclManager.executeAstMock(astSnapshot)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(astSnapshot)
|
||||
|
||||
done()
|
||||
await updateCircle3Point()
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -9,6 +9,9 @@ import {
|
||||
ExtrudeGeometry,
|
||||
Group,
|
||||
LineCurve3,
|
||||
LineBasicMaterial,
|
||||
LineDashedMaterial,
|
||||
Line,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
NormalBufferAttributes,
|
||||
@ -1003,6 +1006,49 @@ export function createArcGeometry({
|
||||
return geo
|
||||
}
|
||||
|
||||
// (lee) The above is much more complex than necessary.
|
||||
// I've derived the new code from:
|
||||
// https://threejs.org/docs/#api/en/extras/curves/EllipseCurve
|
||||
// I'm not sure why it wasn't done like this in the first place?
|
||||
// I don't touch the code above because it may break something else.
|
||||
export function createCircleGeometry({
|
||||
center,
|
||||
radius,
|
||||
color,
|
||||
isDashed = false,
|
||||
scale = 1,
|
||||
}: {
|
||||
center: Coords2d
|
||||
radius: number
|
||||
color: number
|
||||
isDashed?: boolean
|
||||
scale?: number
|
||||
}): Line {
|
||||
const circle = new EllipseCurve(
|
||||
center[0],
|
||||
center[1],
|
||||
radius,
|
||||
radius,
|
||||
0,
|
||||
Math.PI * 2,
|
||||
true,
|
||||
scale
|
||||
)
|
||||
const points = circle.getPoints(75) // just enough points to not see edges.
|
||||
const geometry = new BufferGeometry().setFromPoints(points)
|
||||
const material = !isDashed
|
||||
? new LineBasicMaterial({ color })
|
||||
: new LineDashedMaterial({
|
||||
color,
|
||||
scale,
|
||||
dashSize: 6,
|
||||
gapSize: 6,
|
||||
})
|
||||
const line = new Line(geometry, material)
|
||||
line.computeLineDistances()
|
||||
return line
|
||||
}
|
||||
|
||||
export function dashedStraight(
|
||||
from: Coords2d,
|
||||
to: Coords2d,
|
||||
|
@ -22,6 +22,7 @@ export const CommandBar = () => {
|
||||
|
||||
// Close the command bar when navigating
|
||||
useEffect(() => {
|
||||
if (commandBarState.matches('Closed')) return
|
||||
commandBarSend({ type: 'Close' })
|
||||
}, [pathname])
|
||||
|
||||
|
@ -4,6 +4,8 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { Command } from 'lib/commandTypes'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { getActorNextEvents } from 'lib/utils'
|
||||
import { sortCommands } from 'lib/commandUtils'
|
||||
|
||||
function CommandComboBox({
|
||||
options,
|
||||
@ -18,8 +20,16 @@ function CommandComboBox({
|
||||
|
||||
const defaultOption =
|
||||
options.find((o) => 'isCurrent' in o && o.isCurrent) || null
|
||||
// sort disabled commands to the bottom
|
||||
const sortedOptions = options
|
||||
.map((command) => ({
|
||||
command,
|
||||
disabled: optionIsDisabled(command),
|
||||
}))
|
||||
.sort(sortCommands)
|
||||
.map(({ command }) => command)
|
||||
|
||||
const fuse = new Fuse(options, {
|
||||
const fuse = new Fuse(sortedOptions, {
|
||||
keys: ['displayName', 'name', 'description'],
|
||||
threshold: 0.3,
|
||||
ignoreLocation: true,
|
||||
@ -27,7 +37,7 @@ function CommandComboBox({
|
||||
|
||||
useEffect(() => {
|
||||
const results = fuse.search(query).map((result) => result.item)
|
||||
setFilteredOptions(query.length > 0 ? results : options)
|
||||
setFilteredOptions(query.length > 0 ? results : sortedOptions)
|
||||
}, [query])
|
||||
|
||||
function handleSelection(command: Command) {
|
||||
@ -73,7 +83,8 @@ function CommandComboBox({
|
||||
<Combobox.Option
|
||||
key={option.groupId + option.name + (option.displayName || '')}
|
||||
value={option}
|
||||
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90 ui-disabled:!text-chalkboard-50"
|
||||
disabled={optionIsDisabled(option)}
|
||||
>
|
||||
{'icon' in option && option.icon && (
|
||||
<CustomIcon name={option.icon} className="w-5 h-5" />
|
||||
@ -96,3 +107,11 @@ function CommandComboBox({
|
||||
}
|
||||
|
||||
export default CommandComboBox
|
||||
|
||||
function optionIsDisabled(option: Command): boolean {
|
||||
return (
|
||||
'machineActor' in option &&
|
||||
option.machineActor !== undefined &&
|
||||
!getActorNextEvents(option.machineActor.getSnapshot()).includes(option.name)
|
||||
)
|
||||
}
|
||||
|
@ -538,6 +538,16 @@ const CustomIconMap = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
helix: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.3796 6.35525C10.6758 5.64945 8.44129 5.27796 6.92519 5.64172C6.15726 5.82597 5.7318 6.05228 5.55779 6.21295C5.76304 6.32354 6.2288 6.43945 7.03653 6.43302C7.87009 6.42638 8.9975 6.29045 10.4229 5.9501L10.6551 6.92275C9.17724 7.27564 7.9725 7.42559 7.04449 7.43298C6.14216 7.44017 5.42343 7.31395 4.98579 7.03617C4.75792 6.89153 4.53857 6.65945 4.50435 6.32695C4.47054 5.99852 4.63374 5.72683 4.81912 5.53684C5.17998 5.16702 5.83926 4.87389 6.69188 4.66932C8.48928 4.23806 10.9508 4.68095 12.7623 5.43139C13.669 5.80697 14.4784 6.28567 14.9739 6.82869C15.2234 7.10197 15.4238 7.42493 15.4827 7.78937C15.5448 8.1741 15.4392 8.54567 15.1831 8.86785C14.9896 9.11133 14.6502 9.31092 14.327 9.47089C14.1575 9.55477 13.9707 9.63785 13.7736 9.71907C14.257 9.99254 14.6732 10.2984 14.9739 10.6279C15.2234 10.9011 15.4238 11.2241 15.4827 11.5885C15.5448 11.9733 15.4392 12.3448 15.1831 12.667C14.9896 12.9105 14.6502 13.1101 14.327 13.2701C14.1575 13.3539 13.9707 13.437 13.7735 13.5182C14.3755 13.8587 14.8991 14.2636 15.2067 14.7211L14.3767 15.2789C14.1912 15.0029 13.8109 14.6842 13.2483 14.3702C13.0112 14.2378 12.7496 14.1107 12.4694 13.9913C11.8027 14.2087 11.1417 14.3953 10.6642 14.5188L10.6552 14.5212L10.6551 14.5211C9.17724 14.874 7.9725 15.0239 7.04449 15.0313C6.14216 15.0385 5.42343 14.9123 4.98579 14.6345C4.75792 14.4899 4.53857 14.2578 4.50435 13.9253C4.47054 13.5969 4.63374 13.3252 4.81912 13.1352C5.17998 12.7653 5.83926 12.4722 6.69188 12.2677C8.12302 11.9243 9.96538 12.1368 11.5511 12.6039C11.5872 12.6145 11.6233 12.6253 11.6593 12.6363L10.0638 13.2745C8.93153 13.0645 7.80454 13.0291 6.92519 13.2401C6.15727 13.4243 5.73181 13.6506 5.5578 13.8113C5.76305 13.9219 6.2288 14.0378 7.03653 14.0313C7.8692 14.0247 8.99509 13.8891 10.4183 13.5495C10.5419 13.5175 10.678 13.4812 10.8233 13.4412C10.8184 13.4399 10.8134 13.4387 10.8085 13.4374L12.6 12.9L12.5922 12.8948C12.6584 12.8718 12.7243 12.8485 12.7894 12.825C13.2047 12.6754 13.5845 12.5217 13.8834 12.3738C14.2059 12.2142 14.359 12.0967 14.4003 12.0448C14.4964 11.9239 14.509 11.832 14.4955 11.748C14.4786 11.6437 14.4094 11.4927 14.2353 11.302C13.8963 10.9305 13.2766 10.536 12.4694 10.1921C11.8027 10.4096 11.1417 10.5962 10.6642 10.7197L10.6552 10.722L10.6551 10.7219C9.17724 11.0748 7.9725 11.2248 7.04449 11.2322C6.14216 11.2393 5.42343 11.1131 4.98579 10.8353C4.75792 10.6907 4.53857 10.4586 4.50435 10.1261C4.47054 9.79768 4.63374 9.526 4.81912 9.33601C5.17998 8.96618 5.83926 8.67306 6.69188 8.46848C8.14467 8.11991 10.0313 8.34242 11.6579 8.83682L10.0624 9.47503C8.9375 9.26666 7.80922 9.22878 6.92519 9.44089C6.15726 9.62514 5.7318 9.85144 5.55779 10.0121C5.76304 10.1227 6.2288 10.2386 7.03653 10.2322C7.86921 10.2255 8.9951 10.0899 10.4183 9.75035C10.542 9.71834 10.6781 9.68201 10.8235 9.64197L10.8072 9.63784L12.6 9.1L12.593 9.09536C12.659 9.0724 12.7245 9.04921 12.7894 9.02583C13.2047 8.87627 13.5845 8.72256 13.8834 8.57464C14.2059 8.41505 14.359 8.29757 14.4003 8.24564C14.4964 8.1247 14.509 8.0328 14.4955 7.94882C14.4786 7.84455 14.4094 7.69357 14.2353 7.50279C13.8839 7.11769 13.2307 6.7078 12.3796 6.35525ZM5.47539 9.95537C5.47546 9.95536 5.47623 9.95615 5.47745 9.95779C5.47592 9.9562 5.47531 9.95538 5.47539 9.95537ZM5.49369 10.0846C5.49289 10.0866 5.49232 10.0876 5.49223 10.0876C5.49215 10.0877 5.49255 10.0866 5.49369 10.0846ZM5.47539 13.7545C5.47546 13.7545 5.47623 13.7553 5.47745 13.757C5.47592 13.7554 5.47531 13.7546 5.47539 13.7545ZM5.49369 13.8838C5.49289 13.8858 5.49232 13.8868 5.49223 13.8868C5.49215 13.8868 5.49255 13.8858 5.49369 13.8838ZM5.47539 6.1562C5.47546 6.15619 5.47623 6.15698 5.47745 6.15862C5.47592 6.15704 5.47531 6.15622 5.47539 6.1562ZM5.49369 6.28544C5.49289 6.28746 5.49232 6.28848 5.49223 6.28848C5.49215 6.28849 5.49255 6.28748 5.49369 6.28544Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
hole: (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
|
@ -57,7 +57,9 @@ export const FileMachineProvider = ({
|
||||
useEffect(() => {
|
||||
markOnce('code/didLoadFile')
|
||||
async function fetchKclSamples() {
|
||||
setKclSamples(await getKclSamplesManifest())
|
||||
const manifest = await getKclSamplesManifest()
|
||||
const filteredFiles = manifest.filter((file) => !file.multipleFiles)
|
||||
setKclSamples(filteredFiles)
|
||||
}
|
||||
fetchKclSamples().catch(reportError)
|
||||
}, [])
|
||||
@ -324,7 +326,7 @@ export const FileMachineProvider = ({
|
||||
}
|
||||
},
|
||||
kclSamples.map((sample) => ({
|
||||
value: sample.file,
|
||||
value: sample.pathFromProjectDirectoryToFirstFile,
|
||||
name: sample.title,
|
||||
}))
|
||||
).filter(
|
||||
|
@ -148,6 +148,7 @@ function HelpMenuItem({
|
||||
return (
|
||||
<li className="p-0 m-0">
|
||||
{as === 'a' ? (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<a
|
||||
{...(props as React.ComponentProps<'a'>)}
|
||||
onClick={openExternalBrowserIfDesktop(
|
||||
|
@ -111,7 +111,7 @@ export const ModelingMachineProvider = ({
|
||||
auth,
|
||||
settings: {
|
||||
context: {
|
||||
app: { theme, enableSSAO },
|
||||
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
||||
modeling: {
|
||||
defaultUnit,
|
||||
cameraProjection,
|
||||
@ -121,6 +121,7 @@ export const ModelingMachineProvider = ({
|
||||
},
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
const previousAllowOrbitInSketchMode = useRef(allowOrbitInSketchMode.current)
|
||||
const navigate = useNavigate()
|
||||
const { context, send: fileMachineSend } = useFileContext()
|
||||
const { file } = useLoaderData() as IndexLoaderData
|
||||
@ -634,7 +635,8 @@ export const ModelingMachineProvider = ({
|
||||
input.plane
|
||||
)
|
||||
await kclManager.updateAst(modifiedAst, false)
|
||||
sceneInfra.camControls.enableRotate = false
|
||||
sceneInfra.camControls.enableRotate =
|
||||
sceneInfra.camControls._setting_allowOrbitInSketchMode
|
||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||
|
||||
await letEngineAnimateAndSyncCamAfter(
|
||||
@ -647,6 +649,7 @@ export const ModelingMachineProvider = ({
|
||||
zAxis: input.zAxis,
|
||||
yAxis: input.yAxis,
|
||||
origin: [0, 0, 0],
|
||||
animateTargetId: input.planeId,
|
||||
}
|
||||
}),
|
||||
'animate-to-sketch': fromPromise(
|
||||
@ -671,6 +674,7 @@ export const ModelingMachineProvider = ({
|
||||
origin: info.sketchDetails.origin.map(
|
||||
(a) => a / sceneInfra._baseUnitMultiplier
|
||||
) as [number, number, number],
|
||||
animateTargetId: info?.sketchDetails?.faceId || '',
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -1188,6 +1192,41 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
}, [engineCommandManager.engineConnection, modelingSend])
|
||||
|
||||
useEffect(() => {
|
||||
// Only trigger this if the state actually changes, if it stays the same do not reload the camera
|
||||
if (
|
||||
previousAllowOrbitInSketchMode.current === allowOrbitInSketchMode.current
|
||||
) {
|
||||
//no op
|
||||
previousAllowOrbitInSketchMode.current = allowOrbitInSketchMode.current
|
||||
return
|
||||
}
|
||||
const inSketchMode = modelingState.matches('Sketch')
|
||||
|
||||
// If you are in sketch mode and you disable the orbit, return back to the normal view to the target
|
||||
if (!allowOrbitInSketchMode.current) {
|
||||
const targetId = modelingState.context.sketchDetails?.animateTargetId
|
||||
if (inSketchMode && targetId) {
|
||||
letEngineAnimateAndSyncCamAfter(engineCommandManager, targetId)
|
||||
.then(() => {})
|
||||
.catch((e) => {
|
||||
console.error(
|
||||
'failed to sync engine and client scene after disabling allow orbit in sketch mode'
|
||||
)
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// While you are in sketch mode you should be able to control the enable rotate
|
||||
// Once you exit it goes back to normal
|
||||
if (inSketchMode) {
|
||||
sceneInfra.camControls.enableRotate = allowOrbitInSketchMode.current
|
||||
}
|
||||
|
||||
previousAllowOrbitInSketchMode.current = allowOrbitInSketchMode.current
|
||||
}, [allowOrbitInSketchMode])
|
||||
|
||||
// Allow using the delete key to delete solids
|
||||
useHotkeys(['backspace', 'delete', 'del'], () => {
|
||||
modelingSend({ type: 'Delete selection' })
|
||||
|
@ -18,6 +18,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div
|
||||
className="relative"
|
||||
onClick={(e) => {
|
||||
|
@ -137,6 +137,11 @@ export const SettingsAuthProviderBase = ({
|
||||
sceneInfra.theme = opposingTheme
|
||||
sceneEntitiesManager.updateSegmentBaseColor(opposingTheme)
|
||||
},
|
||||
setAllowOrbitInSketchMode: ({ context }) => {
|
||||
sceneInfra.camControls._setting_allowOrbitInSketchMode =
|
||||
context.app.allowOrbitInSketchMode.current
|
||||
// ModelingMachineProvider will do a use effect to trigger the camera engine sync
|
||||
},
|
||||
toastSuccess: ({ event }) => {
|
||||
if (!('data' in event)) return
|
||||
const eventParts = event.type.replace(/^set./, '').split('.') as [
|
||||
|
@ -313,6 +313,7 @@ export const Stream = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
ref={videoWrapperRef}
|
||||
className="absolute inset-0 z-0"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react'
|
||||
import { AnyStateMachine, Actor, StateFrom } from 'xstate'
|
||||
import { AnyStateMachine, Actor, StateFrom, EventFrom } from 'xstate'
|
||||
import { createMachineCommand } from '../lib/createMachineCommand'
|
||||
import { useCommandsContext } from './useCommandsContext'
|
||||
import { modelingMachine } from 'machines/modelingMachine'
|
||||
@ -15,7 +15,6 @@ import { useKclContext } from 'lang/KclProvider'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { useAppState } from 'AppState'
|
||||
import { getActorNextEvents } from 'lib/utils'
|
||||
|
||||
// This might not be necessary, AnyStateMachine from xstate is working
|
||||
export type AllMachines =
|
||||
@ -60,21 +59,21 @@ export default function useStateMachineCommands<
|
||||
overallState !== NetworkHealthState.Weak) ||
|
||||
isExecuting ||
|
||||
!isStreamReady
|
||||
const newCommands = getActorNextEvents(state)
|
||||
const newCommands = Object.keys(commandBarConfig || {})
|
||||
.filter((_) => !allCommandsRequireNetwork || !disableAllButtons)
|
||||
.filter((e) => !['done.', 'error.'].some((n) => e.includes(n)))
|
||||
.flatMap((type) =>
|
||||
createMachineCommand<T, S>({
|
||||
.flatMap((type) => {
|
||||
const typeWithProperType = type as EventFrom<T>['type']
|
||||
return createMachineCommand<T, S>({
|
||||
// The group is the owner machine's ID.
|
||||
groupId: machineId,
|
||||
type,
|
||||
type: typeWithProperType,
|
||||
state,
|
||||
send,
|
||||
actor,
|
||||
commandBarConfig,
|
||||
onCancel,
|
||||
})
|
||||
)
|
||||
})
|
||||
.filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls
|
||||
|
||||
commandBarSend({ type: 'Add commands', data: { commands: newCommands } })
|
||||
@ -85,5 +84,5 @@ export default function useStateMachineCommands<
|
||||
data: { commands: newCommands },
|
||||
})
|
||||
}
|
||||
}, [state, overallState, isExecuting, isStreamReady])
|
||||
}, [overallState, isExecuting, isStreamReady])
|
||||
}
|
||||
|
@ -1,79 +1,81 @@
|
||||
import { assertParse, initPromise, programMemoryInit } from './wasm'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
// These unit tests makes web requests to a public github repository.
|
||||
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs/promises'
|
||||
import child_process from 'node:child_process'
|
||||
|
||||
// The purpose of these tests is to act as a first line of defense
|
||||
// if something gets real screwy with our KCL ecosystem.
|
||||
// THESE TESTS ONLY RUN UNDER A NODEJS ENVIRONMENT. They DO NOT
|
||||
// test under our application.
|
||||
|
||||
const DIR_KCL_SAMPLES = 'kcl-samples'
|
||||
const URL_GIT_KCL_SAMPLES = 'https://github.com/KittyCAD/kcl-samples.git'
|
||||
|
||||
interface KclSampleFile {
|
||||
file: string
|
||||
pathFromProjectDirectoryToFirstFile: string
|
||||
title: string
|
||||
filename: string
|
||||
description: string
|
||||
}
|
||||
|
||||
try {
|
||||
// @ts-expect-error
|
||||
await fs.rm(DIR_KCL_SAMPLES, { recursive: true })
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
child_process.spawnSync('git', ['clone', URL_GIT_KCL_SAMPLES, DIR_KCL_SAMPLES])
|
||||
|
||||
// @ts-expect-error
|
||||
let files = await fs.readdir(DIR_KCL_SAMPLES)
|
||||
// @ts-expect-error
|
||||
const manifestJsonStr = await fs.readFile(
|
||||
path.resolve(DIR_KCL_SAMPLES, 'manifest.json'),
|
||||
'utf-8'
|
||||
)
|
||||
const manifest = JSON.parse(manifestJsonStr)
|
||||
|
||||
process.chdir(DIR_KCL_SAMPLES)
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
// Only used to actually fetch an older version of KCL code that will break in the parser.
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
async function getBrokenSampleCodeForLocalTesting() {
|
||||
const result = await fetch(
|
||||
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/5ccd04a1773ebdbfd02684057917ce5dbe0eaab3/80-20-rail.kcl'
|
||||
)
|
||||
const text = await result.text()
|
||||
return text
|
||||
}
|
||||
afterAll(async () => {
|
||||
try {
|
||||
process.chdir('..')
|
||||
await fs.rm(DIR_KCL_SAMPLES, { recursive: true })
|
||||
} catch (e) {}
|
||||
})
|
||||
|
||||
async function getKclSampleCodeFromGithub(file: string): Promise<string> {
|
||||
const result = await fetch(
|
||||
`https://raw.githubusercontent.com/KittyCAD/kcl-samples/refs/heads/main/${file}/${file}.kcl`
|
||||
)
|
||||
const text = await result.text()
|
||||
return text
|
||||
}
|
||||
afterEach(() => {
|
||||
process.chdir('..')
|
||||
})
|
||||
|
||||
async function getFileNamesFromManifestJSON(): Promise<KclSampleFile[]> {
|
||||
const result = await fetch(
|
||||
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/refs/heads/main/manifest.json'
|
||||
)
|
||||
const json = await result.json()
|
||||
json.forEach((file: KclSampleFile) => {
|
||||
const filenameWithoutExtension = file.file.split('.')[0]
|
||||
file.filename = filenameWithoutExtension
|
||||
})
|
||||
return json
|
||||
}
|
||||
|
||||
// Value to use across all tests!
|
||||
let files: KclSampleFile[] = []
|
||||
|
||||
describe('Test KCL Samples from public Github repository', () => {
|
||||
describe('When parsing source code', () => {
|
||||
// THIS RUNS ACROSS OTHER TESTS!
|
||||
it('should fetch files', async () => {
|
||||
files = await getFileNamesFromManifestJSON()
|
||||
})
|
||||
// Run through all of the files in the manifest json. This will allow us to be automatically updated
|
||||
// with the latest changes in github. We won't be hard coding the filenames
|
||||
files.forEach((file: KclSampleFile) => {
|
||||
it(`should parse ${file.filename} without errors`, async () => {
|
||||
const code = await getKclSampleCodeFromGithub(file.filename)
|
||||
assertParse(code)
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when performing enginelessExecutor', () => {
|
||||
it(
|
||||
'should run through all the files',
|
||||
async () => {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file: KclSampleFile = files[i]
|
||||
const code = await getKclSampleCodeFromGithub(file.filename)
|
||||
// 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', () => {
|
||||
manifest.forEach((file: KclSampleFile) => {
|
||||
// @ts-expect-error
|
||||
it.sequential(
|
||||
`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 ast = assertParse(code)
|
||||
await enginelessExecutor(ast, programMemoryInit())
|
||||
}
|
||||
},
|
||||
files.length * 1000
|
||||
)
|
||||
},
|
||||
files.length * 1000
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -705,7 +705,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
segIds: [],
|
||||
id: expect.any(String),
|
||||
planeId: 'UUID-1',
|
||||
sweepId: '',
|
||||
sweepId: undefined,
|
||||
codeRef: {
|
||||
pathToNode: [['body', '']],
|
||||
range: [37, 64, true],
|
||||
@ -743,7 +743,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'segment',
|
||||
id: expect.any(String),
|
||||
pathId: expect.any(String),
|
||||
surfaceId: '',
|
||||
surfaceId: undefined,
|
||||
edgeIds: [],
|
||||
codeRef: {
|
||||
range: [70, 86, true],
|
||||
@ -770,7 +770,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
id: expect.any(String),
|
||||
consumedEdgeId: expect.any(String),
|
||||
edgeIds: [],
|
||||
surfaceId: '',
|
||||
surfaceId: undefined,
|
||||
codeRef: {
|
||||
range: [260, 299, true],
|
||||
pathToNode: [['body', '']],
|
||||
|
@ -37,7 +37,7 @@ export interface PathArtifact extends BaseArtifact {
|
||||
type: 'path'
|
||||
planeId: ArtifactId
|
||||
segIds: Array<ArtifactId>
|
||||
sweepId: ArtifactId
|
||||
sweepId?: ArtifactId
|
||||
solid2dId?: ArtifactId
|
||||
codeRef: CodeRef
|
||||
}
|
||||
@ -60,7 +60,7 @@ export interface PathArtifactRich extends BaseArtifact {
|
||||
export interface SegmentArtifact extends BaseArtifact {
|
||||
type: 'segment'
|
||||
pathId: ArtifactId
|
||||
surfaceId: ArtifactId
|
||||
surfaceId?: ArtifactId
|
||||
edgeIds: Array<ArtifactId>
|
||||
edgeCutId?: ArtifactId
|
||||
codeRef: CodeRef
|
||||
@ -68,7 +68,7 @@ export interface SegmentArtifact extends BaseArtifact {
|
||||
interface SegmentArtifactRich extends BaseArtifact {
|
||||
type: 'segment'
|
||||
path: PathArtifact
|
||||
surf: WallArtifact
|
||||
surf?: WallArtifact
|
||||
edges: Array<SweepEdge>
|
||||
edgeCut?: EdgeCut
|
||||
codeRef: CodeRef
|
||||
@ -120,7 +120,7 @@ interface EdgeCut extends BaseArtifact {
|
||||
subType: 'fillet' | 'chamfer'
|
||||
consumedEdgeId: ArtifactId
|
||||
edgeIds: Array<ArtifactId>
|
||||
surfaceId: ArtifactId
|
||||
surfaceId?: ArtifactId
|
||||
codeRef: CodeRef
|
||||
}
|
||||
|
||||
@ -308,7 +308,7 @@ export function getArtifactsToUpdate({
|
||||
id,
|
||||
segIds: [],
|
||||
planeId: currentPlaneId,
|
||||
sweepId: '',
|
||||
sweepId: undefined,
|
||||
codeRef: { range, pathToNode },
|
||||
},
|
||||
})
|
||||
@ -343,7 +343,7 @@ export function getArtifactsToUpdate({
|
||||
type: 'segment',
|
||||
id,
|
||||
pathId,
|
||||
surfaceId: '',
|
||||
surfaceId: undefined,
|
||||
edgeIds: [],
|
||||
codeRef: { range, pathToNode },
|
||||
},
|
||||
@ -450,7 +450,8 @@ export function getArtifactsToUpdate({
|
||||
id: face_id,
|
||||
segId: curve_id,
|
||||
edgeCutEdgeIds: [],
|
||||
sweepId: path.sweepId,
|
||||
// TODO: Add explicit check for sweepId. Should never use ''
|
||||
sweepId: path.sweepId ?? '',
|
||||
pathIds: [],
|
||||
},
|
||||
})
|
||||
@ -458,15 +459,17 @@ export function getArtifactsToUpdate({
|
||||
id: curve_id,
|
||||
artifact: { ...seg, surfaceId: face_id },
|
||||
})
|
||||
const sweep = getArtifact(path.sweepId)
|
||||
if (sweep?.type === 'sweep') {
|
||||
returnArr.push({
|
||||
id: path.sweepId,
|
||||
artifact: {
|
||||
...sweep,
|
||||
surfaceIds: [face_id],
|
||||
},
|
||||
})
|
||||
if (path.sweepId) {
|
||||
const sweep = getArtifact(path.sweepId)
|
||||
if (sweep?.type === 'sweep') {
|
||||
returnArr.push({
|
||||
id: path.sweepId,
|
||||
artifact: {
|
||||
...sweep,
|
||||
surfaceIds: [face_id],
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -483,19 +486,22 @@ export function getArtifactsToUpdate({
|
||||
id: face_id,
|
||||
subType: cap === 'bottom' ? 'start' : 'end',
|
||||
edgeCutEdgeIds: [],
|
||||
sweepId: path.sweepId,
|
||||
// TODO: Add explicit check for sweepId. Should never use ''
|
||||
sweepId: path.sweepId ?? '',
|
||||
pathIds: [],
|
||||
},
|
||||
})
|
||||
const sweep = getArtifact(path.sweepId)
|
||||
if (sweep?.type !== 'sweep') return
|
||||
returnArr.push({
|
||||
id: path.sweepId,
|
||||
artifact: {
|
||||
...sweep,
|
||||
surfaceIds: [face_id],
|
||||
},
|
||||
})
|
||||
if (path.sweepId) {
|
||||
const sweep = getArtifact(path.sweepId)
|
||||
if (sweep?.type !== 'sweep') return
|
||||
returnArr.push({
|
||||
id: path.sweepId,
|
||||
artifact: {
|
||||
...sweep,
|
||||
surfaceIds: [face_id],
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -533,7 +539,8 @@ export function getArtifactsToUpdate({
|
||||
? 'adjacent'
|
||||
: 'opposite',
|
||||
segId: cmd.edge_id,
|
||||
sweepId: path.sweepId,
|
||||
// TODO: Add explicit check for sweepId. Should never use ''
|
||||
sweepId: path.sweepId ?? '',
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -544,7 +551,7 @@ export function getArtifactsToUpdate({
|
||||
},
|
||||
},
|
||||
{
|
||||
id: path.sweepId,
|
||||
id: sweep.id,
|
||||
artifact: {
|
||||
...sweep,
|
||||
edgeIds: [response.data.modeling_response.data.edge],
|
||||
@ -560,7 +567,7 @@ export function getArtifactsToUpdate({
|
||||
subType: cmd.cut_type,
|
||||
consumedEdgeId: cmd.edge_id,
|
||||
edgeIds: [],
|
||||
surfaceId: '',
|
||||
surfaceId: undefined,
|
||||
codeRef: { range, pathToNode },
|
||||
},
|
||||
})
|
||||
@ -722,10 +729,12 @@ export function expandSegment(
|
||||
{ key: segment.pathId, types: ['path'] },
|
||||
artifactGraph
|
||||
)
|
||||
const surf = getArtifactOfTypes(
|
||||
{ key: segment.surfaceId, types: ['wall'] },
|
||||
artifactGraph
|
||||
)
|
||||
const surf = segment.surfaceId
|
||||
? getArtifactOfTypes(
|
||||
{ key: segment.surfaceId, types: ['wall'] },
|
||||
artifactGraph
|
||||
)
|
||||
: undefined
|
||||
const edges = getArtifactsOfTypes(
|
||||
{ keys: segment.edgeIds, types: ['sweepEdge'] },
|
||||
artifactGraph
|
||||
@ -842,6 +851,7 @@ export function getSweepFromSuspectedSweepSurface(
|
||||
artifactGraph
|
||||
)
|
||||
if (err(path)) return path
|
||||
if (!path.sweepId) return new Error('Path does not have a sweepId')
|
||||
return getArtifactOfTypes(
|
||||
{ key: path.sweepId, types: ['sweep'] },
|
||||
artifactGraph
|
||||
@ -859,6 +869,7 @@ export function getSweepFromSuspectedPath(
|
||||
): SweepArtifact | Error {
|
||||
const path = getArtifactOfTypes({ key: id, types: ['path'] }, artifactGraph)
|
||||
if (err(path)) return path
|
||||
if (!path.sweepId) return new Error('Path does not have a sweepId')
|
||||
return getArtifactOfTypes(
|
||||
{ key: path.sweepId, types: ['sweep'] },
|
||||
artifactGraph
|
||||
|
@ -1,5 +1,21 @@
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
|
||||
// Polyfill window.electron fs functions as needed when in a nodejs context
|
||||
// (INTENDED FOR VITEST SHINANGANS.)
|
||||
if (process.env.NODE_ENV === 'test' && process.env.VITEST) {
|
||||
const fs = require('node:fs/promises')
|
||||
const path = require('node:path')
|
||||
Object.assign(window, {
|
||||
electron: {
|
||||
readFile: fs.readFile,
|
||||
stat: fs.stat,
|
||||
readdir: fs.readdir,
|
||||
path,
|
||||
process: {},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// FileSystemManager is a class that provides a way to read files from the local file system.
|
||||
/// It assumes that you are in a project since it is solely used by the std lib
|
||||
/// when executing code.
|
||||
@ -19,13 +35,9 @@ class FileSystemManager {
|
||||
}
|
||||
|
||||
async readFile(path: string): Promise<Uint8Array> {
|
||||
// Using local file system only works from desktop.
|
||||
if (!isDesktop()) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
'This function can only be called from the desktop application'
|
||||
)
|
||||
)
|
||||
// Using local file system only works from desktop and nodejs
|
||||
if (!window?.electron?.readFile) {
|
||||
return Promise.reject(new Error('No polyfill found for this function'))
|
||||
}
|
||||
|
||||
return this.join(this.dir, path).then((filePath) => {
|
||||
@ -35,12 +47,8 @@ class FileSystemManager {
|
||||
|
||||
async exists(path: string): Promise<boolean | void> {
|
||||
// Using local file system only works from desktop.
|
||||
if (!isDesktop()) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
'This function can only be called from the desktop application'
|
||||
)
|
||||
)
|
||||
if (!window?.electron?.stat) {
|
||||
return Promise.reject(new Error('No polyfill found for this function'))
|
||||
}
|
||||
|
||||
return this.join(this.dir, path).then(async (file) => {
|
||||
@ -57,12 +65,8 @@ class FileSystemManager {
|
||||
|
||||
async getAllFiles(path: string): Promise<string[] | void> {
|
||||
// Using local file system only works from desktop.
|
||||
if (!isDesktop()) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
'This function can only be called from the desktop application'
|
||||
)
|
||||
)
|
||||
if (!window?.electron?.readdir) {
|
||||
return Promise.reject(new Error('No polyfill found for this function'))
|
||||
}
|
||||
|
||||
return this.join(this.dir, path).then((filepath) => {
|
||||
|
@ -56,10 +56,13 @@ export type ModelingCommandSchema = {
|
||||
edge: Selections
|
||||
}
|
||||
Fillet: {
|
||||
// todo
|
||||
selection: Selections
|
||||
radius: KclCommandValue
|
||||
}
|
||||
Chamfer: {
|
||||
selection: Selections
|
||||
length: KclCommandValue
|
||||
}
|
||||
'Offset plane': {
|
||||
plane: Selections
|
||||
distance: KclCommandValue
|
||||
@ -326,7 +329,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
Loft: {
|
||||
description: 'Create a 3D body by blending between two or more sketches',
|
||||
icon: 'loft',
|
||||
needsReview: true,
|
||||
needsReview: false,
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
@ -429,7 +432,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
},
|
||||
Fillet: {
|
||||
description: 'Fillet edge',
|
||||
icon: 'fillet',
|
||||
icon: 'fillet3d',
|
||||
status: 'development',
|
||||
needsReview: true,
|
||||
args: {
|
||||
@ -449,6 +452,28 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
},
|
||||
},
|
||||
},
|
||||
Chamfer: {
|
||||
description: 'Chamfer edge',
|
||||
icon: 'chamfer3d',
|
||||
status: 'development',
|
||||
needsReview: true,
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
||||
multiple: true,
|
||||
required: true,
|
||||
skip: false,
|
||||
warningMessage:
|
||||
'Chamfers cannot touch other chamfers yet. This is under development.',
|
||||
},
|
||||
length: {
|
||||
inputType: 'kcl',
|
||||
defaultValue: KCL_DEFAULT_LENGTH,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
'Constrain length': {
|
||||
description: 'Constrain the length of one or more segments.',
|
||||
icon: 'dimension',
|
||||
|
@ -63,12 +63,11 @@ export const projectsCommandBarConfig: StateMachineCommandSetConfig<
|
||||
name: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
options: [],
|
||||
optionsFromContext: (context) =>
|
||||
context.projects.map((p) => ({
|
||||
options: (_, context) =>
|
||||
context?.projects.map((p) => ({
|
||||
name: p.name!,
|
||||
value: p.name!,
|
||||
})),
|
||||
})) || [],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -80,12 +79,11 @@ export const projectsCommandBarConfig: StateMachineCommandSetConfig<
|
||||
oldName: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
options: [],
|
||||
optionsFromContext: (context) =>
|
||||
context.projects.map((p) => ({
|
||||
options: (_, context) =>
|
||||
context?.projects.map((p) => ({
|
||||
name: p.name!,
|
||||
value: p.name!,
|
||||
})),
|
||||
})) || [],
|
||||
},
|
||||
newName: {
|
||||
inputType: 'string',
|
||||
|
@ -76,6 +76,7 @@ export type Command<
|
||||
| ((
|
||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> } // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
) => string | ReactNode)
|
||||
machineActor?: Actor<T>
|
||||
onSubmit: (data?: CommandSchema) => void
|
||||
onCancel?: () => void
|
||||
args?: {
|
||||
@ -95,7 +96,7 @@ export type CommandConfig<
|
||||
Command<T, CommandName, CommandSchema>,
|
||||
'name' | 'groupId' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
|
||||
> & {
|
||||
needsReview?: true
|
||||
needsReview?: boolean
|
||||
status?: 'active' | 'development' | 'inactive'
|
||||
args?: {
|
||||
[ArgName in keyof CommandSchema]: CommandArgumentConfig<
|
||||
|
49
src/lib/commandUtils.test.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { CommandWithDisabledState, sortCommands } from './commandUtils'
|
||||
|
||||
function commandWithDisabled(
|
||||
name: string,
|
||||
disabled: boolean,
|
||||
groupId = 'modeling'
|
||||
): CommandWithDisabledState {
|
||||
return {
|
||||
command: {
|
||||
name,
|
||||
groupId,
|
||||
needsReview: false,
|
||||
onSubmit: () => {},
|
||||
},
|
||||
disabled,
|
||||
}
|
||||
}
|
||||
|
||||
describe('Command sorting', () => {
|
||||
it(`Puts modeling commands first`, () => {
|
||||
const initial = [
|
||||
commandWithDisabled('a', false, 'settings'),
|
||||
commandWithDisabled('b', false, 'modeling'),
|
||||
commandWithDisabled('c', false, 'settings'),
|
||||
]
|
||||
const sorted = initial.sort(sortCommands)
|
||||
expect(sorted[0].command.groupId).toBe('modeling')
|
||||
})
|
||||
|
||||
it(`Puts disabled commands last`, () => {
|
||||
const initial = [
|
||||
commandWithDisabled('a', true, 'modeling'),
|
||||
commandWithDisabled('z', false, 'modeling'),
|
||||
commandWithDisabled('a', false, 'settings'),
|
||||
]
|
||||
const sorted = initial.sort(sortCommands)
|
||||
expect(sorted[sorted.length - 1].disabled).toBe(true)
|
||||
})
|
||||
|
||||
it(`Puts settings commands second to last`, () => {
|
||||
const initial = [
|
||||
commandWithDisabled('a', true, 'modeling'),
|
||||
commandWithDisabled('z', false, 'modeling'),
|
||||
commandWithDisabled('a', false, 'settings'),
|
||||
]
|
||||
const sorted = initial.sort(sortCommands)
|
||||
expect(sorted[1].command.groupId).toBe('settings')
|
||||
})
|
||||
})
|
@ -2,6 +2,9 @@
|
||||
// That object also contains some metadata about what to do with the KCL expression,
|
||||
// such as whether we need to create a new variable for it.
|
||||
// This function extracts the value field from those arg payloads and returns
|
||||
|
||||
import { Command } from './commandTypes'
|
||||
|
||||
// The arg object with all its field as natural values that the command to be executed will expect.
|
||||
export function getCommandArgumentKclValuesOnly(args: Record<string, unknown>) {
|
||||
return Object.fromEntries(
|
||||
@ -13,3 +16,42 @@ export function getCommandArgumentKclValuesOnly(args: Record<string, unknown>) {
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export interface CommandWithDisabledState {
|
||||
command: Command
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting logic for commands in the command combo box.
|
||||
*/
|
||||
export function sortCommands(
|
||||
a: CommandWithDisabledState,
|
||||
b: CommandWithDisabledState
|
||||
) {
|
||||
// Disabled commands should be at the bottom
|
||||
if (a.disabled && !b.disabled) {
|
||||
return 1
|
||||
}
|
||||
if (b.disabled && !a.disabled) {
|
||||
return -1
|
||||
}
|
||||
// Settings commands should be next-to-last
|
||||
if (a.command.groupId === 'settings' && b.command.groupId !== 'settings') {
|
||||
return 1
|
||||
}
|
||||
if (b.command.groupId === 'settings' && a.command.groupId !== 'settings') {
|
||||
return -1
|
||||
}
|
||||
// Modeling commands should be first
|
||||
if (a.command.groupId === 'modeling' && b.command.groupId !== 'modeling') {
|
||||
return -1
|
||||
}
|
||||
if (b.command.groupId === 'modeling' && a.command.groupId !== 'modeling') {
|
||||
return 1
|
||||
}
|
||||
// Sort alphabetically
|
||||
return (a.command.displayName || a.command.name).localeCompare(
|
||||
b.command.displayName || b.command.name
|
||||
)
|
||||
}
|
||||
|
@ -96,6 +96,7 @@ export function createMachineCommand<
|
||||
icon,
|
||||
description: commandConfig.description,
|
||||
needsReview: commandConfig.needsReview || false,
|
||||
machineActor: actor,
|
||||
onSubmit: (data?: S[typeof type]) => {
|
||||
if (data !== undefined && data !== null) {
|
||||
send({ type, data })
|
||||
|
@ -3,6 +3,8 @@ import { isDesktop } from './isDesktop'
|
||||
|
||||
export type KclSamplesManifestItem = {
|
||||
file: string
|
||||
pathFromProjectDirectoryToFirstFile: string
|
||||
multipleFiles: boolean
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
@ -49,20 +49,30 @@ export function kclCommands(
|
||||
if (!data?.sample) {
|
||||
return
|
||||
}
|
||||
const pathParts = data.sample.split('/')
|
||||
const projectPathPart = pathParts[0]
|
||||
const primaryKclFile = pathParts[1]
|
||||
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
||||
data.sample.replace(FILE_EXT, '')
|
||||
)}/${encodeURIComponent(data.sample)}`
|
||||
projectPathPart
|
||||
)}/${encodeURIComponent(primaryKclFile)}`
|
||||
const sampleSettingsFileUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
||||
data.sample.replace(FILE_EXT, '')
|
||||
projectPathPart
|
||||
)}/${PROJECT_SETTINGS_FILE_NAME}`
|
||||
|
||||
Promise.all([fetch(sampleCodeUrl), fetch(sampleSettingsFileUrl)])
|
||||
Promise.allSettled([fetch(sampleCodeUrl), fetch(sampleSettingsFileUrl)])
|
||||
.then((results) => {
|
||||
const a =
|
||||
'value' in results[0] ? results[0].value : results[0].reason
|
||||
const b =
|
||||
'value' in results[1] ? results[1].value : results[1].reason
|
||||
return [a, b]
|
||||
})
|
||||
.then(
|
||||
async ([
|
||||
codeResponse,
|
||||
settingsResponse,
|
||||
]): Promise<OnSubmitProps> => {
|
||||
if (!(codeResponse.ok && settingsResponse.ok)) {
|
||||
if (!codeResponse.ok) {
|
||||
console.error(
|
||||
'Failed to fetch sample code:',
|
||||
codeResponse.statusText
|
||||
@ -70,20 +80,24 @@ export function kclCommands(
|
||||
return Promise.reject(new Error('Failed to fetch sample code'))
|
||||
}
|
||||
const code = await codeResponse.text()
|
||||
const parsedProjectSettings = parseProjectSettings(
|
||||
await settingsResponse.text()
|
||||
)
|
||||
|
||||
// It's possible that a sample doesn't have a project.toml
|
||||
// associated with it.
|
||||
let projectSettingsPayload: ReturnType<
|
||||
typeof projectConfigurationToSettingsPayload
|
||||
> = {}
|
||||
if (!err(parsedProjectSettings)) {
|
||||
projectSettingsPayload = projectConfigurationToSettingsPayload(
|
||||
parsedProjectSettings
|
||||
if (settingsResponse.ok) {
|
||||
const parsedProjectSettings = parseProjectSettings(
|
||||
await settingsResponse.text()
|
||||
)
|
||||
if (!err(parsedProjectSettings)) {
|
||||
projectSettingsPayload =
|
||||
projectConfigurationToSettingsPayload(parsedProjectSettings)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sampleName: data.sample,
|
||||
sampleName: data.sample.split('/')[0] + FILE_EXT,
|
||||
code,
|
||||
method: data.method,
|
||||
sampleUnits:
|
||||
|
@ -19,6 +19,10 @@ const stdLibMap: Record<string, StdLibCallInfo> = {
|
||||
label: 'Fillet',
|
||||
icon: 'fillet3d',
|
||||
},
|
||||
helix: {
|
||||
label: 'Helix',
|
||||
icon: 'helix',
|
||||
},
|
||||
hole: {
|
||||
label: 'Hole',
|
||||
icon: 'hole',
|
||||
|
@ -137,7 +137,7 @@ See later source ranges for more context. about the sweep`,
|
||||
{ key: artifact.pathId, types: ['path'] },
|
||||
artifactGraph
|
||||
)
|
||||
if (!err(path)) {
|
||||
if (!err(path) && path.sweepId) {
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: path.sweepId, types: ['sweep'] },
|
||||
artifactGraph
|
||||
|
@ -670,6 +670,7 @@ export function codeToIdSelections(
|
||||
}
|
||||
}
|
||||
if (type === 'extrude-wall' && entry.artifact.type === 'segment') {
|
||||
if (!entry.artifact.surfaceId) return
|
||||
const wall = engineCommandManager.artifactGraph.get(
|
||||
entry.artifact.surfaceId
|
||||
)
|
||||
@ -714,6 +715,7 @@ export function codeToIdSelections(
|
||||
(type === 'end-cap' || type === 'start-cap') &&
|
||||
entry.artifact.type === 'path'
|
||||
) {
|
||||
if (!entry.artifact.sweepId) return
|
||||
const extrusion = getArtifactOfTypes(
|
||||
{
|
||||
key: entry.artifact.sweepId,
|
||||
|
@ -190,6 +190,14 @@ export function createSettings() {
|
||||
inputType: 'boolean',
|
||||
},
|
||||
}),
|
||||
allowOrbitInSketchMode: new Setting<boolean>({
|
||||
defaultValue: false,
|
||||
description: 'Toggle free camera while in sketch mode',
|
||||
validate: (v) => typeof v === 'boolean',
|
||||
commandConfig: {
|
||||
inputType: 'boolean',
|
||||
},
|
||||
}),
|
||||
onboardingStatus: new Setting<OnboardingStatus>({
|
||||
defaultValue: '',
|
||||
// TODO: this could be better but we don't have a TS side real enum
|
||||
|
@ -41,6 +41,8 @@ export function configurationToSettingsPayload(
|
||||
onboardingStatus: configuration?.settings?.app?.onboarding_status,
|
||||
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
|
||||
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
||||
allowOrbitInSketchMode:
|
||||
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||
projectDirectory: configuration?.settings?.project?.directory,
|
||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||
},
|
||||
@ -80,6 +82,8 @@ export function projectConfigurationToSettingsPayload(
|
||||
onboardingStatus: configuration?.settings?.app?.onboarding_status,
|
||||
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
|
||||
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
||||
allowOrbitInSketchMode:
|
||||
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||
},
|
||||
modeling: {
|
||||
|
@ -173,10 +173,14 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/fillet' }],
|
||||
},
|
||||
{
|
||||
id: 'chamfer',
|
||||
onClick: () => console.error('Chamfer not yet implemented'),
|
||||
id: 'chamfer3d',
|
||||
onClick: ({ commandBarSend }) =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Chamfer', groupId: 'modeling' },
|
||||
}),
|
||||
icon: 'chamfer3d',
|
||||
status: 'kcl-only',
|
||||
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
|
||||
title: 'Chamfer',
|
||||
hotkey: 'C',
|
||||
description: 'Bevel the edges of a 3D solid.',
|
||||
@ -205,6 +209,15 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
description: 'Create a hole in a 3D solid.',
|
||||
links: [],
|
||||
},
|
||||
{
|
||||
id: 'helix',
|
||||
onClick: () => console.error('Helix not yet implemented'),
|
||||
icon: 'helix',
|
||||
status: 'kcl-only',
|
||||
title: 'Helix',
|
||||
description: 'Create a helix or spiral in 3D about an axis.',
|
||||
links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/helix' }],
|
||||
},
|
||||
'break',
|
||||
[
|
||||
{
|
||||
@ -447,18 +460,16 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
disabled: (state) =>
|
||||
state.matches('Sketch no face') ||
|
||||
(!canRectangleOrCircleTool(state.context) &&
|
||||
!state.matches({ Sketch: 'Circle tool' })),
|
||||
isActive: (state) => state.matches({ Sketch: 'Circle tool' }),
|
||||
!state.matches({ Sketch: 'Circle tool' }) &&
|
||||
!state.matches({ Sketch: 'circle3PointToolSelect' })),
|
||||
isActive: (state) =>
|
||||
state.matches({ Sketch: 'Circle tool' }) ||
|
||||
state.matches({ Sketch: 'circle3PointToolSelect' }),
|
||||
hotkey: (state) =>
|
||||
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
|
||||
showTitle: false,
|
||||
description: 'Start drawing a circle from its center',
|
||||
links: [
|
||||
{
|
||||
label: 'GitHub issue',
|
||||
url: 'https://github.com/KittyCAD/modeling-app/issues/1501',
|
||||
},
|
||||
],
|
||||
links: [],
|
||||
},
|
||||
{
|
||||
id: 'circle-three-points',
|
||||
@ -475,7 +486,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
}),
|
||||
icon: 'circle',
|
||||
status: 'available',
|
||||
title: 'Three-point circle',
|
||||
title: '3-point circle',
|
||||
showTitle: false,
|
||||
description: 'Draw a circle defined by three points',
|
||||
links: [],
|
||||
|
@ -119,6 +119,9 @@ export const commandBarMachine = setup({
|
||||
selectedCommand?.onSubmit()
|
||||
}
|
||||
},
|
||||
'Clear selected command': assign({
|
||||
selectedCommand: undefined,
|
||||
}),
|
||||
'Set current argument to first non-skippable': assign({
|
||||
currentArgument: ({ context, event }) => {
|
||||
const { selectedCommand } = context
|
||||
@ -246,6 +249,7 @@ export const commandBarMachine = setup({
|
||||
context.selectedCommand?.needsReview || false,
|
||||
'Command has no arguments': () => false,
|
||||
'All arguments are skippable': () => false,
|
||||
'Has selected command': ({ context }) => !!context.selectedCommand,
|
||||
},
|
||||
actors: {
|
||||
'Validate argument': fromPromise(
|
||||
@ -394,7 +398,7 @@ export const commandBarMachine = setup({
|
||||
),
|
||||
},
|
||||
}).createMachine({
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22O7JwozosyLUj3KiZZY85YtMUNgx5Ii81JuS5xBtPVCCyuVR0AOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSjPPFpDSQUP0DSQf3-QDgNAiCoJggAROBNw+aMkxNf5cMPHJSjPWRdhvZ9LGcF0b0kVQ8ycDRNkvNRWMbdjfwAoCcCjHjMOgyRyQAdywGITPwB42DA7hYzAgAjdAeDQjCoJw-du3TJE6mnWwoT0NIUTUAs71sMtdGsRYCyUtI9OJDijO49DeKw2BJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MBySy8y-LGPCElSeoy2lZJcwcNRfXHQpBWmWQsRsPYdDPZJUu-QyuPssy+Py5qSt4EyyEAkhUCDNhKF-AAzQ70EkJqiu2tqOt88S926w9UhhLlykWXFHXcTJixsd60hHGxNj2Jag01HVODAKzXI8rzE1+R6U38tN8IQJSuR0OZ0gvHQXHGxBPWmUdppUWwFV0MHJAhqGYcpAh1qwrqDx7ZFajUmVpulEbqlqREsfBTQ0jcPM5P5KmaehshNW1PVvOy7Cka7VHeoYDkyjSPQtAvPMqkRQU1BnX1+UydFkQvCWwEhqWAFEIC8xnFd3ZHntZuTDZG4ob1nGxPTUfXrBnS8nDmEpT2ObxTnXVUALeOrgPanycvKyrqpwWqGqurbWsThXjWd5WesQZYtmBkplCceplIncK0SrXYqnccxxcj5s2OQWP4-s3PzJgiqcCqmr6sa7P2x7vi6AcAvJJ7XEy0dUFMWsFxlnKfn+S5CL3E9Jiuapjv3i7qNx+T-bDskY6zourObpz+6cuZgK0Y5Ua0TqEvXDkJR-YnR0s1xjkIMnDln3uuVsHwOxT1NCjIuR5nCSBUExJi1huRqyooUTYpYnzeyUFkKsy4Tg4FQBAOAgg26Nmga7QKohshSBtNYXGDonQumtPYaQfsSjLBHDefepJICUJZtQ4oMx8wXj6jebGt4JwbC2OiVwig9hh2hLpVu0cOhxjbMBBGeABFPwSA3ERRMlhKWCn9WRVQzZQirMo0BAYNzxn4RJGBh5MRbGXsHawGQFBmLrpUasOQorOAjs0OxaUVrGVMvfaCuiVYEV2KWTYg1yxyA0i6ZYaIPSL3lOkS8wSo6hOWpxCJ8te6WRsnZKMjlnIxNgSNIiiTF6vVSdI9JM1taXlxLzTwqiClBnSqtSJScLIFVvjtKANSXquENqoJwtgyZzRFIUQ4MhqgNACdNTQCpLbWyshMnsjpSwjXRJYHecgcmImsLIz0odNAaGqPiHpDYY6HwTlE+ATiqHPwohYewWQshmGhHrCcJz5Bck5toZwGhMheC8EAA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAIwB2AHTiAHAE45AZjkAmdcoaSArCoA0IAJ6JxDOdJ2SF4gCySHaqZIa2Avm8NpMuAsXJUYKSMLEggHLA8fAJhIgiiOgBssokKTpJqiXK2OsqJaoYm8eJqytKJ9hqq+eJW2h5eGNh4RKRkAGKcLb74tJRgAMbc+ANNviGCEVH8gnEKubK5ympVrrlyhabm0rZ5CtYM4hVq83ININ7NfqTSVDSQZADybGA4E2FTvDOxiGoMDNJMjo9H9lLYGDpKpsEModOJpA5XOIwakwcidOdLj1-LdqLQIGQAIIQAijHx4WDvdhcL4xUBxURqSTw3LzfYKZSSOQZWzQ5S1crMuTAiElRKuTFjFo4u74sgAJTA6FQADcwCMpRBKcxJjTorMxEzkhouftuXCGGpIdCpADmZJxfzJLY5Cp5pLydcSLj7gSqeE9d96Yh+TppKoXfkGKdlHloUyFIDUvMXBzbFl3J4LprWt6AMpgfpDLpQDWesgFovDMlXf2ffU-BBZZKuczWWylRRwvlM6Q2LIMZQ2QdHZQeq65245vqDbgPOuBunCEP88MqPQchwVNLKaHptTSYUuRI6f4npyJcfYm5Ylozobz8ShamRWkGhBmeTSQeJX+JXQ6H88jQnCMiugoELCpypQKJeWa3l6U6er0hazvOajPgGr4NsGH6WhYsHOkycglLCEJ7iclgaIosKSLGGgKFe0o3AA4lg3AABZgCQJb4KQUAAK7oK83CwBQHG4DAIwCSQJAiXxJCCcJODcAu2FBsuH55GGJ7irYHYqHpGzGKYWiWB2nK2Kcv6wWO8E5jibGcdxvH8UJIliQAInAqFDGWtY6h8i7vlkZREbYZg6JuwEmQgfxhnkHbiHYJ7yHYTGIU5XE8TgpZucponSISADuWBRLl+BdGwAncBWAkAEboDwClKSJanTEucS1Bk36DicDCpOYLoKHu-x9tGuhgoozKpBlk5ZS5FX5R50gAGpYJQnAQOxJZkBA-BgNIXQqqgADWh0qhtW3sWAeYlv0hKKe5KntW+YRFGi5RyOKDrZEaUW8pppTguGuwDRFEO-nNjnsdlrlPQVsBrVd228LlZDcSQqDemwlDsQAZtj6DSJdm2o7d91gI9rUvYFL4dYIH0RV9P0Zv9CiA3EwMAmCWgVHYKVwY0yE4oqKqcGAxV1Y1zU1uMdNYQzjbMpYcgQlFxFq66u6xXRALbkyg7-Bz0bQzcYsS1LxIEMttOYfWGldYcCWwQmNiRrU0Jq2GRxJCbHZqC6ahm96FuSwqSqquqtuqQrDudVs4iJhUyZtn9qjQokYKHjk6hSLsZhciH0hh1LACiEDNTHr04ZpJT6bIEXxcKp50YDRT-mGrrJbUgFDp3xfIFxAynbx1PPaJe0HUdOAnedJMozd4+IzXjuIJCLIQqUWjJS4MVFBByS7BzyJq38WTB-ZIs3sPo8VcvHlTzgh3HWdF2L3OD8qZST66upCdxUTLBJOUUHB6GFPpSQXt1CWEGpaOiv4EyD1vmPBGj9MbY2kLjAmRMF5kyXmg7+q8AFJwbjkXQEVsj5FyO3RAiQjjfi5CReYPck7iA8FmHAqAIBwEEAhXMf8la4VEMsWwgJuTTXNGYK0tC4rwkDvsAaSQ-qlAhIPPEkBBFvWEVycoFQhywUHGaHWH04Q7FUNBdc+x2FXwnDiSss5eJyzwFo2ucQIplBWHrcEVhuoFFirCeEuxsj8mWEOFYmZhZ2JvNOXyc4ICuLXggaxCJwG70AiUHQfIzHBKHGYDMJFhTFwWjlPKhDRKJJIRFGQcIFBsiOIHJw8ZIQ7CUGrY+9g9AnmKbDRaZSaaFRKmVNGpYqo1Uqe+FKYZan1PyElbJYjrB6C5CUJQ9DL5ROvN6Ep8MBlI3WvgkZEzGzyAbnkZK-wjan38UzeEzZtBswdADYupdjm4XoTIOwuwHB5HyGkYyRRdBBLosCIE6ZvpnFsVs24KD77lPgEFf+kzxRH32EyFYlpOzQjAZYV2ehTkfI4W4IAA */
|
||||
context: {
|
||||
commands: [],
|
||||
selectedCommand: undefined,
|
||||
@ -421,14 +425,6 @@ export const commandBarMachine = setup({
|
||||
target: 'Selecting command',
|
||||
},
|
||||
|
||||
'Find and select command': {
|
||||
target: 'Command selected',
|
||||
actions: [
|
||||
'Find and select command',
|
||||
'Initialize arguments to submit',
|
||||
],
|
||||
},
|
||||
|
||||
'Add commands': {
|
||||
target: 'Closed',
|
||||
|
||||
@ -440,8 +436,6 @@ export const commandBarMachine = setup({
|
||||
),
|
||||
}),
|
||||
],
|
||||
|
||||
reenter: false,
|
||||
},
|
||||
|
||||
'Remove commands': {
|
||||
@ -458,10 +452,13 @@ export const commandBarMachine = setup({
|
||||
),
|
||||
}),
|
||||
],
|
||||
|
||||
reenter: false,
|
||||
},
|
||||
},
|
||||
|
||||
always: {
|
||||
target: 'Command selected',
|
||||
guard: 'Has selected command',
|
||||
},
|
||||
},
|
||||
|
||||
'Selecting command': {
|
||||
@ -478,7 +475,7 @@ export const commandBarMachine = setup({
|
||||
{
|
||||
target: 'Closed',
|
||||
guard: 'Command has no arguments',
|
||||
actions: ['Execute command'],
|
||||
actions: ['Execute command', 'Clear selected command'],
|
||||
},
|
||||
{
|
||||
target: 'Checking Arguments',
|
||||
@ -548,7 +545,7 @@ export const commandBarMachine = setup({
|
||||
on: {
|
||||
'Submit command': {
|
||||
target: 'Closed',
|
||||
actions: ['Execute command'],
|
||||
actions: ['Execute command', 'Clear selected command'],
|
||||
},
|
||||
|
||||
'Add argument': {
|
||||
@ -580,7 +577,7 @@ export const commandBarMachine = setup({
|
||||
},
|
||||
{
|
||||
target: 'Closed',
|
||||
actions: 'Execute command',
|
||||
actions: ['Execute command', 'Clear selected command'],
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
@ -600,6 +597,7 @@ export const commandBarMachine = setup({
|
||||
|
||||
Close: {
|
||||
target: '.Closed',
|
||||
actions: 'Clear selected command',
|
||||
},
|
||||
|
||||
Clear: {
|
||||
@ -607,6 +605,11 @@ export const commandBarMachine = setup({
|
||||
reenter: false,
|
||||
actions: ['Clear argument data'],
|
||||
},
|
||||
|
||||
'Find and select command': {
|
||||
target: '.Command selected',
|
||||
actions: ['Find and select command', 'Initialize arguments to submit'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -52,6 +52,7 @@ import {
|
||||
} from 'lang/modifyAst'
|
||||
import {
|
||||
applyEdgeTreatmentToSelection,
|
||||
ChamferParameters,
|
||||
EdgeTreatmentType,
|
||||
FilletParameters,
|
||||
} from 'lang/modifyAst/addEdgeTreatment'
|
||||
@ -132,6 +133,8 @@ export interface SketchDetails {
|
||||
zAxis: [number, number, number]
|
||||
yAxis: [number, number, number]
|
||||
origin: [number, number, number]
|
||||
// face id or plane id, both are strings
|
||||
animateTargetId?: string
|
||||
}
|
||||
|
||||
export interface SegmentOverlay {
|
||||
@ -272,6 +275,7 @@ export type ModelingMachineEvent =
|
||||
| { type: 'Shell'; data?: ModelingCommandSchema['Shell'] }
|
||||
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
|
||||
| { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
|
||||
| { type: 'Chamfer'; data?: ModelingCommandSchema['Chamfer'] }
|
||||
| { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
|
||||
| { type: 'Text-to-CAD'; data: ModelingCommandSchema['Text-to-CAD'] }
|
||||
| { type: 'Prompt-to-edit'; data: ModelingCommandSchema['Prompt-to-edit'] }
|
||||
@ -420,6 +424,8 @@ export const modelingMachine = setup({
|
||||
},
|
||||
'is editing existing sketch': ({ context: { sketchDetails } }) =>
|
||||
isEditingExistingSketch({ sketchDetails }),
|
||||
'is editing 3-point circle': ({ context: { sketchDetails } }) =>
|
||||
isEditing3PointCircle({ sketchDetails }),
|
||||
'Can make selection horizontal': ({ context: { selectionRanges } }) => {
|
||||
const info = horzVertInfo(selectionRanges, 'horizontal')
|
||||
if (trap(info)) return false
|
||||
@ -1737,6 +1743,33 @@ export const modelingMachine = setup({
|
||||
if (err(filletResult)) return filletResult
|
||||
}
|
||||
),
|
||||
chamferAstMod: fromPromise(
|
||||
async ({
|
||||
input,
|
||||
}: {
|
||||
input: ModelingCommandSchema['Chamfer'] | undefined
|
||||
}) => {
|
||||
if (!input) {
|
||||
return new Error('No input provided')
|
||||
}
|
||||
|
||||
// Extract inputs
|
||||
const ast = kclManager.ast
|
||||
const { selection, length } = input
|
||||
const parameters: ChamferParameters = {
|
||||
type: EdgeTreatmentType.Chamfer,
|
||||
length,
|
||||
}
|
||||
|
||||
// Apply chamfer to selection
|
||||
const chamferResult = await applyEdgeTreatmentToSelection(
|
||||
ast,
|
||||
selection,
|
||||
parameters
|
||||
)
|
||||
if (err(chamferResult)) return chamferResult
|
||||
}
|
||||
),
|
||||
'submit-prompt-edit': fromPromise(
|
||||
async ({ input }: { input: ModelingCommandSchema['Prompt-to-edit'] }) => {
|
||||
console.log('doing thing', input)
|
||||
@ -1821,6 +1854,11 @@ export const modelingMachine = setup({
|
||||
reenter: true,
|
||||
},
|
||||
|
||||
Chamfer: {
|
||||
target: 'Applying chamfer',
|
||||
reenter: true,
|
||||
},
|
||||
|
||||
Export: {
|
||||
target: 'idle',
|
||||
reenter: false,
|
||||
@ -2153,6 +2191,10 @@ export const modelingMachine = setup({
|
||||
target: 'SketchIdle',
|
||||
guard: 'is editing existing sketch',
|
||||
},
|
||||
{
|
||||
target: 'circle3PointToolSelect',
|
||||
guard: 'is editing 3-point circle',
|
||||
},
|
||||
'Line tool',
|
||||
],
|
||||
},
|
||||
@ -2484,13 +2526,8 @@ export const modelingMachine = setup({
|
||||
circle3PointToolSelect: {
|
||||
invoke: {
|
||||
id: 'actor-circle-3-point',
|
||||
input: function ({ context, event }) {
|
||||
// These are not really necessary but I believe they are needed
|
||||
// to satisfy TypeScript type narrowing or undefined check.
|
||||
if (event.type !== 'change tool') return
|
||||
if (event.data?.tool !== 'circle3Points') return
|
||||
input: function ({ context }) {
|
||||
if (!context.sketchDetails) return
|
||||
|
||||
return context.sketchDetails
|
||||
},
|
||||
src: 'actorCircle3Point',
|
||||
@ -2650,6 +2687,19 @@ export const modelingMachine = setup({
|
||||
},
|
||||
},
|
||||
|
||||
'Applying chamfer': {
|
||||
invoke: {
|
||||
src: 'chamferAstMod',
|
||||
id: 'chamferAstMod',
|
||||
input: ({ event }) => {
|
||||
if (event.type !== 'Chamfer') return undefined
|
||||
return event.data
|
||||
},
|
||||
onDone: ['idle'],
|
||||
onError: ['idle'],
|
||||
},
|
||||
},
|
||||
|
||||
'Applying Prompt-to-edit': {
|
||||
invoke: {
|
||||
src: 'submit-prompt-edit',
|
||||
@ -2735,6 +2785,34 @@ export function isEditingExistingSketch({
|
||||
)
|
||||
return (hasStartProfileAt && pipeExpression.body.length > 2) || hasCircle
|
||||
}
|
||||
export function isEditing3PointCircle({
|
||||
sketchDetails,
|
||||
}: {
|
||||
sketchDetails: SketchDetails | null
|
||||
}): boolean {
|
||||
if (!sketchDetails?.sketchPathToNode) return false
|
||||
const variableDeclaration = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
sketchDetails.sketchPathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(variableDeclaration)) return false
|
||||
if (variableDeclaration.node.type !== 'VariableDeclarator') return false
|
||||
const pipeExpression = variableDeclaration.node.init
|
||||
if (pipeExpression.type !== 'PipeExpression') return false
|
||||
const hasStartProfileAt = pipeExpression.body.some(
|
||||
(item) =>
|
||||
item.type === 'CallExpression' && item.callee.name === 'startProfileAt'
|
||||
)
|
||||
const hasCircle3Point = pipeExpression.body.some(
|
||||
(item) =>
|
||||
item.type === 'CallExpressionKw' &&
|
||||
item.callee.name === 'circleThreePoint'
|
||||
)
|
||||
return (
|
||||
(hasStartProfileAt && pipeExpression.body.length > 2) || hasCircle3Point
|
||||
)
|
||||
}
|
||||
export function pipeHasCircle({
|
||||
sketchDetails,
|
||||
}: {
|
||||
@ -2755,6 +2833,27 @@ export function pipeHasCircle({
|
||||
)
|
||||
return hasCircle
|
||||
}
|
||||
export function pipeHasCircleThreePoint({
|
||||
sketchDetails,
|
||||
}: {
|
||||
sketchDetails: SketchDetails | null
|
||||
}): boolean {
|
||||
if (!sketchDetails?.sketchPathToNode) return false
|
||||
const variableDeclaration = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
sketchDetails.sketchPathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(variableDeclaration)) return false
|
||||
if (variableDeclaration.node.type !== 'VariableDeclarator') return false
|
||||
const pipeExpression = variableDeclaration.node.init
|
||||
if (pipeExpression.type !== 'PipeExpression') return false
|
||||
const hasCircle = pipeExpression.body.some(
|
||||
(item) =>
|
||||
item.type === 'CallExpression' && item.callee.name === 'circleThreePoint'
|
||||
)
|
||||
return hasCircle
|
||||
}
|
||||
|
||||
export function canRectangleOrCircleTool({
|
||||
sketchDetails,
|
||||
|
@ -43,6 +43,7 @@ export const settingsMachine = setup({
|
||||
'Execute AST': () => {},
|
||||
toastSuccess: () => {},
|
||||
setClientSideSceneUnits: () => {},
|
||||
setAllowOrbitInSketchMode: () => {},
|
||||
persistSettings: () => {},
|
||||
resetSettings: assign(({ context, event }) => {
|
||||
if (!('level' in event)) return {}
|
||||
@ -157,6 +158,15 @@ export const settingsMachine = setup({
|
||||
actions: ['setSettingAtLevel', 'toastSuccess'],
|
||||
},
|
||||
|
||||
'set.app.allowOrbitInSketchMode': {
|
||||
target: 'persisting settings',
|
||||
actions: [
|
||||
'setSettingAtLevel',
|
||||
'toastSuccess',
|
||||
'setAllowOrbitInSketchMode',
|
||||
],
|
||||
},
|
||||
|
||||
'set.modeling.cameraProjection': {
|
||||
target: 'persisting settings',
|
||||
|
||||
@ -183,6 +193,7 @@ export const settingsMachine = setup({
|
||||
'setClientSideSceneUnits',
|
||||
'Execute AST',
|
||||
'setClientTheme',
|
||||
'setAllowOrbitInSketchMode',
|
||||
],
|
||||
},
|
||||
|
||||
@ -194,6 +205,7 @@ export const settingsMachine = setup({
|
||||
'setClientSideSceneUnits',
|
||||
'Execute AST',
|
||||
'setClientTheme',
|
||||
'setAllowOrbitInSketchMode',
|
||||
],
|
||||
},
|
||||
|
||||
|
@ -320,6 +320,11 @@ export function getAutoUpdater(): AppUpdater {
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
// Disable auto updater on non-versioned builds
|
||||
if (packageJSON.version === '0.0.0') {
|
||||
return
|
||||
}
|
||||
|
||||
const autoUpdater = getAutoUpdater()
|
||||
// TODO: we're getting `Error: Response ends without calling any handlers` with our setup,
|
||||
// so at the moment this isn't worth enabling
|
||||
|
@ -205,6 +205,7 @@ export function OnboardingButtons({
|
||||
</p>
|
||||
)}
|
||||
<ActionButton
|
||||
autoFocus
|
||||
Element="button"
|
||||
onClick={next}
|
||||
iconStart={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}
|
||||
|
146
src/wasm-lib/Cargo.lock
generated
@ -176,7 +176,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -187,7 +187,7 @@ checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -204,7 +204,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -474,7 +474,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -665,7 +665,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -676,7 +676,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -723,7 +723,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "derive-docs"
|
||||
version = "0.1.33"
|
||||
version = "0.1.34"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"anyhow",
|
||||
@ -737,7 +737,7 @@ dependencies = [
|
||||
"rustfmt-wrapper",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -748,7 +748,38 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -791,7 +822,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -829,7 +860,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -990,7 +1021,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1086,7 +1117,7 @@ dependencies = [
|
||||
"inflections",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1132,17 +1163,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
version = "6.2.0"
|
||||
version = "6.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd4ccde012831f9a071a637b0d4e31df31c0f6c525784b35ae76a9ac6bc1e315"
|
||||
checksum = "3d6b224b95c1e668ac0270325ad563b2eef1469fbbb8959bc7c692c844b813d9"
|
||||
dependencies = [
|
||||
"derive_builder",
|
||||
"log",
|
||||
"num-order",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.68",
|
||||
"thiserror 2.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1494,7 +1526,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1684,7 +1716,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.2.29"
|
||||
version = "0.2.30"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx 0.5.1",
|
||||
@ -1752,7 +1784,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-test-server"
|
||||
version = "0.1.19"
|
||||
version = "0.1.20"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"hyper 0.14.30",
|
||||
@ -1819,9 +1851,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-modeling-cmds"
|
||||
version = "0.2.86"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65e34a8eeb4fff5167666d1f2bc36c95d08ab3a0f736a02c8d33a8cde21cfd8d"
|
||||
checksum = "ce9e58b34645facea36bc9f4868877bbe6fcac01b92896825e8d4f2f7c71dbd6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -1852,7 +1884,7 @@ dependencies = [
|
||||
"kittycad-modeling-cmds-macros-impl",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1863,7 +1895,7 @@ checksum = "fdb4ee23cc996aa2dca7584d410e8826e08161e1ac4335bb646d5ede33f37cb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2013,7 +2045,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2312,7 +2344,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.8.5",
|
||||
"structmeta",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2326,7 +2358,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.8.5",
|
||||
"structmeta",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2366,7 +2398,7 @@ dependencies = [
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2424,7 +2456,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2554,7 +2586,7 @@ dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2613,7 +2645,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2626,7 +2658,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3161,7 +3193,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3225,7 +3257,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3236,7 +3268,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3260,7 +3292,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3281,7 +3313,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3430,7 +3462,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3441,7 +3473,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3463,7 +3495,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3506,9 +3538,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.95"
|
||||
version = "2.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a"
|
||||
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3532,7 +3564,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3640,7 +3672,7 @@ checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3651,7 +3683,7 @@ checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3763,7 +3795,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3905,7 +3937,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3933,7 +3965,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4016,7 +4048,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@ -4159,9 +4191,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.11.0"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||
checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
@ -4195,7 +4227,7 @@ dependencies = [
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4256,7 +4288,7 @@ dependencies = [
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -4292,7 +4324,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -4673,7 +4705,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@ -4695,7 +4727,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4715,7 +4747,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@ -4744,7 +4776,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.95",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -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.86", features = [
|
||||
kittycad-modeling-cmds = { version = "0.2.89", features = [
|
||||
"ts-rs",
|
||||
"websocket",
|
||||
] }
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "derive-docs"
|
||||
description = "A tool for generating documentation from Rust derive macros"
|
||||
version = "0.1.33"
|
||||
version = "0.1.34"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
@ -20,7 +20,7 @@ quote = "1"
|
||||
regex = "1.11"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
serde_tokenstream = "0.2"
|
||||
syn = { version = "2.0.95", features = ["full"] }
|
||||
syn = { version = "2.0.96", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.95"
|
||||
|
@ -815,7 +815,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new(&ctx.settings)).await {
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", #fn_name, #index),
|
||||
|
@ -14,7 +14,10 @@ mod test_examples_someFn {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "someFn", 0usize),
|
||||
|
@ -14,7 +14,10 @@ mod test_examples_someFn {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "someFn", 0usize),
|
||||
|
@ -15,7 +15,10 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "show", 0usize),
|
||||
@ -69,7 +72,10 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "show", 1usize),
|
||||
|
@ -15,7 +15,10 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "show", 0usize),
|
||||
|
@ -16,7 +16,10 @@ mod test_examples_my_func {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "my_func", 0usize),
|
||||
@ -70,7 +73,10 @@ mod test_examples_my_func {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "my_func", 1usize),
|
||||
|
@ -16,7 +16,10 @@ mod test_examples_line_to {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "line_to", 0usize),
|
||||
@ -70,7 +73,10 @@ mod test_examples_line_to {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "line_to", 1usize),
|
||||
|
@ -15,7 +15,10 @@ mod test_examples_min {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "min", 0usize),
|
||||
@ -69,7 +72,10 @@ mod test_examples_min {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "min", 1usize),
|
||||
|
@ -15,7 +15,10 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "show", 0usize),
|
||||
|
@ -15,7 +15,10 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "import", 0usize),
|
||||
|
@ -15,7 +15,10 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "import", 0usize),
|
||||
|
@ -15,7 +15,10 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "import", 0usize),
|
||||
|
@ -15,7 +15,10 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "show", 0usize),
|
||||
|
@ -14,7 +14,10 @@ mod test_examples_some_function {
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
};
|
||||
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
|
||||
if let Err(e) = ctx
|
||||
.run(program.into(), &mut crate::ExecState::new(&ctx.settings))
|
||||
.await
|
||||
{
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e,
|
||||
filename: format!("{}{}", "some_function", 0usize),
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-test-server"
|
||||
description = "A test server for KCL"
|
||||
version = "0.1.19"
|
||||
version = "0.1.20"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
|
@ -164,7 +164,7 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
|
||||
};
|
||||
|
||||
eprintln!("Executing {test_name}");
|
||||
let mut exec_state = ExecState::new();
|
||||
let mut exec_state = ExecState::new(&state.settings);
|
||||
// This is a shitty source range, I don't know what else to use for it though.
|
||||
// There's no actual KCL associated with this reset_scene call.
|
||||
if let Err(e) = state
|
||||
|
@ -16,7 +16,7 @@ pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
|
||||
let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new(
|
||||
crate::conn_mock_core::EngineConnection::new(ref_result).await?,
|
||||
)));
|
||||
ctx.run(program.into(), &mut ExecState::new()).await?;
|
||||
ctx.run(program.into(), &mut ExecState::new(&ctx.settings)).await?;
|
||||
|
||||
let result = result.lock().expect("mutex lock").clone();
|
||||
Ok(result)
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-lib"
|
||||
description = "KittyCAD Language implementation and tools"
|
||||
version = "0.2.29"
|
||||
version = "0.2.30"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
@ -22,7 +22,7 @@ clap = { version = "4.5.23", default-features = false, optional = true, features
|
||||
] }
|
||||
convert_case = "0.6.0"
|
||||
dashmap = "6.1.0"
|
||||
derive-docs = { version = "0.1.33", path = "../derive-docs" }
|
||||
derive-docs = { version = "0.1.34", path = "../derive-docs" }
|
||||
dhat = { version = "0.3", optional = true }
|
||||
fnv = "1.0.7"
|
||||
form_urlencoded = "1.2.1"
|
||||
@ -112,7 +112,7 @@ tabled = ["dep:tabled"]
|
||||
base64 = "0.22.1"
|
||||
criterion = { version = "0.5.1", features = ["async_tokio"] }
|
||||
expectorate = "1.1.0"
|
||||
handlebars = "6.2.0"
|
||||
handlebars = "6.3.0"
|
||||
iai = "0.1"
|
||||
image = { version = "0.25.5", default-features = false, features = ["png"] }
|
||||
insta = { version = "1.41.1", features = ["json", "filters", "redactions"] }
|
||||
|
@ -1024,6 +1024,36 @@ mod tests {
|
||||
assert_eq!(snippet, r#"hole(${0:holeSketch}, ${1:%})${}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_helix() {
|
||||
let helix_fn: Box<dyn StdLibFn> = Box::new(crate::std::helix::Helix);
|
||||
let snippet = helix_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"helix({
|
||||
revolutions = ${0:3.14},
|
||||
angleStart = ${1:3.14},
|
||||
ccw = ${2:false},
|
||||
radius = ${3:3.14},
|
||||
axis = ${4:"X"},
|
||||
})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_helix_revolutions() {
|
||||
let helix_fn: Box<dyn StdLibFn> = Box::new(crate::std::helix::HelixRevolutions);
|
||||
let snippet = helix_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"helixRevolutions({
|
||||
revolutions = ${0:3.14},
|
||||
angleStart = ${1:3.14},
|
||||
ccw = ${2:false},
|
||||
}, ${3:%})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
// We want to test the snippets we compile at lsp start.
|
||||
#[test]
|
||||
fn get_all_stdlib_autocomplete_snippets() {
|
||||
|
@ -30,13 +30,16 @@ impl From<KclErrorWithOutputs> for ExecError {
|
||||
#[derive(Debug)]
|
||||
pub struct ExecErrorWithState {
|
||||
pub error: ExecError,
|
||||
pub exec_state: crate::ExecState,
|
||||
pub exec_state: Option<crate::ExecState>,
|
||||
}
|
||||
|
||||
impl ExecErrorWithState {
|
||||
#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
|
||||
pub fn new(error: ExecError, exec_state: crate::ExecState) -> Self {
|
||||
Self { error, exec_state }
|
||||
Self {
|
||||
error,
|
||||
exec_state: Some(exec_state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +47,7 @@ impl From<ExecError> for ExecErrorWithState {
|
||||
fn from(error: ExecError) -> Self {
|
||||
Self {
|
||||
error,
|
||||
exec_state: Default::default(),
|
||||
exec_state: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,7 +56,7 @@ impl From<ConnectionError> for ExecErrorWithState {
|
||||
fn from(error: ConnectionError) -> Self {
|
||||
Self {
|
||||
error: error.into(),
|
||||
exec_state: Default::default(),
|
||||
exec_state: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,8 +21,6 @@ use crate::{
|
||||
|
||||
use super::cad_op::{OpArg, Operation};
|
||||
|
||||
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;
|
||||
|
||||
impl BinaryPart {
|
||||
#[async_recursion]
|
||||
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
@ -974,10 +972,9 @@ fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -
|
||||
if num < 0.0 {
|
||||
return make_err(format!("'{num}' is negative, so you can't index an array with it"))
|
||||
}
|
||||
let nearest_int = num.round();
|
||||
let delta = num-nearest_int;
|
||||
if delta < FLOAT_TO_INT_MAX_DELTA {
|
||||
Ok(Property::UInt(nearest_int as usize))
|
||||
let nearest_int = crate::try_f64_to_usize(num);
|
||||
if let Some(nearest_int) = nearest_int {
|
||||
Ok(Property::UInt(nearest_int))
|
||||
} else {
|
||||
make_err(format!("'{num}' is not an integer, so you can't index an array with it"))
|
||||
}
|
||||
@ -988,6 +985,7 @@ fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Property {
|
||||
fn type_name(&self) -> &'static str {
|
||||
match self {
|
||||
|
@ -601,10 +601,24 @@ impl TryFrom<NumericSuffix> for UnitLen {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||
impl From<crate::UnitLength> for UnitLen {
|
||||
fn from(unit: crate::UnitLength) -> Self {
|
||||
match unit {
|
||||
crate::UnitLength::Cm => UnitLen::Cm,
|
||||
crate::UnitLength::Ft => UnitLen::Feet,
|
||||
crate::UnitLength::In => UnitLen::Inches,
|
||||
crate::UnitLength::M => UnitLen::M,
|
||||
crate::UnitLength::Mm => UnitLen::Mm,
|
||||
crate::UnitLength::Yd => UnitLen::Yards,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum UnitAngle {
|
||||
#[default]
|
||||
Degrees,
|
||||
Radians,
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ type Point2D = kcmc::shared::Point2d<f64>;
|
||||
type Point3D = kcmc::shared::Point3d<f64>;
|
||||
|
||||
pub use function_param::FunctionParam;
|
||||
pub use kcl_value::{KclObjectFields, KclValue};
|
||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
||||
use uuid::Uuid;
|
||||
|
||||
mod annotations;
|
||||
@ -77,7 +77,7 @@ pub struct GlobalState {
|
||||
pub artifact_commands: Vec<ArtifactCommand>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModuleState {
|
||||
/// Program variable bindings.
|
||||
@ -115,21 +115,15 @@ pub struct ExecOutcome {
|
||||
pub artifact_commands: Vec<ArtifactCommand>,
|
||||
}
|
||||
|
||||
impl Default for ExecState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecState {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(exec_settings: &ExecutorSettings) -> Self {
|
||||
ExecState {
|
||||
global: GlobalState::new(),
|
||||
mod_local: ModuleState::default(),
|
||||
mod_local: ModuleState::new(exec_settings),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
fn reset(&mut self, exec_settings: &ExecutorSettings) {
|
||||
let mut id_generator = self.global.id_generator.clone();
|
||||
// We do not pop the ids, since we want to keep the same id generator.
|
||||
// This is for the front end to keep track of the ids.
|
||||
@ -140,7 +134,7 @@ impl ExecState {
|
||||
|
||||
*self = ExecState {
|
||||
global,
|
||||
mod_local: ModuleState::default(),
|
||||
mod_local: ModuleState::new(exec_settings),
|
||||
};
|
||||
}
|
||||
|
||||
@ -204,6 +198,14 @@ impl ExecState {
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn length_unit(&self) -> UnitLen {
|
||||
self.mod_local.settings.default_length_units
|
||||
}
|
||||
|
||||
pub fn angle_unit(&self) -> UnitAngle {
|
||||
self.mod_local.settings.default_angle_units
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
@ -232,6 +234,23 @@ impl GlobalState {
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleState {
|
||||
fn new(exec_settings: &ExecutorSettings) -> Self {
|
||||
ModuleState {
|
||||
memory: Default::default(),
|
||||
dynamic_state: Default::default(),
|
||||
pipe_value: Default::default(),
|
||||
module_exports: Default::default(),
|
||||
import_stack: Default::default(),
|
||||
operations: Default::default(),
|
||||
settings: MetaSettings {
|
||||
default_length_units: exec_settings.units.into(),
|
||||
default_angle_units: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -240,15 +259,6 @@ pub struct MetaSettings {
|
||||
pub default_angle_units: kcl_value::UnitAngle,
|
||||
}
|
||||
|
||||
impl Default for MetaSettings {
|
||||
fn default() -> Self {
|
||||
MetaSettings {
|
||||
default_length_units: kcl_value::UnitLen::Mm,
|
||||
default_angle_units: kcl_value::UnitAngle::Degrees,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MetaSettings {
|
||||
fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> {
|
||||
let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?;
|
||||
@ -1711,7 +1721,7 @@ pub struct ExecutorContext {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
pub struct ExecutorSettings {
|
||||
/// The unit to use in modeling dimensions.
|
||||
/// The project-default unit to use in modeling dimensions.
|
||||
pub units: UnitLength,
|
||||
/// Highlight edges of 3D objects?
|
||||
pub highlight_edges: bool,
|
||||
@ -2214,7 +2224,7 @@ impl ExecutorContext {
|
||||
|
||||
if cache_result.clear_scene && !self.is_mock() {
|
||||
// Pop the execution state, since we are starting fresh.
|
||||
exec_state.reset();
|
||||
exec_state.reset(&self.settings);
|
||||
|
||||
// We don't do this in mock mode since there is no engine connection
|
||||
// anyways and from the TS side we override memory and don't want to clear it.
|
||||
@ -2457,7 +2467,7 @@ impl ExecutorContext {
|
||||
|
||||
let mut local_state = ModuleState {
|
||||
import_stack: exec_state.mod_local.import_stack.clone(),
|
||||
..Default::default()
|
||||
..ModuleState::new(&self.settings)
|
||||
};
|
||||
local_state.import_stack.push(info.path.clone());
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
@ -2830,7 +2840,7 @@ mod tests {
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
let mut exec_state = ExecState::default();
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
ctx.run(program.clone().into(), &mut exec_state).await?;
|
||||
|
||||
Ok((program, ctx, exec_state))
|
||||
@ -3302,6 +3312,25 @@ let shape = layer() |> patternTransform(10, transform, %)
|
||||
assert_eq!(7.4, mem_get_json(exec_state.memory(), "thing").as_f64().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_unit_default() {
|
||||
let ast = r#"const inMm = 25.4 * mm()
|
||||
const inInches = 1.0 * inch()"#;
|
||||
let (_, _, exec_state) = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(25.4, mem_get_json(exec_state.memory(), "inMm").as_f64().unwrap());
|
||||
assert_eq!(25.4, mem_get_json(exec_state.memory(), "inInches").as_f64().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_unit_overriden() {
|
||||
let ast = r#"@settings(defaultLengthUnit = inch)
|
||||
const inMm = 25.4 * mm()
|
||||
const inInches = 1.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!(1.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
|
||||
@ -4045,7 +4074,7 @@ shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
|
||||
.unwrap();
|
||||
let old_program = crate::Program::parse_no_errs(code).unwrap();
|
||||
// Execute the program.
|
||||
let mut exec_state = Default::default();
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
let cache_info = crate::CacheInformation {
|
||||
old: None,
|
||||
new_ast: old_program.ast.clone(),
|
||||
|
@ -70,7 +70,7 @@ mod settings;
|
||||
#[cfg(test)]
|
||||
mod simulation_tests;
|
||||
mod source_range;
|
||||
mod std;
|
||||
pub mod std;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod test_server;
|
||||
mod thread;
|
||||
@ -84,7 +84,7 @@ pub use engine::{EngineManager, ExecutionKind};
|
||||
pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs};
|
||||
pub use execution::{
|
||||
cache::{CacheInformation, OldAstState},
|
||||
ExecState, ExecutorContext, ExecutorSettings,
|
||||
ExecState, ExecutorContext, ExecutorSettings, Point2d,
|
||||
};
|
||||
pub use lsp::{
|
||||
copilot::Backend as CopilotLspBackend,
|
||||
|