Compare commits

...

31 Commits

Author SHA1 Message Date
c0817b00e4 Auto retry on yarn install as it sometimes fail on windows (#5077) 2025-01-16 18:03:31 -05:00
4ea1d16fb6 Loft command prompt focus fix (#5080) 2025-01-16 18:03:01 -05:00
d049bf33e8 Return key to go through Onboarding steps (#5086)
Fixes #5049
2025-01-16 17:45:59 -05:00
7b11047d07 Fix helix sweep, you can make springs! (#5089)
* fix dumb mistake

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

* updates for springs!

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

* update docs

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

* update known issues

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-01-16 13:50:13 -08:00
412e9568f2 Per-file units (use per-file settings for conversion functions) (#5064)
* Use the project default units for the per-file unit default values

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

* Use per-file units in conversion functions

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-01-17 07:55:01 +13:00
9be208e5e1 Fix wasm bundle upload (upload-artifact@v4) (#5084) 2025-01-16 13:33:14 -05:00
842ef5ede9 Feature: Allow orbit in sketch mode via setting (#4990)
* feat: enable/disable free camera aka allow orbit in sketch mode mvp

* fix: removing comments

* fix: logic for enabling and disabling in and out of sketch mode

* fix: fmt, linter, tsc fixes

* fix: added comment

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

* fix: current,prev check to no op the useeffect if the values are the same

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-16 11:48:13 -06:00
3f855d7bad Make commands disable, not unregister, based on their machineActor (#5070)
* Make "Find and select command" global to commandBarMachine

* Make commands not removed based on their actor state, only disabled

* Sort commands better in CommandComboBox

* Break out sort logic, add a few unit tests

* Fix missed name change

* Needed to make one more change from source branch:
since `optionsFromContext` now only gets fired once, I/O-based options need to use the `options` config instead.

---------

Co-authored-by: 49fl <ircsurfer33@gmail.com>
2025-01-16 12:08:48 -05:00
0a1a6e50cf 3-point circle interactive component (#4982)
* Add dragging behavior to 3 point circle

Uses our talked about technique of calling Rust functions to calculate new
geometry coordinates and parameters. It works very well!

Need to have the modeling app show "edit sketch" still.

* Cargo fmt

* cargo fmt

* Address Jon's comments

* Fix clippy

* Dont use isNaN

* Make points easier to select (enlarge)

* Fix circle button not being activated

* Ensure efficiency of updating editor vs execution

* Make cargo clippy happy
2025-01-16 11:10:36 -05:00
d4e955289c Add helix icon and start of toolbar/feature tree impl (#5062)
* Add icon

* Add to stdLibMap

* Add to toolbar as "kcl-only"

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

* Trigger CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
2025-01-16 14:22:19 +00:00
c147a219f4 Minor bits and pieces (#5066)
* Use std deprecation for int rather than a hack in the parser

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

* Don't allow an epsilon when converting floats to ints for property access

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

* Fixup tests

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-01-16 14:00:32 +13:00
38513a1e25 Fix broken golden standard tests caused by changes to kcl-samples (#5065)
* Fix our golden standard tests (broken by new assemblies kcl-samples)

* Finally use the right combination of env vars

* Fix the manifest

* Continue to fix multiple file kcl-samples

* Fix loading in desktop app

* Type narrow for tsc

* fmt

---------

Co-authored-by: Frank Noirot <frank@kittycad.io>
2025-01-15 23:30:20 +00:00
c0c5c790ca Bump typescript from 5.7.2 to 5.7.3 (#5021)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.7.2 to 5.7.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.7.2...v5.7.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-14 16:18:26 -05:00
8b60f75220 custom axis and origin example for helix (#5057)
* custom axis and origin for helix

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

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

* empty

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-14 12:05:36 -08:00
f91ad4331f Bump @types/node from 20.14.9 to 22.10.6 in /packages/codemirror-lsp-client (#5041) 2025-01-14 17:36:46 +00:00
max
59103a2118 Remove Redundant Fillet Button State Test (#5009)
delete obsolete test
2025-01-14 12:09:29 -05:00
max
9737c2550a Hook up chamfer UI with AST-mod (#4694)
* button

* config

* hook up with ast

* cmd bar test

* button states fix and test

* little naming fix

* xState action to actor

* remove button state test updates

* fixture-based approach

* nightly

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>

* Update src/lib/toolbar.ts

Co-authored-by: Frank Noirot <frank@zoo.dev>

---------

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>
2025-01-14 12:08:32 -05:00
bf9d01a8dd Bump xstate from 5.17.4 to 5.19.2 (#5027) 2025-01-14 15:39:05 +00:00
702e322f90 ci: Add yarn test of packages/codemirror-lang-kcl (#5035)
* ci: Add yarn test of packages/codemirror-lang-kcl

* Fix CI error running tests

* Fix postcss config error
2025-01-14 09:30:08 -05:00
e82830754d turns on helix from edge (#5036)
* updates for new lib

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

* autocomplete

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

* bump version

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

* bump all the things

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

* new samples

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

* docs

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-01-13 23:34:43 +00:00
7806377a5a Disable auto-updater on non-versioned builds (#5042) 2025-01-13 17:40:51 -05:00
859afa2fd8 Upgrade all wasm-bindgen dependencies together (#5037) 2025-01-13 13:24:23 -08:00
0a5f3093fc Fix Cargo.lock to not have changes (#5034) 2025-01-13 15:38:24 -05:00
b65f7939f6 Fix artifact types to be more accurate (#5022) 2025-01-13 15:02:55 -05:00
c35dea5e07 Bump syn from 2.0.95 to 2.0.96 in /src/wasm-lib (#5015)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.95 to 2.0.96.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.95...2.0.96)

---
updated-dependencies:
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-13 10:04:30 -08:00
fc66d4745f Bump handlebars from 6.2.0 to 6.3.0 in /src/wasm-lib (#5012)
Bumps [handlebars](https://github.com/sunng87/handlebars-rust) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/sunng87/handlebars-rust/releases)
- [Changelog](https://github.com/sunng87/handlebars-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sunng87/handlebars-rust/compare/v6.2.0...v6.3.0)

---
updated-dependencies:
- dependency-name: handlebars
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-13 13:03:34 -05:00
b313d26c2a Bump @lezer/generator from 1.7.1 to 1.7.2 (#5018)
Bumps [@lezer/generator](https://github.com/lezer-parser/generator) from 1.7.1 to 1.7.2.
- [Changelog](https://github.com/lezer-parser/generator/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lezer-parser/generator/compare/1.7.1...1.7.2)

---
updated-dependencies:
- dependency-name: "@lezer/generator"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-13 11:48:13 -05:00
00b94ead62 Add packages to Dependabot updates (#5024) 2025-01-13 11:29:00 -05:00
0531ea1ce9 Change Dependabot PRs to always be made on Mondays (#5025) 2025-01-13 15:30:33 +00:00
5f9a4887c1 Developer workflow: added auto generated workspace file from vitest extension in vscode (#4997)
* chore: added auto generated workspace file from vitest extension in vscode

* fix: auto fmt fixes
2025-01-13 09:57:12 -05:00
da7dfa16d8 Fix lost lints and add new ones (#5011)
* Add eslint-plugin-jsx-a11y dependency

* Add jsx-a11y lint

* Add eslint-plugin-react-hooks dependency

* Add react hooks lints

* Ignore new react hooks lint in tests

* Add eslint-plugin-testing-library dependency

* Add testing-library lint

* Fix yarn lint to use all files recursively
2025-01-13 09:30:14 -05:00
120 changed files with 1882 additions and 828 deletions

View File

@ -6,16 +6,24 @@
"plugins": [ "plugins": [
"css-modules", "css-modules",
"jest", "jest",
"jsx-a11y",
"react", "react",
"react-hooks",
"suggest-no-throw", "suggest-no-throw",
"testing-library",
"@typescript-eslint" "@typescript-eslint"
], ],
"extends": [ "extends": [
"plugin:css-modules/recommended" "plugin:css-modules/recommended",
"plugin:jsx-a11y/recommended",
"plugin:react-hooks/recommended"
], ],
"rules": { "rules": {
"@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/no-misused-promises": "error",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-autofocus": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"no-restricted-globals": [ "no-restricted-globals": [
"error", "error",
{ {
@ -33,6 +41,9 @@
"overrides": [ "overrides": [
{ {
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure "files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
"extends": [
"plugin:testing-library/react"
],
"rules": { "rules": {
"suggest-no-throw/suggest-no-throw": "off", "suggest-no-throw/suggest-no-throw": "off",
"testing-library/prefer-screen-queries": "off", "testing-library/prefer-screen-queries": "off",
@ -41,6 +52,9 @@
}, },
{ {
"files": ["src/**/*.test.ts"], "files": ["src/**/*.test.ts"],
"extends": [
"plugin:testing-library/react"
],
"rules": { "rules": {
"suggest-no-throw/suggest-no-throw": "off", "suggest-no-throw/suggest-no-throw": "off",
} }

View File

@ -6,23 +6,29 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: 'npm' # See documentation for possible values - package-ecosystem: 'npm' # See documentation for possible values
directory: '/' # Location of package manifests directories:
- '/'
- '/packages/codemirror-lang-kcl/'
- '/packages/codemirror-lsp-client/'
schedule: schedule:
interval: 'weekly' interval: weekly
day: monday
reviewers: reviewers:
- franknoirot - franknoirot
- irev-dev - irev-dev
- package-ecosystem: 'github-actions' # See documentation for possible values - package-ecosystem: 'github-actions' # See documentation for possible values
directory: '/' # Location of package manifests directory: '/' # Location of package manifests
schedule: schedule:
interval: 'weekly' interval: weekly
day: monday
reviewers: reviewers:
- adamchalmers - adamchalmers
- jessfraz - jessfraz
- package-ecosystem: 'cargo' # See documentation for possible values - package-ecosystem: 'cargo' # See documentation for possible values
directory: '/src/wasm-lib/' # Location of package manifests directory: '/src/wasm-lib/' # Location of package manifests
schedule: schedule:
interval: 'weekly' interval: weekly
day: monday
reviewers: reviewers:
- adamchalmers - adamchalmers
- jessfraz - jessfraz
@ -30,3 +36,6 @@ updates:
serde-dependencies: serde-dependencies:
patterns: patterns:
- "serde*" - "serde*"
wasm-bindgen-deps:
patterns:
- "wasm-bindgen*"

View File

@ -27,7 +27,7 @@ jobs:
# Upload the WASM bundle as an artifact # Upload the WASM bundle as an artifact
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: wasm-bundle name: wasm-bundle
path: src/wasm-lib/pkg path: src/wasm-lib/pkg

View File

@ -126,7 +126,13 @@ jobs:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' # Set this to npm, yarn or pnpm. 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 - run: yarn tronb:vite

View 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

View File

@ -24,5 +24,3 @@ once fixed in engine will just start working here with no language changes.
chamfer cases work currently. chamfer cases work currently.
- **Appearance**: Changing the appearance on a loft does not work. - **Appearance**: Changing the appearance on a loft does not work.
- **Helix**: Currently sweeping a helix does not work.

File diff suppressed because one or more lines are too long

View File

@ -53,7 +53,6 @@ layout: manual
* [`hollow`](kcl/hollow) * [`hollow`](kcl/hollow)
* [`import`](kcl/import) * [`import`](kcl/import)
* [`inch`](kcl/inch) * [`inch`](kcl/inch)
* [`int`](kcl/int)
* [`lastSegX`](kcl/lastSegX) * [`lastSegX`](kcl/lastSegX)
* [`lastSegY`](kcl/lastSegY) * [`lastSegY`](kcl/lastSegY)
* [`legAngX`](kcl/legAngX) * [`legAngX`](kcl/legAngX)

View File

@ -4,6 +4,8 @@ excerpt: "Convert a number to an integer."
layout: manual layout: manual
--- ---
**WARNING:** This function is deprecated.
Convert a number to an integer. Convert a number to an integer.
DEPRECATED use floor(), ceil(), or round(). DEPRECATED use floor(), ceil(), or round().

View File

@ -75843,7 +75843,6 @@
"required": [ "required": [
"angleStart", "angleStart",
"axis", "axis",
"length",
"radius", "radius",
"revolutions" "revolutions"
], ],
@ -75864,9 +75863,10 @@
"type": "boolean" "type": "boolean"
}, },
"length": { "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", "type": "number",
"format": "double" "format": "double",
"nullable": true
}, },
"radius": { "radius": {
"description": "Radius of the helix.", "description": "Radius of the helix.",
@ -76961,8 +76961,9 @@
"unpublished": false, "unpublished": false,
"deprecated": false, "deprecated": false,
"examples": [ "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 "labelRequired": true
}, },
"unpublished": false, "unpublished": false,
"deprecated": false, "deprecated": true,
"examples": [ "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 }, %)" "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, "deprecated": false,
"examples": [ "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 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 }, %)"
] ]
}, },
{ {

File diff suppressed because one or more lines are too long

View File

@ -19,7 +19,7 @@ Data for a helix.
| `revolutions` |`number`| Number of revolutions. | No | | `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No | | `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | 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 | | `radius` |`number`| Radius of the helix. | No |
| `axis` |[`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference)| Axis to use as mirror. | No | | `axis` |[`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference)| Axis to use as mirror. | No |

View File

@ -121,18 +121,23 @@ export class AuthenticatedTronApp {
export const fixtures = { export const fixtures = {
cmdBar: async ({ page }: { page: Page }, use: any) => { cmdBar: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new CmdBarFixture(page)) await use(new CmdBarFixture(page))
}, },
editor: async ({ page }: { page: Page }, use: any) => { editor: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new EditorFixture(page)) await use(new EditorFixture(page))
}, },
toolbar: async ({ page }: { page: Page }, use: any) => { toolbar: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new ToolbarFixture(page)) await use(new ToolbarFixture(page))
}, },
scene: async ({ page }: { page: Page }, use: any) => { scene: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new SceneFixture(page)) await use(new SceneFixture(page))
}, },
homePage: async ({ page }: { page: Page }, use: any) => { homePage: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new HomePageFixture(page)) await use(new HomePageFixture(page))
}, },
} }

View File

@ -15,6 +15,7 @@ export class ToolbarFixture {
extrudeButton!: Locator extrudeButton!: Locator
loftButton!: Locator loftButton!: Locator
sweepButton!: Locator sweepButton!: Locator
chamferButton!: Locator
shellButton!: Locator shellButton!: Locator
offsetPlaneButton!: Locator offsetPlaneButton!: Locator
startSketchBtn!: Locator startSketchBtn!: Locator
@ -42,6 +43,7 @@ export class ToolbarFixture {
this.extrudeButton = page.getByTestId('extrude') this.extrudeButton = page.getByTestId('extrude')
this.loftButton = page.getByTestId('loft') this.loftButton = page.getByTestId('loft')
this.sweepButton = page.getByTestId('sweep') this.sweepButton = page.getByTestId('sweep')
this.chamferButton = page.getByTestId('chamfer3d')
this.shellButton = page.getByTestId('shell') this.shellButton = page.getByTestId('shell')
this.offsetPlaneButton = page.getByTestId('plane-offset') this.offsetPlaneButton = page.getByTestId('plane-offset')
this.startSketchBtn = page.getByTestId('sketch') this.startSketchBtn = page.getByTestId('sketch')

View File

@ -829,12 +829,6 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
}) })
await selectSketches() await selectSketches()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Selection: '2 faces' },
commandName: 'Loft',
})
await cmdBar.progressCmdBar()
}) })
} else { } else {
await test.step(`Preselect the two sketches`, async () => { 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 test.step(`Go through the command bar flow with preselected sketches`, async () => {
await toolbar.loftButton.click() await toolbar.loftButton.click()
await cmdBar.progressCmdBar() 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 = [ const shellPointAndClickCapCases = [
{ shouldPreselect: true }, { shouldPreselect: true },
{ shouldPreselect: false }, { shouldPreselect: false },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -8,8 +8,8 @@ import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
test.describe('Testing in-app sample loading', () => { test.describe('Testing in-app sample loading', () => {
/** /**
* Note this test implicitly depends on the KCL sample "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/car-wheel/car-wheel.kcl * 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 ({ test('Web: should overwrite current code, cannot create new file', async ({
editor, editor,
@ -29,8 +29,8 @@ test.describe('Testing in-app sample loading', () => {
// Locators and constants // Locators and constants
const newSample = { const newSample = {
file: 'car-wheel' + FILE_EXT, file: 'a-parametric-bearing-pillow-block' + FILE_EXT,
title: 'Car Wheel', title: 'A Parametric Bearing Pillow Block',
} }
const commandBarButton = page.getByRole('button', { name: 'Commands' }) const commandBarButton = page.getByRole('button', { name: 'Commands' })
const samplesCommandOption = page.getByRole('option', { 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: * 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 * "a-parametric-bearing-pillow-block": https://github.com/KittyCAD/kcl-samples/blob/main/a-parametric-bearing-pillow-block/main.kcl
* "gear-rack.kcl": https://github.com/KittyCAD/kcl-samples/blob/main/gear-rack/gear-rack.kcl * "gear-rack": https://github.com/KittyCAD/kcl-samples/blob/main/gear-rack/main.kcl
*/ */
test( test(
'Desktop: should create new file by default, optionally overwrite', 'Desktop: should create new file by default, optionally overwrite',
@ -93,8 +93,8 @@ test.describe('Testing in-app sample loading', () => {
// Locators and constants // Locators and constants
const sampleOne = { const sampleOne = {
file: 'car-wheel' + FILE_EXT, file: 'a-parametric-bearing-pillow-block' + FILE_EXT,
title: 'Car Wheel', title: 'A Parametric Bearing Pillow Block',
} }
const sampleTwo = { const sampleTwo = {
file: 'gear-rack' + FILE_EXT, file: 'gear-rack' + FILE_EXT,

View File

@ -906,53 +906,6 @@ test.describe('Testing selections', () => {
).not.toBeDisabled() ).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 removeAfterFirstParenthesis = (inputString: string) => {
const index = inputString.indexOf('(') const index = inputString.indexOf('(')
if (index !== -1) { if (index !== -1) {

View File

@ -65,7 +65,7 @@
"vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-protocol": "^3.17.5",
"vscode-uri": "^3.0.8", "vscode-uri": "^3.0.8",
"web-vitals": "^3.5.2", "web-vitals": "^3.5.2",
"xstate": "^5.17.4", "xstate": "^5.19.2",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"scripts": { "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", "build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"", "remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings", "wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
"lint-fix": "eslint --fix src/**/*.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 src/**/*.ts src/**/*.tsx e2e/**/*.ts packages/codemirror-lsp-client/src/**/*.ts", "lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json", "files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
"files:set-notes": "./scripts/set-files-notes.sh", "files:set-notes": "./scripts/set-files-notes.sh",
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh", "files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
@ -149,7 +149,7 @@
"@electron-forge/plugin-vite": "7.4.0", "@electron-forge/plugin-vite": "7.4.0",
"@electron/fuses": "1.8.0", "@electron/fuses": "1.8.0",
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.2",
"@nabla/vite-plugin-eslint": "^2.0.5", "@nabla/vite-plugin-eslint": "^2.0.5",
"@playwright/test": "^1.49.0", "@playwright/test": "^1.49.0",
"@testing-library/jest-dom": "^5.14.1", "@testing-library/jest-dom": "^5.14.1",
@ -183,8 +183,11 @@
"eslint-plugin-css-modules": "^2.12.0", "eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-import": "^2.30.0", "eslint-plugin-import": "^2.30.0",
"eslint-plugin-jest": "^28.10.0", "eslint-plugin-jest": "^28.10.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.3", "eslint-plugin-react": "^7.37.3",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-suggest-no-throw": "^1.0.0", "eslint-plugin-suggest-no-throw": "^1.0.0",
"eslint-plugin-testing-library": "^7.1.1",
"happy-dom": "^16.3.0", "happy-dom": "^16.3.0",
"http-server": "^14.1.1", "http-server": "^14.1.1",
"husky": "^9.1.5", "husky": "^9.1.5",
@ -198,7 +201,7 @@
"setimmediate": "^1.0.5", "setimmediate": "^1.0.5",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "^5.7.2", "typescript": "^5.7.3",
"typescript-eslint": "^8.19.1", "typescript-eslint": "^8.19.1",
"vite": "^5.4.6", "vite": "^5.4.6",
"vite-plugin-package-version": "^1.1.0", "vite-plugin-package-version": "^1.1.0",

View File

@ -4,4 +4,5 @@ dist
tsconfig.tsbuildinfo tsconfig.tsbuildinfo
*.d.ts *.d.ts
*.js *.js
!postcss.config.js
!rollup.config.js !rollup.config.js

View File

@ -28,6 +28,7 @@
"@rollup/plugin-typescript": "^12.1.2", "@rollup/plugin-typescript": "^12.1.2",
"rollup": "^4.29.1", "rollup": "^4.29.1",
"rollup-plugin-dts": "^6.1.1", "rollup-plugin-dts": "^6.1.1",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^2.1.8" "vitest": "^2.1.8"
}, },
"files": [ "files": [

View File

@ -0,0 +1 @@
// This is here to prevent using the one in the root of the project.

View File

@ -398,7 +398,7 @@ check-error@^2.1.1:
resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc"
integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==
debug@^4.3.7: debug@^4.1.1, debug@^4.3.7:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== 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" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== 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: hasown@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" 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" resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a"
integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== 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: typescript@^5.7.2:
version "5.7.2" version "5.7.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" 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" pathe "^1.1.2"
vite "^5.0.0" 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: vite@^5.0.0:
version "5.4.11" version "5.4.11"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5" resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5"

View File

@ -29,7 +29,7 @@
"vscode-uri": "^3.0.8" "vscode-uri": "^3.0.8"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.14.9", "@types/node": "^22.10.6",
"ts-node": "^10.9.2" "ts-node": "^10.9.2"
} }
} }

View File

@ -109,12 +109,12 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
"@types/node@^20.14.9": "@types/node@^22.10.6":
version "20.14.9" version "22.10.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.6.tgz#5c6795e71635876039f853cbccd59f523d9e4239"
integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg== integrity sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==
dependencies: dependencies:
undici-types "~5.26.4" undici-types "~6.20.0"
acorn-walk@^8.1.1: acorn-walk@^8.1.1:
version "8.3.3" version "8.3.3"
@ -187,10 +187,10 @@ typescript@^5.7.2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
undici-types@~5.26.4: undici-types@~6.20.0:
version "5.26.5" version "6.20.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
v8-compile-cache-lib@^3.0.1: v8-compile-cache-lib@^3.0.1:
version "3.0.1" version "3.0.1"

View File

@ -1,172 +1,212 @@
[ [
{ {
"file": "80-20-rail.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "80-20-rail/main.kcl",
"multipleFiles": false,
"title": "80/20 Rail", "title": "80/20 Rail",
"description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position" "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", "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." "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", "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." "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", "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." "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", "file": "main.kcl",
"title": "Brake Caliper", "pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl",
"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." "multipleFiles": true,
},
{
"file": "car-wheel.kcl",
"title": "Car Wheel",
"description": "A sports car wheel with a circular lug pattern and spokes."
},
{
"file": "car-wheel-assembly.kcl",
"title": "Car Wheel Assembly", "title": "Car Wheel Assembly",
"description": "A car wheel assembly with a rotor, tire, and lug nuts." "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", "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." "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", "title": "Enclosure",
"description": "An enclosure body and sealing lid for storing items" "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", "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." "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", "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." "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", "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" "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", "title": "Food Service Spatula",
"description": "Use these spatulas for mixing, flipping, and scraping." "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", "title": "French Press",
"description": "A french press immersion coffee maker" "description": "A french press immersion coffee maker"
}, },
{ {
"file": "gear.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "gear/main.kcl",
"multipleFiles": false,
"title": "Spur Gear", "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." "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", "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." "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", "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." "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", "title": "I-beam",
"description": "A structural metal beam with an I shaped cross section. Often used in construction" "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", "title": "Kitt",
"description": "The beloved KittyCAD mascot in a voxelized style." "description": "The beloved KittyCAD mascot in a voxelized style."
}, },
{ {
"file": "lego.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "lego/main.kcl",
"multipleFiles": false,
"title": "Lego Brick", "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." "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", "file": "main.kcl",
"title": "Lug Nut", "pathFromProjectDirectoryToFirstFile": "mounting-plate/main.kcl",
"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." "multipleFiles": false,
},
{
"file": "mounting-plate.kcl",
"title": "Mounting Plate", "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." "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", "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" "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", "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." "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", "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." "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", "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." "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", "title": "Poopy Shoe",
"description": "poop shute for bambu labs printer - optimized for printing." "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", "title": "Router template for a cross bar",
"description": "A guide for routing a notch into 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", "title": "Router template for a slate",
"description": "A guide for routing a slate for a cross bar." "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", "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." "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", "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." "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", "file": "main.kcl",
"title": "Tire", "pathFromProjectDirectoryToFirstFile": "walkie-talkie/main.kcl",
"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." "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", "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." "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."
} }
] ]

View File

@ -108,6 +108,8 @@ export class CameraControls {
interactionGuards: MouseGuard = cameraMouseDragGuards.Zoo interactionGuards: MouseGuard = cameraMouseDragGuards.Zoo
isFovAnimationInProgress = false isFovAnimationInProgress = false
perspectiveFovBeforeOrtho = 45 perspectiveFovBeforeOrtho = 45
// NOTE: Duplicated state across Provider and singleton. Mapped from settingsMachine
_setting_allowOrbitInSketchMode = false
get isPerspective() { get isPerspective() {
return this.camera instanceof PerspectiveCamera return this.camera instanceof PerspectiveCamera
} }

View File

@ -1,5 +1,6 @@
import { import {
BoxGeometry, BoxGeometry,
Color,
DoubleSide, DoubleSide,
Group, Group,
Intersection, Intersection,
@ -59,6 +60,7 @@ import {
resultIsOk, resultIsOk,
SourceRange, SourceRange,
} from 'lang/wasm' } from 'lang/wasm'
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
import { import {
engineCommandManager, engineCommandManager,
kclManager, kclManager,
@ -70,7 +72,7 @@ import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { executeAst, ToolTip } from 'lang/langHelpers' import { executeAst, ToolTip } from 'lang/langHelpers'
import { import {
createProfileStartHandle, createProfileStartHandle,
createArcGeometry, createCircleGeometry,
SegmentUtils, SegmentUtils,
segmentUtils, segmentUtils,
} from './segments' } from './segments'
@ -109,6 +111,8 @@ import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d' import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
import { SegmentInputs } from 'lang/std/stdTypes' import { SegmentInputs } from 'lang/std/stdTypes'
import { Node } from 'wasm-lib/kcl/bindings/Node' 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 { radToDeg } from 'three/src/math/MathUtils'
import { getArtifactFromRange, codeRefFromRange } from 'lang/std/artifactGraph' import { getArtifactFromRange, codeRefFromRange } from 'lang/std/artifactGraph'
@ -1261,110 +1265,98 @@ export class SceneEntities {
const groupOfDrafts = new Group() const groupOfDrafts = new Group()
groupOfDrafts.name = 'circle-3-point-group' groupOfDrafts.name = 'circle-3-point-group'
groupOfDrafts.position.copy(sketchOrigin) groupOfDrafts.position.copy(sketchOrigin)
// lee: I'm keeping this here as a developer gotchya: // lee: I'm keeping this here as a developer gotchya:
// Do not reorient your surfaces to the intersection plane. Your points are // If you use 3D points, do not rotate anything.
// already in 3D space, not 2D. If you intersect say XZ, you want the points // If you use 2D points (easier to deal with, generally do this!), then
// to continue to live at the 3D intersection point, not be rotated to end // rotate the group just like this! Remember to rotate other groups too!
// up elsewhere! groupOfDrafts.setRotationFromQuaternion(orientation)
// groupOfDrafts.setRotationFromQuaternion(orientation)
this.scene.add(groupOfDrafts) 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 geometry = new SphereGeometry(DRAFT_POINT_RADIUS)
const color = getThemeColorForThreeJs(sceneInfra._theme) 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) 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.layers.set(SKETCH_LAYER)
mesh.position.copy(center) mesh.position.copy(center)
mesh.scale.set(scale, scale, scale) mesh.scale.set(scale, scale, scale)
mesh.renderOrder = 100 mesh.renderOrder = 100
groupOfDrafts.add(mesh) return mesh
return mesh.id
} }
const circle3Point = ( const createCircle3PointGraphic = async (
points: Vector2[] points: Vector2[],
): undefined | { center: Vector3; radius: number } => { center: Vector2,
// A 3-point circle is undefined if it doesn't have 3 points :) radius: number
if (points.length !== 3) return undefined ) => {
if (
// y = (i/j)(x-h) + b Number.isNaN(radius) ||
// i and j variables for the slopes Number.isNaN(center.x) ||
const i = [points[1].x - points[0].x, points[2].x - points[1].x] Number.isNaN(center.y)
const j = [points[1].y - points[0].y, points[2].y - points[1].y] )
return
// 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 color = getThemeColorForThreeJs(sceneInfra._theme) const color = getThemeColorForThreeJs(sceneInfra._theme)
const geometryCircle = createArcGeometry({ const lineCircle = createCircleGeometry({
center: [circleParams.center.x, circleParams.center.y], center: [center.x, center.y],
radius: circleParams.radius, radius,
startAngle: 0, color,
endAngle: Math.PI * 2, isDashed: false,
ccw: true, scale: 1,
isDashed: true,
scale,
}) })
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) if (groupCircle) groupOfDrafts.remove(groupCircle)
groupCircle = new Group() groupCircle = new Group()
groupCircle.renderOrder = 1 groupCircle.renderOrder = 1
groupCircle.add(lineCircle)
const meshCircle = new Mesh(geometryCircle, materialCircle) const pointMesh = createPoint(new Vector3(center.x, center.y, 0), {
meshCircle.userData = { type: CIRCLE_3_POINT_DRAFT_CIRCLE } noInteraction: true,
meshCircle.layers.set(SKETCH_LAYER) })
meshCircle.position.set(circleParams.center.x, circleParams.center.y, 0) groupCircle.add(pointMesh)
meshCircle.scale.set(scale, scale, scale)
groupCircle.add(meshCircle)
const geometryPolyLine = new BufferGeometry().setFromPoints([ const geometryPolyLine = new BufferGeometry().setFromPoints([
...points, ...points.map((p) => new Vector3(p.x, p.y, 0)),
points[0], new Vector3(points[0].x, points[0].y, 0),
]) ])
const materialPolyLine = new LineDashedMaterial({ const materialPolyLine = new LineDashedMaterial({
color, color,
scale, scale: 1 / scale,
dashSize: 6, dashSize: 6,
gapSize: 6, gapSize: 6,
}) })
@ -1375,13 +1367,146 @@ export class SceneEntities {
groupOfDrafts.add(groupCircle) groupOfDrafts.add(groupCircle)
} }
// The target of our dragging const insertCircle3PointKclIntoAstSnapshot = (
let target: Object3D | undefined = undefined 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 = () => { const cleanupFn = () => {
this.scene.remove(groupOfDrafts) 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({ sceneInfra.setCallbacks({
async onDrag(args) { async onDrag(args) {
const draftPointsIntersected = args.intersects.filter( const draftPointsIntersected = args.intersects.filter(
@ -1397,8 +1522,18 @@ export class SceneEntities {
// The user was off their mark! Missed the object to select. // The user was off their mark! Missed the object to select.
if (!target) return 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) points.set(target.id, args.intersectionPoint.twoD)
if (points.size <= 2) return
await updateCircle3Point()
}, },
async onDragEnd(_args) { async onDragEnd(_args) {
target = undefined target = undefined
@ -1407,45 +1542,19 @@ export class SceneEntities {
if (points.size >= 3) return if (points.size >= 3) return
if (!args.intersectionPoint) return if (!args.intersectionPoint) return
const id = createPoint(args.intersectionPoint.threeD) const pointMesh = createPoint(
points.set(id, args.intersectionPoint.twoD) new Vector3(
args.intersectionPoint.twoD.x,
if (points.size < 2) return args.intersectionPoint.twoD.y,
0
// We've now got 3 points, let's create our circle! )
const astSnapshot = structuredClone(kclManager.ast)
let nodeQueryResult
nodeQueryResult = getNodeFromPath<VariableDeclaration>(
astSnapshot,
startSketchOnASTNodePath,
'VariableDeclaration'
) )
if (err(nodeQueryResult)) return Promise.reject(nodeQueryResult) groupOfDrafts.add(pointMesh)
const startSketchOnASTNode = nodeQueryResult points.set(pointMesh.id, args.intersectionPoint.twoD)
const circleParams = circle3Point(Array.from(points.values())) if (points.size <= 2) return
if (!circleParams) return await updateCircle3Point()
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()
}, },
}) })

View File

@ -9,6 +9,9 @@ import {
ExtrudeGeometry, ExtrudeGeometry,
Group, Group,
LineCurve3, LineCurve3,
LineBasicMaterial,
LineDashedMaterial,
Line,
Mesh, Mesh,
MeshBasicMaterial, MeshBasicMaterial,
NormalBufferAttributes, NormalBufferAttributes,
@ -1003,6 +1006,49 @@ export function createArcGeometry({
return geo 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( export function dashedStraight(
from: Coords2d, from: Coords2d,
to: Coords2d, to: Coords2d,

View File

@ -22,6 +22,7 @@ export const CommandBar = () => {
// Close the command bar when navigating // Close the command bar when navigating
useEffect(() => { useEffect(() => {
if (commandBarState.matches('Closed')) return
commandBarSend({ type: 'Close' }) commandBarSend({ type: 'Close' })
}, [pathname]) }, [pathname])

View File

@ -4,6 +4,8 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
import { Command } from 'lib/commandTypes' import { Command } from 'lib/commandTypes'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { getActorNextEvents } from 'lib/utils'
import { sortCommands } from 'lib/commandUtils'
function CommandComboBox({ function CommandComboBox({
options, options,
@ -18,8 +20,16 @@ function CommandComboBox({
const defaultOption = const defaultOption =
options.find((o) => 'isCurrent' in o && o.isCurrent) || null 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'], keys: ['displayName', 'name', 'description'],
threshold: 0.3, threshold: 0.3,
ignoreLocation: true, ignoreLocation: true,
@ -27,7 +37,7 @@ function CommandComboBox({
useEffect(() => { useEffect(() => {
const results = fuse.search(query).map((result) => result.item) const results = fuse.search(query).map((result) => result.item)
setFilteredOptions(query.length > 0 ? results : options) setFilteredOptions(query.length > 0 ? results : sortedOptions)
}, [query]) }, [query])
function handleSelection(command: Command) { function handleSelection(command: Command) {
@ -73,7 +83,8 @@ function CommandComboBox({
<Combobox.Option <Combobox.Option
key={option.groupId + option.name + (option.displayName || '')} key={option.groupId + option.name + (option.displayName || '')}
value={option} 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 && ( {'icon' in option && option.icon && (
<CustomIcon name={option.icon} className="w-5 h-5" /> <CustomIcon name={option.icon} className="w-5 h-5" />
@ -96,3 +107,11 @@ function CommandComboBox({
} }
export default CommandComboBox export default CommandComboBox
function optionIsDisabled(option: Command): boolean {
return (
'machineActor' in option &&
option.machineActor !== undefined &&
!getActorNextEvents(option.machineActor.getSnapshot()).includes(option.name)
)
}

View File

@ -538,6 +538,16 @@ const CustomIconMap = {
/> />
</svg> </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: ( hole: (
<svg <svg
viewBox="0 0 20 20" viewBox="0 0 20 20"

View File

@ -57,7 +57,9 @@ export const FileMachineProvider = ({
useEffect(() => { useEffect(() => {
markOnce('code/didLoadFile') markOnce('code/didLoadFile')
async function fetchKclSamples() { async function fetchKclSamples() {
setKclSamples(await getKclSamplesManifest()) const manifest = await getKclSamplesManifest()
const filteredFiles = manifest.filter((file) => !file.multipleFiles)
setKclSamples(filteredFiles)
} }
fetchKclSamples().catch(reportError) fetchKclSamples().catch(reportError)
}, []) }, [])
@ -324,7 +326,7 @@ export const FileMachineProvider = ({
} }
}, },
kclSamples.map((sample) => ({ kclSamples.map((sample) => ({
value: sample.file, value: sample.pathFromProjectDirectoryToFirstFile,
name: sample.title, name: sample.title,
})) }))
).filter( ).filter(

View File

@ -148,6 +148,7 @@ function HelpMenuItem({
return ( return (
<li className="p-0 m-0"> <li className="p-0 m-0">
{as === 'a' ? ( {as === 'a' ? (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<a <a
{...(props as React.ComponentProps<'a'>)} {...(props as React.ComponentProps<'a'>)}
onClick={openExternalBrowserIfDesktop( onClick={openExternalBrowserIfDesktop(

View File

@ -111,7 +111,7 @@ export const ModelingMachineProvider = ({
auth, auth,
settings: { settings: {
context: { context: {
app: { theme, enableSSAO }, app: { theme, enableSSAO, allowOrbitInSketchMode },
modeling: { modeling: {
defaultUnit, defaultUnit,
cameraProjection, cameraProjection,
@ -121,6 +121,7 @@ export const ModelingMachineProvider = ({
}, },
}, },
} = useSettingsAuthContext() } = useSettingsAuthContext()
const previousAllowOrbitInSketchMode = useRef(allowOrbitInSketchMode.current)
const navigate = useNavigate() const navigate = useNavigate()
const { context, send: fileMachineSend } = useFileContext() const { context, send: fileMachineSend } = useFileContext()
const { file } = useLoaderData() as IndexLoaderData const { file } = useLoaderData() as IndexLoaderData
@ -634,7 +635,8 @@ export const ModelingMachineProvider = ({
input.plane input.plane
) )
await kclManager.updateAst(modifiedAst, false) await kclManager.updateAst(modifiedAst, false)
sceneInfra.camControls.enableRotate = false sceneInfra.camControls.enableRotate =
sceneInfra.camControls._setting_allowOrbitInSketchMode
sceneInfra.camControls.syncDirection = 'clientToEngine' sceneInfra.camControls.syncDirection = 'clientToEngine'
await letEngineAnimateAndSyncCamAfter( await letEngineAnimateAndSyncCamAfter(
@ -647,6 +649,7 @@ export const ModelingMachineProvider = ({
zAxis: input.zAxis, zAxis: input.zAxis,
yAxis: input.yAxis, yAxis: input.yAxis,
origin: [0, 0, 0], origin: [0, 0, 0],
animateTargetId: input.planeId,
} }
}), }),
'animate-to-sketch': fromPromise( 'animate-to-sketch': fromPromise(
@ -671,6 +674,7 @@ export const ModelingMachineProvider = ({
origin: info.sketchDetails.origin.map( origin: info.sketchDetails.origin.map(
(a) => a / sceneInfra._baseUnitMultiplier (a) => a / sceneInfra._baseUnitMultiplier
) as [number, number, number], ) as [number, number, number],
animateTargetId: info?.sketchDetails?.faceId || '',
} }
} }
), ),
@ -1188,6 +1192,41 @@ export const ModelingMachineProvider = ({
} }
}, [engineCommandManager.engineConnection, modelingSend]) }, [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 // Allow using the delete key to delete solids
useHotkeys(['backspace', 'delete', 'del'], () => { useHotkeys(['backspace', 'delete', 'del'], () => {
modelingSend({ type: 'Delete selection' }) modelingSend({ type: 'Delete selection' })

View File

@ -18,6 +18,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
return ( return (
<Menu> <Menu>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div <div
className="relative" className="relative"
onClick={(e) => { onClick={(e) => {

View File

@ -137,6 +137,11 @@ export const SettingsAuthProviderBase = ({
sceneInfra.theme = opposingTheme sceneInfra.theme = opposingTheme
sceneEntitiesManager.updateSegmentBaseColor(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 }) => { toastSuccess: ({ event }) => {
if (!('data' in event)) return if (!('data' in event)) return
const eventParts = event.type.replace(/^set./, '').split('.') as [ const eventParts = event.type.replace(/^set./, '').split('.') as [

View File

@ -313,6 +313,7 @@ export const Stream = () => {
} }
return ( return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div <div
ref={videoWrapperRef} ref={videoWrapperRef}
className="absolute inset-0 z-0" className="absolute inset-0 z-0"

View File

@ -1,5 +1,5 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { AnyStateMachine, Actor, StateFrom } from 'xstate' import { AnyStateMachine, Actor, StateFrom, EventFrom } from 'xstate'
import { createMachineCommand } from '../lib/createMachineCommand' import { createMachineCommand } from '../lib/createMachineCommand'
import { useCommandsContext } from './useCommandsContext' import { useCommandsContext } from './useCommandsContext'
import { modelingMachine } from 'machines/modelingMachine' import { modelingMachine } from 'machines/modelingMachine'
@ -15,7 +15,6 @@ import { useKclContext } from 'lang/KclProvider'
import { useNetworkContext } from 'hooks/useNetworkContext' import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus' import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { useAppState } from 'AppState' import { useAppState } from 'AppState'
import { getActorNextEvents } from 'lib/utils'
// This might not be necessary, AnyStateMachine from xstate is working // This might not be necessary, AnyStateMachine from xstate is working
export type AllMachines = export type AllMachines =
@ -60,21 +59,21 @@ export default function useStateMachineCommands<
overallState !== NetworkHealthState.Weak) || overallState !== NetworkHealthState.Weak) ||
isExecuting || isExecuting ||
!isStreamReady !isStreamReady
const newCommands = getActorNextEvents(state) const newCommands = Object.keys(commandBarConfig || {})
.filter((_) => !allCommandsRequireNetwork || !disableAllButtons) .filter((_) => !allCommandsRequireNetwork || !disableAllButtons)
.filter((e) => !['done.', 'error.'].some((n) => e.includes(n))) .flatMap((type) => {
.flatMap((type) => const typeWithProperType = type as EventFrom<T>['type']
createMachineCommand<T, S>({ return createMachineCommand<T, S>({
// The group is the owner machine's ID. // The group is the owner machine's ID.
groupId: machineId, groupId: machineId,
type, type: typeWithProperType,
state, state,
send, send,
actor, actor,
commandBarConfig, commandBarConfig,
onCancel, onCancel,
}) })
) })
.filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls .filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls
commandBarSend({ type: 'Add commands', data: { commands: newCommands } }) commandBarSend({ type: 'Add commands', data: { commands: newCommands } })
@ -85,5 +84,5 @@ export default function useStateMachineCommands<
data: { commands: newCommands }, data: { commands: newCommands },
}) })
} }
}, [state, overallState, isExecuting, isStreamReady]) }, [overallState, isExecuting, isStreamReady])
} }

View File

@ -1,79 +1,81 @@
import { assertParse, initPromise, programMemoryInit } from './wasm' import { assertParse, initPromise, programMemoryInit } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers' 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 { interface KclSampleFile {
file: string file: string
pathFromProjectDirectoryToFirstFile: string
title: string title: string
filename: string filename: string
description: 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 () => { beforeAll(async () => {
await initPromise await initPromise
}) })
// Only used to actually fetch an older version of KCL code that will break in the parser. afterAll(async () => {
/* eslint-disable @typescript-eslint/no-unused-vars */ try {
async function getBrokenSampleCodeForLocalTesting() { process.chdir('..')
const result = await fetch( await fs.rm(DIR_KCL_SAMPLES, { recursive: true })
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/5ccd04a1773ebdbfd02684057917ce5dbe0eaab3/80-20-rail.kcl' } catch (e) {}
) })
const text = await result.text()
return text
}
async function getKclSampleCodeFromGithub(file: string): Promise<string> { afterEach(() => {
const result = await fetch( process.chdir('..')
`https://raw.githubusercontent.com/KittyCAD/kcl-samples/refs/heads/main/${file}/${file}.kcl` })
)
const text = await result.text()
return text
}
async function getFileNamesFromManifestJSON(): Promise<KclSampleFile[]> { // The tests have to be sequential because we need to change directories
const result = await fetch( // to support `import` working properly.
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/refs/heads/main/manifest.json' // @ts-expect-error
) describe.sequential('Test KCL Samples from public Github repository', () => {
const json = await result.json() // @ts-expect-error
json.forEach((file: KclSampleFile) => { describe.sequential('when performing enginelessExecutor', () => {
const filenameWithoutExtension = file.file.split('.')[0] manifest.forEach((file: KclSampleFile) => {
file.filename = filenameWithoutExtension // @ts-expect-error
}) it.sequential(
return json `should execute ${file.title} (${file.file}) successfully`,
} async () => {
const [dirProject, fileKcl] =
// Value to use across all tests! file.pathFromProjectDirectoryToFirstFile.split('/')
let files: KclSampleFile[] = [] process.chdir(dirProject)
const code = await fs.readFile(fileKcl, 'utf-8')
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)
const ast = assertParse(code) const ast = assertParse(code)
await enginelessExecutor(ast, programMemoryInit()) await enginelessExecutor(ast, programMemoryInit())
} },
}, files.length * 1000
files.length * 1000 )
) })
}) })
}) })

View File

@ -705,7 +705,7 @@ describe('testing getArtifactsToUpdate', () => {
segIds: [], segIds: [],
id: expect.any(String), id: expect.any(String),
planeId: 'UUID-1', planeId: 'UUID-1',
sweepId: '', sweepId: undefined,
codeRef: { codeRef: {
pathToNode: [['body', '']], pathToNode: [['body', '']],
range: [37, 64, true], range: [37, 64, true],
@ -743,7 +743,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
id: expect.any(String), id: expect.any(String),
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: '', surfaceId: undefined,
edgeIds: [], edgeIds: [],
codeRef: { codeRef: {
range: [70, 86, true], range: [70, 86, true],
@ -770,7 +770,7 @@ describe('testing getArtifactsToUpdate', () => {
id: expect.any(String), id: expect.any(String),
consumedEdgeId: expect.any(String), consumedEdgeId: expect.any(String),
edgeIds: [], edgeIds: [],
surfaceId: '', surfaceId: undefined,
codeRef: { codeRef: {
range: [260, 299, true], range: [260, 299, true],
pathToNode: [['body', '']], pathToNode: [['body', '']],

View File

@ -37,7 +37,7 @@ export interface PathArtifact extends BaseArtifact {
type: 'path' type: 'path'
planeId: ArtifactId planeId: ArtifactId
segIds: Array<ArtifactId> segIds: Array<ArtifactId>
sweepId: ArtifactId sweepId?: ArtifactId
solid2dId?: ArtifactId solid2dId?: ArtifactId
codeRef: CodeRef codeRef: CodeRef
} }
@ -60,7 +60,7 @@ export interface PathArtifactRich extends BaseArtifact {
export interface SegmentArtifact extends BaseArtifact { export interface SegmentArtifact extends BaseArtifact {
type: 'segment' type: 'segment'
pathId: ArtifactId pathId: ArtifactId
surfaceId: ArtifactId surfaceId?: ArtifactId
edgeIds: Array<ArtifactId> edgeIds: Array<ArtifactId>
edgeCutId?: ArtifactId edgeCutId?: ArtifactId
codeRef: CodeRef codeRef: CodeRef
@ -68,7 +68,7 @@ export interface SegmentArtifact extends BaseArtifact {
interface SegmentArtifactRich extends BaseArtifact { interface SegmentArtifactRich extends BaseArtifact {
type: 'segment' type: 'segment'
path: PathArtifact path: PathArtifact
surf: WallArtifact surf?: WallArtifact
edges: Array<SweepEdge> edges: Array<SweepEdge>
edgeCut?: EdgeCut edgeCut?: EdgeCut
codeRef: CodeRef codeRef: CodeRef
@ -120,7 +120,7 @@ interface EdgeCut extends BaseArtifact {
subType: 'fillet' | 'chamfer' subType: 'fillet' | 'chamfer'
consumedEdgeId: ArtifactId consumedEdgeId: ArtifactId
edgeIds: Array<ArtifactId> edgeIds: Array<ArtifactId>
surfaceId: ArtifactId surfaceId?: ArtifactId
codeRef: CodeRef codeRef: CodeRef
} }
@ -308,7 +308,7 @@ export function getArtifactsToUpdate({
id, id,
segIds: [], segIds: [],
planeId: currentPlaneId, planeId: currentPlaneId,
sweepId: '', sweepId: undefined,
codeRef: { range, pathToNode }, codeRef: { range, pathToNode },
}, },
}) })
@ -343,7 +343,7 @@ export function getArtifactsToUpdate({
type: 'segment', type: 'segment',
id, id,
pathId, pathId,
surfaceId: '', surfaceId: undefined,
edgeIds: [], edgeIds: [],
codeRef: { range, pathToNode }, codeRef: { range, pathToNode },
}, },
@ -450,7 +450,8 @@ export function getArtifactsToUpdate({
id: face_id, id: face_id,
segId: curve_id, segId: curve_id,
edgeCutEdgeIds: [], edgeCutEdgeIds: [],
sweepId: path.sweepId, // TODO: Add explicit check for sweepId. Should never use ''
sweepId: path.sweepId ?? '',
pathIds: [], pathIds: [],
}, },
}) })
@ -458,15 +459,17 @@ export function getArtifactsToUpdate({
id: curve_id, id: curve_id,
artifact: { ...seg, surfaceId: face_id }, artifact: { ...seg, surfaceId: face_id },
}) })
const sweep = getArtifact(path.sweepId) if (path.sweepId) {
if (sweep?.type === 'sweep') { const sweep = getArtifact(path.sweepId)
returnArr.push({ if (sweep?.type === 'sweep') {
id: path.sweepId, returnArr.push({
artifact: { id: path.sweepId,
...sweep, artifact: {
surfaceIds: [face_id], ...sweep,
}, surfaceIds: [face_id],
}) },
})
}
} }
} }
} }
@ -483,19 +486,22 @@ export function getArtifactsToUpdate({
id: face_id, id: face_id,
subType: cap === 'bottom' ? 'start' : 'end', subType: cap === 'bottom' ? 'start' : 'end',
edgeCutEdgeIds: [], edgeCutEdgeIds: [],
sweepId: path.sweepId, // TODO: Add explicit check for sweepId. Should never use ''
sweepId: path.sweepId ?? '',
pathIds: [], pathIds: [],
}, },
}) })
const sweep = getArtifact(path.sweepId) if (path.sweepId) {
if (sweep?.type !== 'sweep') return const sweep = getArtifact(path.sweepId)
returnArr.push({ if (sweep?.type !== 'sweep') return
id: path.sweepId, returnArr.push({
artifact: { id: path.sweepId,
...sweep, artifact: {
surfaceIds: [face_id], ...sweep,
}, surfaceIds: [face_id],
}) },
})
}
} }
} }
}) })
@ -533,7 +539,8 @@ export function getArtifactsToUpdate({
? 'adjacent' ? 'adjacent'
: 'opposite', : 'opposite',
segId: cmd.edge_id, 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: { artifact: {
...sweep, ...sweep,
edgeIds: [response.data.modeling_response.data.edge], edgeIds: [response.data.modeling_response.data.edge],
@ -560,7 +567,7 @@ export function getArtifactsToUpdate({
subType: cmd.cut_type, subType: cmd.cut_type,
consumedEdgeId: cmd.edge_id, consumedEdgeId: cmd.edge_id,
edgeIds: [], edgeIds: [],
surfaceId: '', surfaceId: undefined,
codeRef: { range, pathToNode }, codeRef: { range, pathToNode },
}, },
}) })
@ -722,10 +729,12 @@ export function expandSegment(
{ key: segment.pathId, types: ['path'] }, { key: segment.pathId, types: ['path'] },
artifactGraph artifactGraph
) )
const surf = getArtifactOfTypes( const surf = segment.surfaceId
{ key: segment.surfaceId, types: ['wall'] }, ? getArtifactOfTypes(
artifactGraph { key: segment.surfaceId, types: ['wall'] },
) artifactGraph
)
: undefined
const edges = getArtifactsOfTypes( const edges = getArtifactsOfTypes(
{ keys: segment.edgeIds, types: ['sweepEdge'] }, { keys: segment.edgeIds, types: ['sweepEdge'] },
artifactGraph artifactGraph
@ -842,6 +851,7 @@ export function getSweepFromSuspectedSweepSurface(
artifactGraph artifactGraph
) )
if (err(path)) return path if (err(path)) return path
if (!path.sweepId) return new Error('Path does not have a sweepId')
return getArtifactOfTypes( return getArtifactOfTypes(
{ key: path.sweepId, types: ['sweep'] }, { key: path.sweepId, types: ['sweep'] },
artifactGraph artifactGraph
@ -859,6 +869,7 @@ export function getSweepFromSuspectedPath(
): SweepArtifact | Error { ): SweepArtifact | Error {
const path = getArtifactOfTypes({ key: id, types: ['path'] }, artifactGraph) const path = getArtifactOfTypes({ key: id, types: ['path'] }, artifactGraph)
if (err(path)) return path if (err(path)) return path
if (!path.sweepId) return new Error('Path does not have a sweepId')
return getArtifactOfTypes( return getArtifactOfTypes(
{ key: path.sweepId, types: ['sweep'] }, { key: path.sweepId, types: ['sweep'] },
artifactGraph artifactGraph

View File

@ -1,5 +1,21 @@
import { isDesktop } from 'lib/isDesktop' 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. /// 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 /// It assumes that you are in a project since it is solely used by the std lib
/// when executing code. /// when executing code.
@ -19,13 +35,9 @@ class FileSystemManager {
} }
async readFile(path: string): Promise<Uint8Array> { async readFile(path: string): Promise<Uint8Array> {
// Using local file system only works from desktop. // Using local file system only works from desktop and nodejs
if (!isDesktop()) { if (!window?.electron?.readFile) {
return Promise.reject( return Promise.reject(new Error('No polyfill found for this function'))
new Error(
'This function can only be called from the desktop application'
)
)
} }
return this.join(this.dir, path).then((filePath) => { return this.join(this.dir, path).then((filePath) => {
@ -35,12 +47,8 @@ class FileSystemManager {
async exists(path: string): Promise<boolean | void> { async exists(path: string): Promise<boolean | void> {
// Using local file system only works from desktop. // Using local file system only works from desktop.
if (!isDesktop()) { if (!window?.electron?.stat) {
return Promise.reject( return Promise.reject(new Error('No polyfill found for this function'))
new Error(
'This function can only be called from the desktop application'
)
)
} }
return this.join(this.dir, path).then(async (file) => { return this.join(this.dir, path).then(async (file) => {
@ -57,12 +65,8 @@ class FileSystemManager {
async getAllFiles(path: string): Promise<string[] | void> { async getAllFiles(path: string): Promise<string[] | void> {
// Using local file system only works from desktop. // Using local file system only works from desktop.
if (!isDesktop()) { if (!window?.electron?.readdir) {
return Promise.reject( return Promise.reject(new Error('No polyfill found for this function'))
new Error(
'This function can only be called from the desktop application'
)
)
} }
return this.join(this.dir, path).then((filepath) => { return this.join(this.dir, path).then((filepath) => {

View File

@ -56,10 +56,13 @@ export type ModelingCommandSchema = {
edge: Selections edge: Selections
} }
Fillet: { Fillet: {
// todo
selection: Selections selection: Selections
radius: KclCommandValue radius: KclCommandValue
} }
Chamfer: {
selection: Selections
length: KclCommandValue
}
'Offset plane': { 'Offset plane': {
plane: Selections plane: Selections
distance: KclCommandValue distance: KclCommandValue
@ -326,7 +329,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
Loft: { Loft: {
description: 'Create a 3D body by blending between two or more sketches', description: 'Create a 3D body by blending between two or more sketches',
icon: 'loft', icon: 'loft',
needsReview: true, needsReview: false,
args: { args: {
selection: { selection: {
inputType: 'selection', inputType: 'selection',
@ -429,7 +432,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
}, },
Fillet: { Fillet: {
description: 'Fillet edge', description: 'Fillet edge',
icon: 'fillet', icon: 'fillet3d',
status: 'development', status: 'development',
needsReview: true, needsReview: true,
args: { 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': { 'Constrain length': {
description: 'Constrain the length of one or more segments.', description: 'Constrain the length of one or more segments.',
icon: 'dimension', icon: 'dimension',

View File

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

View File

@ -76,6 +76,7 @@ export type Command<
| (( | ((
commandBarContext: { argumentsToSubmit: Record<string, unknown> } // Should be the commandbarMachine's context, but it creates a circular dependency commandBarContext: { argumentsToSubmit: Record<string, unknown> } // Should be the commandbarMachine's context, but it creates a circular dependency
) => string | ReactNode) ) => string | ReactNode)
machineActor?: Actor<T>
onSubmit: (data?: CommandSchema) => void onSubmit: (data?: CommandSchema) => void
onCancel?: () => void onCancel?: () => void
args?: { args?: {
@ -95,7 +96,7 @@ export type CommandConfig<
Command<T, CommandName, CommandSchema>, Command<T, CommandName, CommandSchema>,
'name' | 'groupId' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview' 'name' | 'groupId' | 'onSubmit' | 'onCancel' | 'args' | 'needsReview'
> & { > & {
needsReview?: true needsReview?: boolean
status?: 'active' | 'development' | 'inactive' status?: 'active' | 'development' | 'inactive'
args?: { args?: {
[ArgName in keyof CommandSchema]: CommandArgumentConfig< [ArgName in keyof CommandSchema]: CommandArgumentConfig<

View 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')
})
})

View File

@ -2,6 +2,9 @@
// That object also contains some metadata about what to do with the KCL expression, // 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. // such as whether we need to create a new variable for it.
// This function extracts the value field from those arg payloads and returns // 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. // 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>) { export function getCommandArgumentKclValuesOnly(args: Record<string, unknown>) {
return Object.fromEntries( 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
)
}

View File

@ -96,6 +96,7 @@ export function createMachineCommand<
icon, icon,
description: commandConfig.description, description: commandConfig.description,
needsReview: commandConfig.needsReview || false, needsReview: commandConfig.needsReview || false,
machineActor: actor,
onSubmit: (data?: S[typeof type]) => { onSubmit: (data?: S[typeof type]) => {
if (data !== undefined && data !== null) { if (data !== undefined && data !== null) {
send({ type, data }) send({ type, data })

View File

@ -3,6 +3,8 @@ import { isDesktop } from './isDesktop'
export type KclSamplesManifestItem = { export type KclSamplesManifestItem = {
file: string file: string
pathFromProjectDirectoryToFirstFile: string
multipleFiles: boolean
title: string title: string
description: string description: string
} }

View File

@ -49,20 +49,30 @@ export function kclCommands(
if (!data?.sample) { if (!data?.sample) {
return 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( const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
data.sample.replace(FILE_EXT, '') projectPathPart
)}/${encodeURIComponent(data.sample)}` )}/${encodeURIComponent(primaryKclFile)}`
const sampleSettingsFileUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent( const sampleSettingsFileUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
data.sample.replace(FILE_EXT, '') projectPathPart
)}/${PROJECT_SETTINGS_FILE_NAME}` )}/${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( .then(
async ([ async ([
codeResponse, codeResponse,
settingsResponse, settingsResponse,
]): Promise<OnSubmitProps> => { ]): Promise<OnSubmitProps> => {
if (!(codeResponse.ok && settingsResponse.ok)) { if (!codeResponse.ok) {
console.error( console.error(
'Failed to fetch sample code:', 'Failed to fetch sample code:',
codeResponse.statusText codeResponse.statusText
@ -70,20 +80,24 @@ export function kclCommands(
return Promise.reject(new Error('Failed to fetch sample code')) return Promise.reject(new Error('Failed to fetch sample code'))
} }
const code = await codeResponse.text() 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< let projectSettingsPayload: ReturnType<
typeof projectConfigurationToSettingsPayload typeof projectConfigurationToSettingsPayload
> = {} > = {}
if (!err(parsedProjectSettings)) { if (settingsResponse.ok) {
projectSettingsPayload = projectConfigurationToSettingsPayload( const parsedProjectSettings = parseProjectSettings(
parsedProjectSettings await settingsResponse.text()
) )
if (!err(parsedProjectSettings)) {
projectSettingsPayload =
projectConfigurationToSettingsPayload(parsedProjectSettings)
}
} }
return { return {
sampleName: data.sample, sampleName: data.sample.split('/')[0] + FILE_EXT,
code, code,
method: data.method, method: data.method,
sampleUnits: sampleUnits:

View File

@ -19,6 +19,10 @@ const stdLibMap: Record<string, StdLibCallInfo> = {
label: 'Fillet', label: 'Fillet',
icon: 'fillet3d', icon: 'fillet3d',
}, },
helix: {
label: 'Helix',
icon: 'helix',
},
hole: { hole: {
label: 'Hole', label: 'Hole',
icon: 'hole', icon: 'hole',

View File

@ -137,7 +137,7 @@ See later source ranges for more context. about the sweep`,
{ key: artifact.pathId, types: ['path'] }, { key: artifact.pathId, types: ['path'] },
artifactGraph artifactGraph
) )
if (!err(path)) { if (!err(path) && path.sweepId) {
const sweep = getArtifactOfTypes( const sweep = getArtifactOfTypes(
{ key: path.sweepId, types: ['sweep'] }, { key: path.sweepId, types: ['sweep'] },
artifactGraph artifactGraph

View File

@ -670,6 +670,7 @@ export function codeToIdSelections(
} }
} }
if (type === 'extrude-wall' && entry.artifact.type === 'segment') { if (type === 'extrude-wall' && entry.artifact.type === 'segment') {
if (!entry.artifact.surfaceId) return
const wall = engineCommandManager.artifactGraph.get( const wall = engineCommandManager.artifactGraph.get(
entry.artifact.surfaceId entry.artifact.surfaceId
) )
@ -714,6 +715,7 @@ export function codeToIdSelections(
(type === 'end-cap' || type === 'start-cap') && (type === 'end-cap' || type === 'start-cap') &&
entry.artifact.type === 'path' entry.artifact.type === 'path'
) { ) {
if (!entry.artifact.sweepId) return
const extrusion = getArtifactOfTypes( const extrusion = getArtifactOfTypes(
{ {
key: entry.artifact.sweepId, key: entry.artifact.sweepId,

View File

@ -190,6 +190,14 @@ export function createSettings() {
inputType: 'boolean', 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>({ onboardingStatus: new Setting<OnboardingStatus>({
defaultValue: '', defaultValue: '',
// TODO: this could be better but we don't have a TS side real enum // TODO: this could be better but we don't have a TS side real enum

View File

@ -41,6 +41,8 @@ export function configurationToSettingsPayload(
onboardingStatus: configuration?.settings?.app?.onboarding_status, onboardingStatus: configuration?.settings?.app?.onboarding_status,
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner, dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
streamIdleMode: configuration?.settings?.app?.stream_idle_mode, streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
allowOrbitInSketchMode:
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
projectDirectory: configuration?.settings?.project?.directory, projectDirectory: configuration?.settings?.project?.directory,
enableSSAO: configuration?.settings?.modeling?.enable_ssao, enableSSAO: configuration?.settings?.modeling?.enable_ssao,
}, },
@ -80,6 +82,8 @@ export function projectConfigurationToSettingsPayload(
onboardingStatus: configuration?.settings?.app?.onboarding_status, onboardingStatus: configuration?.settings?.app?.onboarding_status,
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner, dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
streamIdleMode: configuration?.settings?.app?.stream_idle_mode, streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
allowOrbitInSketchMode:
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
enableSSAO: configuration?.settings?.modeling?.enable_ssao, enableSSAO: configuration?.settings?.modeling?.enable_ssao,
}, },
modeling: { modeling: {

View File

@ -173,10 +173,14 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/fillet' }], links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/fillet' }],
}, },
{ {
id: 'chamfer', id: 'chamfer3d',
onClick: () => console.error('Chamfer not yet implemented'), onClick: ({ commandBarSend }) =>
commandBarSend({
type: 'Find and select command',
data: { name: 'Chamfer', groupId: 'modeling' },
}),
icon: 'chamfer3d', icon: 'chamfer3d',
status: 'kcl-only', status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
title: 'Chamfer', title: 'Chamfer',
hotkey: 'C', hotkey: 'C',
description: 'Bevel the edges of a 3D solid.', 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.', description: 'Create a hole in a 3D solid.',
links: [], 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', 'break',
[ [
{ {
@ -447,18 +460,16 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
disabled: (state) => disabled: (state) =>
state.matches('Sketch no face') || state.matches('Sketch no face') ||
(!canRectangleOrCircleTool(state.context) && (!canRectangleOrCircleTool(state.context) &&
!state.matches({ Sketch: 'Circle tool' })), !state.matches({ Sketch: 'Circle tool' }) &&
isActive: (state) => state.matches({ Sketch: 'Circle tool' }), !state.matches({ Sketch: 'circle3PointToolSelect' })),
isActive: (state) =>
state.matches({ Sketch: 'Circle tool' }) ||
state.matches({ Sketch: 'circle3PointToolSelect' }),
hotkey: (state) => hotkey: (state) =>
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C', state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
showTitle: false, showTitle: false,
description: 'Start drawing a circle from its center', description: 'Start drawing a circle from its center',
links: [ links: [],
{
label: 'GitHub issue',
url: 'https://github.com/KittyCAD/modeling-app/issues/1501',
},
],
}, },
{ {
id: 'circle-three-points', id: 'circle-three-points',
@ -475,7 +486,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}), }),
icon: 'circle', icon: 'circle',
status: 'available', status: 'available',
title: 'Three-point circle', title: '3-point circle',
showTitle: false, showTitle: false,
description: 'Draw a circle defined by three points', description: 'Draw a circle defined by three points',
links: [], links: [],

View File

@ -119,6 +119,9 @@ export const commandBarMachine = setup({
selectedCommand?.onSubmit() selectedCommand?.onSubmit()
} }
}, },
'Clear selected command': assign({
selectedCommand: undefined,
}),
'Set current argument to first non-skippable': assign({ 'Set current argument to first non-skippable': assign({
currentArgument: ({ context, event }) => { currentArgument: ({ context, event }) => {
const { selectedCommand } = context const { selectedCommand } = context
@ -246,6 +249,7 @@ export const commandBarMachine = setup({
context.selectedCommand?.needsReview || false, context.selectedCommand?.needsReview || false,
'Command has no arguments': () => false, 'Command has no arguments': () => false,
'All arguments are skippable': () => false, 'All arguments are skippable': () => false,
'Has selected command': ({ context }) => !!context.selectedCommand,
}, },
actors: { actors: {
'Validate argument': fromPromise( 'Validate argument': fromPromise(
@ -394,7 +398,7 @@ export const commandBarMachine = setup({
), ),
}, },
}).createMachine({ }).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: { context: {
commands: [], commands: [],
selectedCommand: undefined, selectedCommand: undefined,
@ -421,14 +425,6 @@ export const commandBarMachine = setup({
target: 'Selecting command', target: 'Selecting command',
}, },
'Find and select command': {
target: 'Command selected',
actions: [
'Find and select command',
'Initialize arguments to submit',
],
},
'Add commands': { 'Add commands': {
target: 'Closed', target: 'Closed',
@ -440,8 +436,6 @@ export const commandBarMachine = setup({
), ),
}), }),
], ],
reenter: false,
}, },
'Remove commands': { 'Remove commands': {
@ -458,10 +452,13 @@ export const commandBarMachine = setup({
), ),
}), }),
], ],
reenter: false,
}, },
}, },
always: {
target: 'Command selected',
guard: 'Has selected command',
},
}, },
'Selecting command': { 'Selecting command': {
@ -478,7 +475,7 @@ export const commandBarMachine = setup({
{ {
target: 'Closed', target: 'Closed',
guard: 'Command has no arguments', guard: 'Command has no arguments',
actions: ['Execute command'], actions: ['Execute command', 'Clear selected command'],
}, },
{ {
target: 'Checking Arguments', target: 'Checking Arguments',
@ -548,7 +545,7 @@ export const commandBarMachine = setup({
on: { on: {
'Submit command': { 'Submit command': {
target: 'Closed', target: 'Closed',
actions: ['Execute command'], actions: ['Execute command', 'Clear selected command'],
}, },
'Add argument': { 'Add argument': {
@ -580,7 +577,7 @@ export const commandBarMachine = setup({
}, },
{ {
target: 'Closed', target: 'Closed',
actions: 'Execute command', actions: ['Execute command', 'Clear selected command'],
}, },
], ],
onError: [ onError: [
@ -600,6 +597,7 @@ export const commandBarMachine = setup({
Close: { Close: {
target: '.Closed', target: '.Closed',
actions: 'Clear selected command',
}, },
Clear: { Clear: {
@ -607,6 +605,11 @@ export const commandBarMachine = setup({
reenter: false, reenter: false,
actions: ['Clear argument data'], actions: ['Clear argument data'],
}, },
'Find and select command': {
target: '.Command selected',
actions: ['Find and select command', 'Initialize arguments to submit'],
},
}, },
}) })

View File

@ -52,6 +52,7 @@ import {
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { import {
applyEdgeTreatmentToSelection, applyEdgeTreatmentToSelection,
ChamferParameters,
EdgeTreatmentType, EdgeTreatmentType,
FilletParameters, FilletParameters,
} from 'lang/modifyAst/addEdgeTreatment' } from 'lang/modifyAst/addEdgeTreatment'
@ -132,6 +133,8 @@ export interface SketchDetails {
zAxis: [number, number, number] zAxis: [number, number, number]
yAxis: [number, number, number] yAxis: [number, number, number]
origin: [number, number, number] origin: [number, number, number]
// face id or plane id, both are strings
animateTargetId?: string
} }
export interface SegmentOverlay { export interface SegmentOverlay {
@ -272,6 +275,7 @@ export type ModelingMachineEvent =
| { type: 'Shell'; data?: ModelingCommandSchema['Shell'] } | { type: 'Shell'; data?: ModelingCommandSchema['Shell'] }
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] } | { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
| { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] } | { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
| { type: 'Chamfer'; data?: ModelingCommandSchema['Chamfer'] }
| { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] } | { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
| { type: 'Text-to-CAD'; data: ModelingCommandSchema['Text-to-CAD'] } | { type: 'Text-to-CAD'; data: ModelingCommandSchema['Text-to-CAD'] }
| { type: 'Prompt-to-edit'; data: ModelingCommandSchema['Prompt-to-edit'] } | { type: 'Prompt-to-edit'; data: ModelingCommandSchema['Prompt-to-edit'] }
@ -420,6 +424,8 @@ export const modelingMachine = setup({
}, },
'is editing existing sketch': ({ context: { sketchDetails } }) => 'is editing existing sketch': ({ context: { sketchDetails } }) =>
isEditingExistingSketch({ sketchDetails }), isEditingExistingSketch({ sketchDetails }),
'is editing 3-point circle': ({ context: { sketchDetails } }) =>
isEditing3PointCircle({ sketchDetails }),
'Can make selection horizontal': ({ context: { selectionRanges } }) => { 'Can make selection horizontal': ({ context: { selectionRanges } }) => {
const info = horzVertInfo(selectionRanges, 'horizontal') const info = horzVertInfo(selectionRanges, 'horizontal')
if (trap(info)) return false if (trap(info)) return false
@ -1737,6 +1743,33 @@ export const modelingMachine = setup({
if (err(filletResult)) return filletResult 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( 'submit-prompt-edit': fromPromise(
async ({ input }: { input: ModelingCommandSchema['Prompt-to-edit'] }) => { async ({ input }: { input: ModelingCommandSchema['Prompt-to-edit'] }) => {
console.log('doing thing', input) console.log('doing thing', input)
@ -1821,6 +1854,11 @@ export const modelingMachine = setup({
reenter: true, reenter: true,
}, },
Chamfer: {
target: 'Applying chamfer',
reenter: true,
},
Export: { Export: {
target: 'idle', target: 'idle',
reenter: false, reenter: false,
@ -2153,6 +2191,10 @@ export const modelingMachine = setup({
target: 'SketchIdle', target: 'SketchIdle',
guard: 'is editing existing sketch', guard: 'is editing existing sketch',
}, },
{
target: 'circle3PointToolSelect',
guard: 'is editing 3-point circle',
},
'Line tool', 'Line tool',
], ],
}, },
@ -2484,13 +2526,8 @@ export const modelingMachine = setup({
circle3PointToolSelect: { circle3PointToolSelect: {
invoke: { invoke: {
id: 'actor-circle-3-point', id: 'actor-circle-3-point',
input: function ({ context, event }) { input: function ({ context }) {
// 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
if (!context.sketchDetails) return if (!context.sketchDetails) return
return context.sketchDetails return context.sketchDetails
}, },
src: 'actorCircle3Point', 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': { 'Applying Prompt-to-edit': {
invoke: { invoke: {
src: 'submit-prompt-edit', src: 'submit-prompt-edit',
@ -2735,6 +2785,34 @@ export function isEditingExistingSketch({
) )
return (hasStartProfileAt && pipeExpression.body.length > 2) || hasCircle 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({ export function pipeHasCircle({
sketchDetails, sketchDetails,
}: { }: {
@ -2755,6 +2833,27 @@ export function pipeHasCircle({
) )
return hasCircle 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({ export function canRectangleOrCircleTool({
sketchDetails, sketchDetails,

View File

@ -43,6 +43,7 @@ export const settingsMachine = setup({
'Execute AST': () => {}, 'Execute AST': () => {},
toastSuccess: () => {}, toastSuccess: () => {},
setClientSideSceneUnits: () => {}, setClientSideSceneUnits: () => {},
setAllowOrbitInSketchMode: () => {},
persistSettings: () => {}, persistSettings: () => {},
resetSettings: assign(({ context, event }) => { resetSettings: assign(({ context, event }) => {
if (!('level' in event)) return {} if (!('level' in event)) return {}
@ -157,6 +158,15 @@ export const settingsMachine = setup({
actions: ['setSettingAtLevel', 'toastSuccess'], actions: ['setSettingAtLevel', 'toastSuccess'],
}, },
'set.app.allowOrbitInSketchMode': {
target: 'persisting settings',
actions: [
'setSettingAtLevel',
'toastSuccess',
'setAllowOrbitInSketchMode',
],
},
'set.modeling.cameraProjection': { 'set.modeling.cameraProjection': {
target: 'persisting settings', target: 'persisting settings',
@ -183,6 +193,7 @@ export const settingsMachine = setup({
'setClientSideSceneUnits', 'setClientSideSceneUnits',
'Execute AST', 'Execute AST',
'setClientTheme', 'setClientTheme',
'setAllowOrbitInSketchMode',
], ],
}, },
@ -194,6 +205,7 @@ export const settingsMachine = setup({
'setClientSideSceneUnits', 'setClientSideSceneUnits',
'Execute AST', 'Execute AST',
'setClientTheme', 'setClientTheme',
'setAllowOrbitInSketchMode',
], ],
}, },

View File

@ -320,6 +320,11 @@ export function getAutoUpdater(): AppUpdater {
} }
app.on('ready', () => { app.on('ready', () => {
// Disable auto updater on non-versioned builds
if (packageJSON.version === '0.0.0') {
return
}
const autoUpdater = getAutoUpdater() const autoUpdater = getAutoUpdater()
// TODO: we're getting `Error: Response ends without calling any handlers` with our setup, // TODO: we're getting `Error: Response ends without calling any handlers` with our setup,
// so at the moment this isn't worth enabling // so at the moment this isn't worth enabling

View File

@ -205,6 +205,7 @@ export function OnboardingButtons({
</p> </p>
)} )}
<ActionButton <ActionButton
autoFocus
Element="button" Element="button"
onClick={next} onClick={next}
iconStart={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }} iconStart={{ icon: 'arrowRight', bgClassName: 'dark:bg-chalkboard-80' }}

146
src/wasm-lib/Cargo.lock generated
View File

@ -176,7 +176,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -187,7 +187,7 @@ checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -204,7 +204,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -474,7 +474,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -665,7 +665,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -676,7 +676,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -723,7 +723,7 @@ dependencies = [
[[package]] [[package]]
name = "derive-docs" name = "derive-docs"
version = "0.1.33" version = "0.1.34"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"anyhow", "anyhow",
@ -737,7 +737,7 @@ dependencies = [
"rustfmt-wrapper", "rustfmt-wrapper",
"serde", "serde",
"serde_tokenstream", "serde_tokenstream",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -748,7 +748,38 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "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]] [[package]]
@ -791,7 +822,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -829,7 +860,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -990,7 +1021,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1086,7 +1117,7 @@ dependencies = [
"inflections", "inflections",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1132,17 +1163,18 @@ dependencies = [
[[package]] [[package]]
name = "handlebars" name = "handlebars"
version = "6.2.0" version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd4ccde012831f9a071a637b0d4e31df31c0f6c525784b35ae76a9ac6bc1e315" checksum = "3d6b224b95c1e668ac0270325ad563b2eef1469fbbb8959bc7c692c844b813d9"
dependencies = [ dependencies = [
"derive_builder",
"log", "log",
"num-order", "num-order",
"pest", "pest",
"pest_derive", "pest_derive",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 1.0.68", "thiserror 2.0.0",
] ]
[[package]] [[package]]
@ -1494,7 +1526,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1684,7 +1716,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.2.29" version = "0.2.30"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx 0.5.1", "approx 0.5.1",
@ -1752,7 +1784,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-test-server" name = "kcl-test-server"
version = "0.1.19" version = "0.1.20"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hyper 0.14.30", "hyper 0.14.30",
@ -1819,9 +1851,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds" name = "kittycad-modeling-cmds"
version = "0.2.86" version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65e34a8eeb4fff5167666d1f2bc36c95d08ab3a0f736a02c8d33a8cde21cfd8d" checksum = "ce9e58b34645facea36bc9f4868877bbe6fcac01b92896825e8d4f2f7c71dbd6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -1852,7 +1884,7 @@ dependencies = [
"kittycad-modeling-cmds-macros-impl", "kittycad-modeling-cmds-macros-impl",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1863,7 +1895,7 @@ checksum = "fdb4ee23cc996aa2dca7584d410e8826e08161e1ac4335bb646d5ede33f37cb3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2013,7 +2045,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2312,7 +2344,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"structmeta", "structmeta",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2326,7 +2358,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"structmeta", "structmeta",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2366,7 +2398,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2424,7 +2456,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2554,7 +2586,7 @@ dependencies = [
"proc-macro-error-attr2", "proc-macro-error-attr2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2613,7 +2645,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-macros-backend", "pyo3-macros-backend",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2626,7 +2658,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-build-config", "pyo3-build-config",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3161,7 +3193,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3225,7 +3257,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3236,7 +3268,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3260,7 +3292,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3281,7 +3313,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3430,7 +3462,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"structmeta-derive", "structmeta-derive",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3441,7 +3473,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3463,7 +3495,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3506,9 +3538,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.95" version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3532,7 +3564,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3640,7 +3672,7 @@ checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3651,7 +3683,7 @@ checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3763,7 +3795,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3905,7 +3937,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3933,7 +3965,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -4016,7 +4048,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
"termcolor", "termcolor",
] ]
@ -4159,9 +4191,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.11.0" version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"serde", "serde",
@ -4195,7 +4227,7 @@ dependencies = [
"proc-macro-error2", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -4256,7 +4288,7 @@ dependencies = [
"log", "log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -4292,7 +4324,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -4673,7 +4705,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
"synstructure", "synstructure",
] ]
@ -4695,7 +4727,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -4715,7 +4747,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
"synstructure", "synstructure",
] ]
@ -4744,7 +4776,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]

View File

@ -76,7 +76,7 @@ members = [
[workspace.dependencies] [workspace.dependencies]
http = "1" http = "1"
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] } 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", "ts-rs",
"websocket", "websocket",
] } ] }

View File

@ -1,7 +1,7 @@
[package] [package]
name = "derive-docs" name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros" description = "A tool for generating documentation from Rust derive macros"
version = "0.1.33" version = "0.1.34"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"
@ -20,7 +20,7 @@ quote = "1"
regex = "1.11" regex = "1.11"
serde = { version = "1.0.217", features = ["derive"] } serde = { version = "1.0.217", features = ["derive"] }
serde_tokenstream = "0.2" serde_tokenstream = "0.2"
syn = { version = "2.0.95", features = ["full"] } syn = { version = "2.0.96", features = ["full"] }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.95" anyhow = "1.0.95"

View File

@ -815,7 +815,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", #fn_name, #index), filename: format!("{}{}", #fn_name, #index),

View File

@ -14,7 +14,10 @@ mod test_examples_someFn {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "someFn", 0usize), filename: format!("{}{}", "someFn", 0usize),

View File

@ -14,7 +14,10 @@ mod test_examples_someFn {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "someFn", 0usize), filename: format!("{}{}", "someFn", 0usize),

View File

@ -15,7 +15,10 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "show", 0usize), filename: format!("{}{}", "show", 0usize),
@ -69,7 +72,10 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "show", 1usize), filename: format!("{}{}", "show", 1usize),

View File

@ -15,7 +15,10 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "show", 0usize), filename: format!("{}{}", "show", 0usize),

View File

@ -16,7 +16,10 @@ mod test_examples_my_func {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "my_func", 0usize), filename: format!("{}{}", "my_func", 0usize),
@ -70,7 +73,10 @@ mod test_examples_my_func {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "my_func", 1usize), filename: format!("{}{}", "my_func", 1usize),

View File

@ -16,7 +16,10 @@ mod test_examples_line_to {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "line_to", 0usize), filename: format!("{}{}", "line_to", 0usize),
@ -70,7 +73,10 @@ mod test_examples_line_to {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "line_to", 1usize), filename: format!("{}{}", "line_to", 1usize),

View File

@ -15,7 +15,10 @@ mod test_examples_min {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "min", 0usize), filename: format!("{}{}", "min", 0usize),
@ -69,7 +72,10 @@ mod test_examples_min {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "min", 1usize), filename: format!("{}{}", "min", 1usize),

View File

@ -15,7 +15,10 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "show", 0usize), filename: format!("{}{}", "show", 0usize),

View File

@ -15,7 +15,10 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "import", 0usize), filename: format!("{}{}", "import", 0usize),

View File

@ -15,7 +15,10 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "import", 0usize), filename: format!("{}{}", "import", 0usize),

View File

@ -15,7 +15,10 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "import", 0usize), filename: format!("{}{}", "import", 0usize),

View File

@ -15,7 +15,10 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "show", 0usize), filename: format!("{}{}", "show", 0usize),

View File

@ -14,7 +14,10 @@ mod test_examples_some_function {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, 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 { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e,
filename: format!("{}{}", "some_function", 0usize), filename: format!("{}{}", "some_function", 0usize),

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-test-server" name = "kcl-test-server"
description = "A test server for KCL" description = "A test server for KCL"
version = "0.1.19" version = "0.1.20"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View File

@ -164,7 +164,7 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
}; };
eprintln!("Executing {test_name}"); 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. // 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. // There's no actual KCL associated with this reset_scene call.
if let Err(e) = state if let Err(e) = state

View File

@ -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( let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new(
crate::conn_mock_core::EngineConnection::new(ref_result).await?, 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(); let result = result.lock().expect("mutex lock").clone();
Ok(result) Ok(result)

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-lib" name = "kcl-lib"
description = "KittyCAD Language implementation and tools" description = "KittyCAD Language implementation and tools"
version = "0.2.29" version = "0.2.30"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" 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" convert_case = "0.6.0"
dashmap = "6.1.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 } dhat = { version = "0.3", optional = true }
fnv = "1.0.7" fnv = "1.0.7"
form_urlencoded = "1.2.1" form_urlencoded = "1.2.1"
@ -112,7 +112,7 @@ tabled = ["dep:tabled"]
base64 = "0.22.1" base64 = "0.22.1"
criterion = { version = "0.5.1", features = ["async_tokio"] } criterion = { version = "0.5.1", features = ["async_tokio"] }
expectorate = "1.1.0" expectorate = "1.1.0"
handlebars = "6.2.0" handlebars = "6.3.0"
iai = "0.1" iai = "0.1"
image = { version = "0.25.5", default-features = false, features = ["png"] } image = { version = "0.25.5", default-features = false, features = ["png"] }
insta = { version = "1.41.1", features = ["json", "filters", "redactions"] } insta = { version = "1.41.1", features = ["json", "filters", "redactions"] }

View File

@ -1024,6 +1024,36 @@ mod tests {
assert_eq!(snippet, r#"hole(${0:holeSketch}, ${1:%})${}"#); 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. // We want to test the snippets we compile at lsp start.
#[test] #[test]
fn get_all_stdlib_autocomplete_snippets() { fn get_all_stdlib_autocomplete_snippets() {

View File

@ -30,13 +30,16 @@ impl From<KclErrorWithOutputs> for ExecError {
#[derive(Debug)] #[derive(Debug)]
pub struct ExecErrorWithState { pub struct ExecErrorWithState {
pub error: ExecError, pub error: ExecError,
pub exec_state: crate::ExecState, pub exec_state: Option<crate::ExecState>,
} }
impl ExecErrorWithState { impl ExecErrorWithState {
#[cfg_attr(target_arch = "wasm32", expect(dead_code))] #[cfg_attr(target_arch = "wasm32", expect(dead_code))]
pub fn new(error: ExecError, exec_state: crate::ExecState) -> Self { 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 { fn from(error: ExecError) -> Self {
Self { Self {
error, error,
exec_state: Default::default(), exec_state: None,
} }
} }
} }
@ -53,7 +56,7 @@ impl From<ConnectionError> for ExecErrorWithState {
fn from(error: ConnectionError) -> Self { fn from(error: ConnectionError) -> Self {
Self { Self {
error: error.into(), error: error.into(),
exec_state: Default::default(), exec_state: None,
} }
} }
} }

View File

@ -21,8 +21,6 @@ use crate::{
use super::cad_op::{OpArg, Operation}; use super::cad_op::{OpArg, Operation};
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;
impl BinaryPart { impl BinaryPart {
#[async_recursion] #[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> { 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 { if num < 0.0 {
return make_err(format!("'{num}' is negative, so you can't index an array with it")) return make_err(format!("'{num}' is negative, so you can't index an array with it"))
} }
let nearest_int = num.round(); let nearest_int = crate::try_f64_to_usize(num);
let delta = num-nearest_int; if let Some(nearest_int) = nearest_int {
if delta < FLOAT_TO_INT_MAX_DELTA { Ok(Property::UInt(nearest_int))
Ok(Property::UInt(nearest_int as usize))
} else { } else {
make_err(format!("'{num}' is not an integer, so you can't index an array with it")) 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 { impl Property {
fn type_name(&self) -> &'static str { fn type_name(&self) -> &'static str {
match self { match self {

View File

@ -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)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum UnitAngle { pub enum UnitAngle {
#[default]
Degrees, Degrees,
Radians, Radians,
} }

View File

@ -21,7 +21,7 @@ type Point2D = kcmc::shared::Point2d<f64>;
type Point3D = kcmc::shared::Point3d<f64>; type Point3D = kcmc::shared::Point3d<f64>;
pub use function_param::FunctionParam; pub use function_param::FunctionParam;
pub use kcl_value::{KclObjectFields, KclValue}; pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
use uuid::Uuid; use uuid::Uuid;
mod annotations; mod annotations;
@ -77,7 +77,7 @@ pub struct GlobalState {
pub artifact_commands: Vec<ArtifactCommand>, pub artifact_commands: Vec<ArtifactCommand>,
} }
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ModuleState { pub struct ModuleState {
/// Program variable bindings. /// Program variable bindings.
@ -115,21 +115,15 @@ pub struct ExecOutcome {
pub artifact_commands: Vec<ArtifactCommand>, pub artifact_commands: Vec<ArtifactCommand>,
} }
impl Default for ExecState {
fn default() -> Self {
Self::new()
}
}
impl ExecState { impl ExecState {
pub fn new() -> Self { pub fn new(exec_settings: &ExecutorSettings) -> Self {
ExecState { ExecState {
global: GlobalState::new(), 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(); let mut id_generator = self.global.id_generator.clone();
// We do not pop the ids, since we want to keep the same id generator. // 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. // This is for the front end to keep track of the ids.
@ -140,7 +134,7 @@ impl ExecState {
*self = ExecState { *self = ExecState {
global, global,
mod_local: ModuleState::default(), mod_local: ModuleState::new(exec_settings),
}; };
} }
@ -204,6 +198,14 @@ impl ExecState {
Ok(id) 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 { 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -240,15 +259,6 @@ pub struct MetaSettings {
pub default_angle_units: kcl_value::UnitAngle, 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 { impl MetaSettings {
fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> { fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> {
let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?; 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)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
pub struct ExecutorSettings { pub struct ExecutorSettings {
/// The unit to use in modeling dimensions. /// The project-default unit to use in modeling dimensions.
pub units: UnitLength, pub units: UnitLength,
/// Highlight edges of 3D objects? /// Highlight edges of 3D objects?
pub highlight_edges: bool, pub highlight_edges: bool,
@ -2214,7 +2224,7 @@ impl ExecutorContext {
if cache_result.clear_scene && !self.is_mock() { if cache_result.clear_scene && !self.is_mock() {
// Pop the execution state, since we are starting fresh. // 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 // 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. // 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 { let mut local_state = ModuleState {
import_stack: exec_state.mod_local.import_stack.clone(), import_stack: exec_state.mod_local.import_stack.clone(),
..Default::default() ..ModuleState::new(&self.settings)
}; };
local_state.import_stack.push(info.path.clone()); local_state.import_stack.push(info.path.clone());
std::mem::swap(&mut exec_state.mod_local, &mut local_state); std::mem::swap(&mut exec_state.mod_local, &mut local_state);
@ -2830,7 +2840,7 @@ mod tests {
settings: Default::default(), settings: Default::default(),
context_type: ContextType::Mock, 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?; ctx.run(program.clone().into(), &mut exec_state).await?;
Ok((program, ctx, exec_state)) 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()); 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")] #[tokio::test(flavor = "multi_thread")]
async fn test_zero_param_fn() { async fn test_zero_param_fn() {
let ast = r#"const sigmaAllow = 35000 // psi let ast = r#"const sigmaAllow = 35000 // psi
@ -4045,7 +4074,7 @@ shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
.unwrap(); .unwrap();
let old_program = crate::Program::parse_no_errs(code).unwrap(); let old_program = crate::Program::parse_no_errs(code).unwrap();
// Execute the program. // Execute the program.
let mut exec_state = Default::default(); let mut exec_state = ExecState::new(&ctx.settings);
let cache_info = crate::CacheInformation { let cache_info = crate::CacheInformation {
old: None, old: None,
new_ast: old_program.ast.clone(), new_ast: old_program.ast.clone(),

View File

@ -70,7 +70,7 @@ mod settings;
#[cfg(test)] #[cfg(test)]
mod simulation_tests; mod simulation_tests;
mod source_range; mod source_range;
mod std; pub mod std;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub mod test_server; pub mod test_server;
mod thread; mod thread;
@ -84,7 +84,7 @@ pub use engine::{EngineManager, ExecutionKind};
pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs}; pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs};
pub use execution::{ pub use execution::{
cache::{CacheInformation, OldAstState}, cache::{CacheInformation, OldAstState},
ExecState, ExecutorContext, ExecutorSettings, ExecState, ExecutorContext, ExecutorSettings, Point2d,
}; };
pub use lsp::{ pub use lsp::{
copilot::Backend as CopilotLspBackend, copilot::Backend as CopilotLspBackend,

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