Compare commits

..

8 Commits

Author SHA1 Message Date
5474b3409e Merge branch 'main' into kurt-contraint-colours 2025-07-02 12:18:46 +10:00
29ae16fbf0 fix console noise 2025-07-02 11:55:50 +10:00
38ee257996 Merge branch 'main' into kurt-contraint-colours 2025-07-02 06:03:07 +10:00
47c29b2681 Update snapshots 2025-07-01 08:18:39 +00:00
23f51d73ee Update snapshots 2025-07-01 08:04:33 +00:00
f752a496de fix package 2025-07-01 17:51:02 +10:00
6545fb6db0 package 2025-07-01 17:26:15 +10:00
e63eb18d65 constraint colors 2025-07-01 16:46:43 +10:00
56 changed files with 935 additions and 947 deletions

View File

@ -3,17 +3,18 @@
NODE_ENV=development
DEV=true
# App
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
VITE_KITTYCAD_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_SITE_APP_URL=https://app.dev.zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000
#VITE_WASM_URL="optional override of Wasm URL if not on default port 3000"
#VITE_KITTYCAD_API_TOKEN="required for testing, optional to skip auth in the app"
FAIL_ON_CONSOLE_ERRORS=true
#VITE_WASM_URL="optional way of overriding the wasm url, particular for unit tests which need this if you running not on the default 3000 port"
#VITE_KC_DEV_TOKEN="optional token to skip auth in the app"
#token="required token for playwright. TODO: clean up env vars in #3973"
# KCL
RUST_BACKTRACE=1
PYO3_PYTHON=/usr/local/bin/python3
#KITTYCAD_API_TOKEN=$VITE_KITTYCAD_API_TOKEN
#KITTYCAD_API_TOKEN="required token for engine testing"
FAIL_ON_CONSOLE_ERRORS=true

View File

@ -1,8 +1,7 @@
NODE_ENV=production
# App
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
VITE_KITTYCAD_API_BASE_URL=https://api.zoo.dev
VITE_KC_API_BASE_URL=https://api.zoo.dev
VITE_KC_SITE_BASE_URL=https://zoo.dev
VITE_KC_SITE_APP_URL=https://app.zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=15000

View File

@ -157,7 +157,7 @@ jobs:
timeout_minutes: 5
max_attempts: 5
env:
VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
@ -169,7 +169,7 @@ jobs:
if: always()
run: npm run test:snapshots -- --last-failed --update-snapshots
env:
VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
@ -284,7 +284,7 @@ jobs:
timeout_minutes: 5
max_attempts: 5
env:
VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
@ -410,7 +410,7 @@ jobs:
max_attempts: 9
env:
FAIL_ON_CONSOLE_ERRORS: true
VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}

View File

@ -62,7 +62,7 @@ jobs:
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: xvfb-run -a npm run test:unit
env:
VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: Check for changes
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}

View File

@ -65,7 +65,7 @@ If you're not a Zoo employee you won't be able to access the dev environment, yo
### Development environment variables
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `zoo.dev`). There is an optional environment variable called `VITE_KITTYCAD_API_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `zoo.dev`). There is an optional environment variable called `VITE_KC_DEV_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
### Developing in Chrome
@ -96,7 +96,7 @@ To package the app for your platform with electron-builder, run `npm run tronb:p
Prepare these system dependencies:
- Set `$VITE_KITTYCAD_API_TOKEN` from https://zoo.dev/account/api-tokens
- Set $token from https://zoo.dev/account/api-tokens
#### Snapshot tests (Google Chrome on Ubuntu only)
@ -259,7 +259,7 @@ If the application needs to overwrite the known file on disk use this pattern. T
- `npm run circular-deps:overwrite`
- `npm run url-checker:overwrite`
#### Diff baseline and current
#### Diff baseline and current
These commands will write a /tmp/ file on disk and compare it to the known file in the repository. This command will also be used in the CI CD pipeline for automated checks

View File

@ -49,7 +49,7 @@ RUST_SOURCES := $(wildcard rust/**/*.rs)
REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx)
TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts)
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx) .env*
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx)
.PHONY: build
build: install public/kcl_wasm_lib_bg.wasm public/kcl-samples/manifest.json .vite/build/main.js

View File

@ -1,5 +1,6 @@
import { expect, test } from '@e2e/playwright/zoo-test'
// test file is for testing auth functionality
test.describe('Authentication tests', () => {
test(
`The user can sign out and back in`,
@ -12,12 +13,22 @@ test.describe('Authentication tests', () => {
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.projectSection.waitFor()
// This is only needed as an override to test-utils' setup() for this test
await page.addInitScript(() => {
localStorage.setItem('TOKEN_PERSIST_KEY', '')
})
await test.step('Click on sign out and expect sign in page', async () => {
await toolbar.userSidebarButton.click()
await toolbar.signOutButton.click()
await expect(signInPage.signInButton).toBeVisible()
})
await test.step("Refresh doesn't log the user back in", async () => {
await page.reload()
await expect(signInPage.signInButton).toBeVisible()
})
await test.step('Click on sign in and cancel, click again and expect different code', async () => {
await signInPage.signInButton.click()
await expect(signInPage.userCode).toBeVisible()

View File

@ -187,68 +187,6 @@ sketch001 = startProfile(sketch002, at = [12.34, -12.34])
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
})
test('Can select planes in Feature Tree after Start Sketch', async ({
page,
homePage,
toolbar,
editor,
}) => {
// Load the app with empty code
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`plane001 = offsetPlane(XZ, offset = 5)`
)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await test.step('Click Start Sketch button', async () => {
await page.getByRole('button', { name: 'Start Sketch' }).click()
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible()
await expect(page.getByText('select a plane or face')).toBeVisible()
})
await test.step('Open feature tree and select Front plane (XZ)', async () => {
await toolbar.openFeatureTreePane()
await page.getByRole('button', { name: 'Front plane' }).click()
await page.waitForTimeout(600)
await expect(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(XZ)')
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
})
await test.step('Click Start Sketch button again', async () => {
await page.getByRole('button', { name: 'Start Sketch' }).click()
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible()
})
await test.step('Select the offset plane', async () => {
await toolbar.openFeatureTreePane()
await page.getByRole('button', { name: 'Offset plane' }).click()
await page.waitForTimeout(600)
await expect(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(plane001)')
})
})
test('Can edit segments by dragging their handles', () => {
const doEditSegmentsByDraggingHandle = async (
page: Page,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 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: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -17,7 +17,7 @@ import dotenv from 'dotenv'
const NODE_ENV = process.env.NODE_ENV || 'development'
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
export const token = process.env.VITE_KITTYCAD_API_TOKEN || ''
export const token = process.env.token || ''
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'

7
interface.d.ts vendored
View File

@ -72,13 +72,16 @@ export interface IElectronAPI {
}
process: {
env: {
BASE_URL: string
IS_PLAYWRIGHT: string
VITE_KITTYCAD_API_TOKEN: string
VITE_KC_DEV_TOKEN: string
VITE_KC_API_WS_MODELING_URL: string
VITE_KITTYCAD_API_BASE_URL: string
VITE_KC_API_BASE_URL: string
VITE_KC_SITE_BASE_URL: string
VITE_KC_SITE_APP_URL: string
VITE_KC_SKIP_AUTH: string
VITE_KC_CONNECTION_TIMEOUT_MS: string
VITE_KC_DEV_TOKEN: string
NODE_ENV: string
PROD: string
DEV: string

252
package-lock.json generated
View File

@ -43,6 +43,7 @@
"bson": "^6.10.3",
"chokidar": "^4.0.3",
"codemirror": "^6.0.1",
"culori": "^4.0.2",
"decamelize": "^6.0.0",
"diff": "^7.0.0",
"electron-updater": "^6.6.2",
@ -92,6 +93,7 @@
"@playwright/test": "^1.52.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^15.0.7",
"@types/culori": "^4.0.0",
"@types/diff": "^7.0.2",
"@types/electron": "^1.6.10",
"@types/hammerjs": "^2.0.46",
@ -149,7 +151,7 @@
"ts-node": "^10.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.30.1",
"vite": "^5.4.19",
"vite": "^5.4.18",
"vite-plugin-package-version": "^1.1.0",
"vite-plugin-top-level-await": "^1.5.0",
"vite-tsconfig-paths": "^4.3.2",
@ -3652,9 +3654,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
"integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz",
"integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==",
"cpu": [
"ppc64"
],
@ -3668,9 +3670,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz",
"integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz",
"integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==",
"cpu": [
"arm"
],
@ -3684,9 +3686,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz",
"integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz",
"integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==",
"cpu": [
"arm64"
],
@ -3700,9 +3702,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz",
"integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz",
"integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==",
"cpu": [
"x64"
],
@ -3716,9 +3718,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz",
"integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz",
"integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==",
"cpu": [
"arm64"
],
@ -3732,9 +3734,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz",
"integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz",
"integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==",
"cpu": [
"x64"
],
@ -3748,9 +3750,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz",
"integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz",
"integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==",
"cpu": [
"arm64"
],
@ -3764,9 +3766,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz",
"integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz",
"integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==",
"cpu": [
"x64"
],
@ -3780,9 +3782,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz",
"integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz",
"integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==",
"cpu": [
"arm"
],
@ -3796,9 +3798,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz",
"integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz",
"integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==",
"cpu": [
"arm64"
],
@ -3812,9 +3814,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz",
"integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz",
"integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==",
"cpu": [
"ia32"
],
@ -3828,9 +3830,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz",
"integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz",
"integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==",
"cpu": [
"loong64"
],
@ -3844,9 +3846,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz",
"integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz",
"integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==",
"cpu": [
"mips64el"
],
@ -3860,9 +3862,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz",
"integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz",
"integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==",
"cpu": [
"ppc64"
],
@ -3876,9 +3878,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz",
"integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz",
"integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==",
"cpu": [
"riscv64"
],
@ -3892,9 +3894,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz",
"integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz",
"integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==",
"cpu": [
"s390x"
],
@ -3908,9 +3910,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz",
"integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz",
"integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==",
"cpu": [
"x64"
],
@ -3924,9 +3926,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
"integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz",
"integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==",
"cpu": [
"arm64"
],
@ -3940,9 +3942,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz",
"integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz",
"integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==",
"cpu": [
"x64"
],
@ -3956,9 +3958,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
"integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz",
"integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==",
"cpu": [
"arm64"
],
@ -3972,9 +3974,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz",
"integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz",
"integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==",
"cpu": [
"x64"
],
@ -3988,9 +3990,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz",
"integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz",
"integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==",
"cpu": [
"x64"
],
@ -4004,9 +4006,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz",
"integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz",
"integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==",
"cpu": [
"arm64"
],
@ -4020,9 +4022,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz",
"integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz",
"integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==",
"cpu": [
"ia32"
],
@ -4036,9 +4038,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz",
"integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz",
"integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==",
"cpu": [
"x64"
],
@ -7392,6 +7394,13 @@
"integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==",
"license": "MIT"
},
"node_modules/@types/culori": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/culori/-/culori-4.0.0.tgz",
"integrity": "sha512-aFljQwjb++sl6TAyEXeHTiK/fk9epZOQ+nMmadjnAvzZFIvNoQ0x8XQYfcOaRTBwmDUPUlghhZCJ66MTcqQAsg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@ -11823,6 +11832,15 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/culori": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/culori/-/culori-4.0.2.tgz",
"integrity": "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==",
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -13219,9 +13237,9 @@
"optional": true
},
"node_modules/esbuild": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
"integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
"integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==",
"dev": true,
"hasInstallScript": true,
"bin": {
@ -13231,31 +13249,31 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.4",
"@esbuild/android-arm": "0.25.4",
"@esbuild/android-arm64": "0.25.4",
"@esbuild/android-x64": "0.25.4",
"@esbuild/darwin-arm64": "0.25.4",
"@esbuild/darwin-x64": "0.25.4",
"@esbuild/freebsd-arm64": "0.25.4",
"@esbuild/freebsd-x64": "0.25.4",
"@esbuild/linux-arm": "0.25.4",
"@esbuild/linux-arm64": "0.25.4",
"@esbuild/linux-ia32": "0.25.4",
"@esbuild/linux-loong64": "0.25.4",
"@esbuild/linux-mips64el": "0.25.4",
"@esbuild/linux-ppc64": "0.25.4",
"@esbuild/linux-riscv64": "0.25.4",
"@esbuild/linux-s390x": "0.25.4",
"@esbuild/linux-x64": "0.25.4",
"@esbuild/netbsd-arm64": "0.25.4",
"@esbuild/netbsd-x64": "0.25.4",
"@esbuild/openbsd-arm64": "0.25.4",
"@esbuild/openbsd-x64": "0.25.4",
"@esbuild/sunos-x64": "0.25.4",
"@esbuild/win32-arm64": "0.25.4",
"@esbuild/win32-ia32": "0.25.4",
"@esbuild/win32-x64": "0.25.4"
"@esbuild/aix-ppc64": "0.25.3",
"@esbuild/android-arm": "0.25.3",
"@esbuild/android-arm64": "0.25.3",
"@esbuild/android-x64": "0.25.3",
"@esbuild/darwin-arm64": "0.25.3",
"@esbuild/darwin-x64": "0.25.3",
"@esbuild/freebsd-arm64": "0.25.3",
"@esbuild/freebsd-x64": "0.25.3",
"@esbuild/linux-arm": "0.25.3",
"@esbuild/linux-arm64": "0.25.3",
"@esbuild/linux-ia32": "0.25.3",
"@esbuild/linux-loong64": "0.25.3",
"@esbuild/linux-mips64el": "0.25.3",
"@esbuild/linux-ppc64": "0.25.3",
"@esbuild/linux-riscv64": "0.25.3",
"@esbuild/linux-s390x": "0.25.3",
"@esbuild/linux-x64": "0.25.3",
"@esbuild/netbsd-arm64": "0.25.3",
"@esbuild/netbsd-x64": "0.25.3",
"@esbuild/openbsd-arm64": "0.25.3",
"@esbuild/openbsd-x64": "0.25.3",
"@esbuild/sunos-x64": "0.25.3",
"@esbuild/win32-arm64": "0.25.3",
"@esbuild/win32-ia32": "0.25.3",
"@esbuild/win32-x64": "0.25.3"
}
},
"node_modules/escalade": {
@ -25132,10 +25150,11 @@
"optional": true
},
"node_modules/vite": {
"version": "5.4.19",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
"integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
"version": "5.4.18",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz",
"integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
@ -26639,25 +26658,10 @@
"vscode-uri": "^3.1.0"
},
"devDependencies": {
"@types/node": "^24.0.7",
"@types/node": "^22.14.1",
"ts-node": "^10.9.2"
}
},
"packages/codemirror-lsp-client/node_modules/@types/node": {
"version": "24.0.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
"integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
"dev": true,
"dependencies": {
"undici-types": "~7.8.0"
}
},
"packages/codemirror-lsp-client/node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"dev": true
},
"rust/kcl-language-server": {
"version": "0.0.0",
"license": "MIT",
@ -26675,7 +26679,7 @@
"@vscode/test-electron": "^2.4.1",
"@vscode/vsce": "^3.3.2",
"cross-env": "^7.0.3",
"esbuild": "^0.25.4",
"esbuild": "^0.25.3",
"glob": "^11.0.1",
"mocha": "^11.1.0",
"typescript": "^5.8.3"

View File

@ -45,6 +45,7 @@
"bson": "^6.10.3",
"chokidar": "^4.0.3",
"codemirror": "^6.0.1",
"culori": "^4.0.2",
"decamelize": "^6.0.0",
"diff": "^7.0.0",
"electron-updater": "^6.6.2",
@ -170,6 +171,7 @@
"@playwright/test": "^1.52.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^15.0.7",
"@types/culori": "^4.0.0",
"@types/diff": "^7.0.2",
"@types/electron": "^1.6.10",
"@types/hammerjs": "^2.0.46",
@ -227,7 +229,7 @@
"ts-node": "^10.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.30.1",
"vite": "^5.4.19",
"vite": "^5.4.18",
"vite-plugin-package-version": "^1.1.0",
"vite-plugin-top-level-await": "^1.5.0",
"vite-tsconfig-paths": "^4.3.2",

View File

@ -123,7 +123,7 @@
"@vscode/test-electron": "^2.4.1",
"@vscode/vsce": "^3.3.2",
"cross-env": "^7.0.3",
"esbuild": "^0.25.4",
"esbuild": "^0.25.3",
"glob": "^11.0.1",
"mocha": "^11.1.0",
"typescript": "^5.8.3"

View File

@ -10,76 +10,71 @@ DATA;
NAMED_UNIT(*)
SI_UNIT($, .METRE.)
);
#2 = (
NAMED_UNIT(*)
PLANE_ANGLE_UNIT()
SI_UNIT($, .RADIAN.)
);
#3 = UNCERTAINTY_MEASURE_WITH_UNIT(0.00001, #1, 'DISTANCE_ACCURACY_VALUE', $);
#4 = (
#2 = UNCERTAINTY_MEASURE_WITH_UNIT(0.00001, #1, 'DISTANCE_ACCURACY_VALUE', $);
#3 = (
GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#3))
GLOBAL_UNIT_ASSIGNED_CONTEXT((#1, #2))
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#2))
GLOBAL_UNIT_ASSIGNED_CONTEXT((#1))
REPRESENTATION_CONTEXT('', '3D')
);
#5 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005));
#6 = VERTEX_POINT('NONE', #5);
#7 = CARTESIAN_POINT('NONE', (0.015, 0, -0.005));
#8 = VERTEX_POINT('NONE', #7);
#9 = DIRECTION('NONE', (1, 0, -0));
#10 = DIRECTION('NONE', (0, 1, 0));
#11 = CARTESIAN_POINT('NONE', (0.005, -0.01, -0.005));
#12 = AXIS2_PLACEMENT_3D('NONE', #11, #10, #9);
#13 = CIRCLE('NONE', #12, 0.01);
#14 = DIRECTION('NONE', (0, 1, 0));
#15 = VECTOR('NONE', #14, 1);
#16 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005));
#17 = LINE('NONE', #16, #15);
#18 = DIRECTION('NONE', (1, 0, -0));
#19 = DIRECTION('NONE', (0, 1, 0));
#20 = CARTESIAN_POINT('NONE', (0.005, 0, -0.005));
#21 = AXIS2_PLACEMENT_3D('NONE', #20, #19, #18);
#22 = CIRCLE('NONE', #21, 0.01);
#23 = EDGE_CURVE('NONE', #6, #6, #13, .T.);
#24 = EDGE_CURVE('NONE', #6, #8, #17, .T.);
#25 = EDGE_CURVE('NONE', #8, #8, #22, .T.);
#26 = CARTESIAN_POINT('NONE', (0.005, -0.005, -0.005));
#27 = DIRECTION('NONE', (0, 1, 0));
#28 = DIRECTION('NONE', (1, 0, -0));
#29 = AXIS2_PLACEMENT_3D('NONE', #26, #27, #28);
#30 = CYLINDRICAL_SURFACE('NONE', #29, 0.01);
#31 = CARTESIAN_POINT('NONE', (0, -0.01, -0));
#32 = DIRECTION('NONE', (0, 1, 0));
#33 = AXIS2_PLACEMENT_3D('NONE', #31, #32, $);
#34 = PLANE('NONE', #33);
#35 = CARTESIAN_POINT('NONE', (0, 0, -0));
#36 = DIRECTION('NONE', (0, 1, 0));
#37 = AXIS2_PLACEMENT_3D('NONE', #35, #36, $);
#38 = PLANE('NONE', #37);
#39 = ORIENTED_EDGE('NONE', *, *, #23, .T.);
#40 = ORIENTED_EDGE('NONE', *, *, #25, .F.);
#41 = EDGE_LOOP('NONE', (#39));
#42 = FACE_BOUND('NONE', #41, .T.);
#43 = EDGE_LOOP('NONE', (#40));
#44 = FACE_BOUND('NONE', #43, .T.);
#45 = ADVANCED_FACE('NONE', (#42, #44), #30, .T.);
#46 = ORIENTED_EDGE('NONE', *, *, #23, .F.);
#47 = EDGE_LOOP('NONE', (#46));
#48 = FACE_BOUND('NONE', #47, .T.);
#49 = ADVANCED_FACE('NONE', (#48), #34, .F.);
#50 = ORIENTED_EDGE('NONE', *, *, #25, .T.);
#51 = EDGE_LOOP('NONE', (#50));
#52 = FACE_BOUND('NONE', #51, .T.);
#53 = ADVANCED_FACE('NONE', (#52), #38, .T.);
#54 = CLOSED_SHELL('NONE', (#45, #49, #53));
#55 = MANIFOLD_SOLID_BREP('NONE', #54);
#56 = APPLICATION_CONTEXT('configuration controlled 3D design of mechanical parts and assemblies');
#57 = PRODUCT_DEFINITION_CONTEXT('part definition', #56, 'design');
#58 = PRODUCT('UNIDENTIFIED_PRODUCT', 'NONE', $, ());
#59 = PRODUCT_DEFINITION_FORMATION('', $, #58);
#60 = PRODUCT_DEFINITION('design', $, #59, #57);
#61 = PRODUCT_DEFINITION_SHAPE('NONE', $, #60);
#62 = ADVANCED_BREP_SHAPE_REPRESENTATION('NONE', (#55), #4);
#63 = SHAPE_DEFINITION_REPRESENTATION(#61, #62);
#4 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005));
#5 = VERTEX_POINT('NONE', #4);
#6 = CARTESIAN_POINT('NONE', (0.015, 0, -0.005));
#7 = VERTEX_POINT('NONE', #6);
#8 = DIRECTION('NONE', (1, 0, -0));
#9 = DIRECTION('NONE', (0, 1, 0));
#10 = CARTESIAN_POINT('NONE', (0.005, -0.01, -0.005));
#11 = AXIS2_PLACEMENT_3D('NONE', #10, #9, #8);
#12 = CIRCLE('NONE', #11, 0.01);
#13 = DIRECTION('NONE', (0, 1, 0));
#14 = VECTOR('NONE', #13, 1);
#15 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005));
#16 = LINE('NONE', #15, #14);
#17 = DIRECTION('NONE', (1, 0, -0));
#18 = DIRECTION('NONE', (0, 1, 0));
#19 = CARTESIAN_POINT('NONE', (0.005, 0, -0.005));
#20 = AXIS2_PLACEMENT_3D('NONE', #19, #18, #17);
#21 = CIRCLE('NONE', #20, 0.01);
#22 = EDGE_CURVE('NONE', #5, #5, #12, .T.);
#23 = EDGE_CURVE('NONE', #5, #7, #16, .T.);
#24 = EDGE_CURVE('NONE', #7, #7, #21, .T.);
#25 = CARTESIAN_POINT('NONE', (0.005, -0.005, -0.005));
#26 = DIRECTION('NONE', (0, 1, 0));
#27 = DIRECTION('NONE', (1, 0, -0));
#28 = AXIS2_PLACEMENT_3D('NONE', #25, #26, #27);
#29 = CYLINDRICAL_SURFACE('NONE', #28, 0.01);
#30 = CARTESIAN_POINT('NONE', (0, -0.01, -0));
#31 = DIRECTION('NONE', (0, 1, 0));
#32 = AXIS2_PLACEMENT_3D('NONE', #30, #31, $);
#33 = PLANE('NONE', #32);
#34 = CARTESIAN_POINT('NONE', (0, 0, -0));
#35 = DIRECTION('NONE', (0, 1, 0));
#36 = AXIS2_PLACEMENT_3D('NONE', #34, #35, $);
#37 = PLANE('NONE', #36);
#38 = ORIENTED_EDGE('NONE', *, *, #22, .T.);
#39 = ORIENTED_EDGE('NONE', *, *, #24, .F.);
#40 = EDGE_LOOP('NONE', (#38));
#41 = FACE_BOUND('NONE', #40, .T.);
#42 = EDGE_LOOP('NONE', (#39));
#43 = FACE_BOUND('NONE', #42, .T.);
#44 = ADVANCED_FACE('NONE', (#41, #43), #29, .T.);
#45 = ORIENTED_EDGE('NONE', *, *, #22, .F.);
#46 = EDGE_LOOP('NONE', (#45));
#47 = FACE_BOUND('NONE', #46, .T.);
#48 = ADVANCED_FACE('NONE', (#47), #33, .F.);
#49 = ORIENTED_EDGE('NONE', *, *, #24, .T.);
#50 = EDGE_LOOP('NONE', (#49));
#51 = FACE_BOUND('NONE', #50, .T.);
#52 = ADVANCED_FACE('NONE', (#51), #37, .T.);
#53 = CLOSED_SHELL('NONE', (#44, #48, #52));
#54 = MANIFOLD_SOLID_BREP('NONE', #53);
#55 = APPLICATION_CONTEXT('configuration controlled 3D design of mechanical parts and assemblies');
#56 = PRODUCT_DEFINITION_CONTEXT('part definition', #55, 'design');
#57 = PRODUCT('UNIDENTIFIED_PRODUCT', 'NONE', $, ());
#58 = PRODUCT_DEFINITION_FORMATION('', $, #57);
#59 = PRODUCT_DEFINITION('design', $, #58, #56);
#60 = PRODUCT_DEFINITION_SHAPE('NONE', $, #59);
#61 = ADVANCED_BREP_SHAPE_REPRESENTATION('NONE', (#54), #3);
#62 = SHAPE_DEFINITION_REPRESENTATION(#60, #61);
ENDSEC;
END-ISO-10303-21;

View File

@ -994,39 +994,6 @@ impl Node<MemberExpression> {
// Check the property and object match -- e.g. ints for arrays, strs for objects.
match (object, property, self.computed) {
(KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
"yAxis" => {
let (p, u) = plane.info.y_axis.as_3_dims();
Ok(KclValue::array_from_point3d(
p,
NumericType::Known(crate::exec::UnitType::Length(u)),
vec![meta],
))
}
"xAxis" => {
let (p, u) = plane.info.x_axis.as_3_dims();
Ok(KclValue::array_from_point3d(
p,
NumericType::Known(crate::exec::UnitType::Length(u)),
vec![meta],
))
}
"origin" => {
let (p, u) = plane.info.origin.as_3_dims();
Ok(KclValue::array_from_point3d(
p,
NumericType::Known(crate::exec::UnitType::Length(u)),
vec![meta],
))
}
other => Err(KclError::new_undefined_value(
KclErrorDetails::new(
format!("Property '{other}' not found in plane"),
vec![self.clone().into()],
),
None,
)),
},
(KclValue::Object { value: map, meta: _ }, Property::String(property), false) => {
if let Some(value) = map.get(&property) {
Ok(value.to_owned())
@ -1046,22 +1013,7 @@ impl Node<MemberExpression> {
vec![self.clone().into()],
)))
}
(KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
if i == 0
&& let Some(value) = map.get("x")
{
return Ok(value.to_owned());
}
if i == 1
&& let Some(value) = map.get("y")
{
return Ok(value.to_owned());
}
if i == 2
&& let Some(value) = map.get("z")
{
return Ok(value.to_owned());
}
(KclValue::Object { .. }, p, _) => {
let t = p.type_name();
let article = article_for(t);
Err(KclError::new_semantic(KclErrorDetails::new(
@ -2253,12 +2205,4 @@ y = x[0mm + 1]
"#;
parse_execute(ast).await.unwrap_err();
}
#[tokio::test(flavor = "multi_thread")]
async fn getting_property_of_plane() {
// let ast = include_str!("../../tests/inputs/planestuff.kcl");
let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
parse_execute(&ast).await.unwrap();
}
}

View File

@ -921,12 +921,6 @@ impl Point3d {
units: UnitLen::Unknown,
}
}
pub fn as_3_dims(&self) -> ([f64; 3], UnitLen) {
let p = [self.x, self.y, self.z];
let u = self.units;
(p, u)
}
}
impl From<[TyF64; 3]> for Point3d {

View File

@ -458,31 +458,6 @@ impl KclValue {
}
}
/// Put the point into a KCL point.
pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
let [x, y, z] = p;
Self::HomArray {
value: vec![
Self::Number {
value: x,
meta: meta.clone(),
ty,
},
Self::Number {
value: y,
meta: meta.clone(),
ty,
},
Self::Number {
value: z,
meta: meta.clone(),
ty,
},
],
ty: ty.into(),
}
}
pub(crate) fn as_usize(&self) -> Option<usize> {
match self {
KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),

View File

@ -1,60 +0,0 @@
// There are 3 ways to define a plane in KCL, according to https://zoo.dev/docs/kcl-std/types/std-types-Plane
// - A default plane
// - Modifying a default plane e.g. via offsetPlane
// - Defining your own struct
// This file tests they all work equivalently.
// Define a plane using struct representation.
myPlane = {
origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
}
// Prove we can get its axes and origin.
ax = myPlane.xAxis
assert(ax[0], isEqualTo = 1)
assert(ax[1], isEqualTo = 0)
assert(ax[2], isEqualTo = 0)
ay = myPlane.yAxis
assert(ay[0], isEqualTo = 0)
assert(ay[1], isEqualTo = 1)
assert(ay[2], isEqualTo = 0)
aorigin = myPlane.origin
assert(aorigin[0], isEqualTo = 0)
assert(aorigin[1], isEqualTo = 0)
assert(aorigin[2], isEqualTo = 0)
// Define a plane using standard planes.
myOtherPlane = XY
// Prove we can get its axes and origin.
axOther = myOtherPlane.xAxis
assert(axOther[0], isEqualTo = 1)
assert(axOther[1], isEqualTo = 0)
assert(axOther[2], isEqualTo = 0)
ayOther = myOtherPlane.yAxis
assert(ayOther[0], isEqualTo = 0)
assert(ayOther[1], isEqualTo = 1)
assert(ayOther[2], isEqualTo = 0)
aoriginOther = myOtherPlane.origin
assert(aoriginOther[0], isEqualTo = 0)
assert(aoriginOther[1], isEqualTo = 0)
assert(aoriginOther[2], isEqualTo = 0)
// Define a plane using a plane-modifying function like offsetPlane.
myAlternatePlane = offsetPlane(XY, offset = 0)
// Prove we can get its axes and origin.
axAlternate = myAlternatePlane.xAxis
assert(axAlternate[0], isEqualTo = 1)
assert(axAlternate[1], isEqualTo = 0)
assert(axAlternate[2], isEqualTo = 0)
ayAlternate = myAlternatePlane.yAxis
assert(ayAlternate[0], isEqualTo = 0)
assert(ayAlternate[1], isEqualTo = 1)
assert(ayAlternate[2], isEqualTo = 0)
aoriginAlternate = myAlternatePlane.origin
assert(aoriginAlternate[0], isEqualTo = 0)
assert(aoriginAlternate[1], isEqualTo = 0)
assert(aoriginAlternate[2], isEqualTo = 0)

View File

@ -4,8 +4,6 @@
URL STATUS
000 https://${BASE_URL}
405 https://api.dev.zoo.dev/oauth2/token/revoke
401 https://api.dev.zoo.dev/users
301 https://discord.gg/JQEpHR7Nt2
404 https://github.com/KittyCAD/engine/issues/3528
404 https://github.com/KittyCAD/modeling-app/commit/${ref}

View File

@ -324,6 +324,8 @@ export class SceneEntities {
group: segment,
scale: factor,
sceneInfra: this.sceneInfra,
// Note: AST and code not available in onCamChange, so constraints won't be checked here
// This is primarily for scaling changes
})
callBack && !err(callBack) && callbacks.push(callBack)
if (segment.name === PROFILE_START) {
@ -729,6 +731,8 @@ export class SceneEntities {
scale,
theme: this.sceneInfra._theme,
isDraft: false,
ast: maybeModdedAst,
code: this.codeManager.code,
})
_profileStart.layers.set(SKETCH_LAYER)
_profileStart.traverse((child) => {
@ -866,6 +870,8 @@ export class SceneEntities {
isSelected,
sceneInfra: this.sceneInfra,
selection,
ast: maybeModdedAst,
code: this.codeManager.code,
})
if (err(result)) return
const { group: _group, updateOverlaysCallback } = result
@ -3252,6 +3258,8 @@ export class SceneEntities {
scale: factor,
prevSegment: sgPaths[index - 1],
sceneInfra: this.sceneInfra,
ast: modifiedAst,
code: this.codeManager.code,
})
if (callBack && !err(callBack)) return callBack

View File

@ -76,12 +76,17 @@ import {
} from '@src/clientSideScene/sceneUtils'
import { angleLengthInfo } from '@src/components/Toolbar/angleLengthInfo'
import type { Coords2d } from '@src/lang/std/sketch'
import { getConstraintInfoKw } from '@src/lang/std/sketch'
import type { SegmentInputs } from '@src/lang/std/stdTypes'
import type { PathToNode } from '@src/lang/wasm'
import type { PathToNode, Program } from '@src/lang/wasm'
import { getTangentialArcToInfo } from '@src/lang/wasm'
import { getNodeFromPath } from '@src/lang/queryAst'
import type { Selections } from '@src/lib/selections'
import type { Themes } from '@src/lib/theme'
import { getThemeColorForThreeJs } from '@src/lib/theme'
import {
getThemeColorForThreeJs,
getPrimaryColorForThreeJs,
} from '@src/lib/theme'
import { err } from '@src/lib/trap'
import { isClockwise, normaliseAngle, roundOff } from '@src/lib/utils'
import { getTangentPointFromPreviousArc } from '@src/lib/utils2d'
@ -95,6 +100,7 @@ import toast from 'react-hot-toast'
import { ARG_INTERIOR_ABSOLUTE } from '@src/lang/constants'
const ANGLE_INDICATOR_RADIUS = 30 // in px
interface CreateSegmentArgs {
input: SegmentInputs
prevSegment: Sketch['paths'][number]
@ -108,6 +114,9 @@ interface CreateSegmentArgs {
isSelected?: boolean
sceneInfra: SceneInfra
selection?: Selections
// Add optional AST and code for constraint checking
ast?: Program
code?: string
}
interface UpdateSegmentArgs {
@ -116,6 +125,9 @@ interface UpdateSegmentArgs {
group: Group
sceneInfra: SceneInfra
scale?: number
// Add optional AST and code for constraint checking
ast?: Program
code?: string
}
interface CreateSegmentResult {
@ -144,6 +156,55 @@ export interface SegmentUtils {
) => CreateSegmentResult['updateOverlaysCallback'] | Error
}
/**
* Checks if a segment is fully constrained by examining all its constraint info
*/
function isSegmentFullyConstrained(
pathToNode: PathToNode,
ast: Program,
code: string
): boolean {
try {
const nodeMeta = getNodeFromPath<any>(ast, pathToNode)
if (err(nodeMeta) || nodeMeta.node.type !== 'CallExpressionKw') {
return false
}
const constraintInfos = getConstraintInfoKw(nodeMeta.node, code, pathToNode)
// If there are no constraints, consider it not fully constrained
if (constraintInfos.length === 0) {
return false
}
// Check if all constraints are constrained
return constraintInfos.every((info) => info.isConstrained)
} catch (error) {
console.warn('Error checking segment constraints:', error)
return false
}
}
/**
* Gets the appropriate color for a segment based on selection, constraints, and theme
*/
function getSegmentColor({
theme,
isSelected,
callExpName = '',
isFullyConstrained = false,
}: {
theme: Themes
isSelected: boolean
callExpName?: string
isFullyConstrained: boolean
}): number {
if (isSelected) return 0x0000ff // Blue for selected
if (callExpName === 'close') return 0x444444 // Gray for close segments
if (!isFullyConstrained) return getPrimaryColorForThreeJs() // Primary color for unconstrained segments
return getThemeColorForThreeJs(theme) // Default theme color for constrained segments
}
class StraightSegment implements SegmentUtils {
init: SegmentUtils['init'] = ({
input,
@ -158,13 +219,32 @@ class StraightSegment implements SegmentUtils {
sceneInfra,
prevSegment,
selection,
ast,
code,
}) => {
if (input.type !== 'straight-segment')
return new Error('Invalid segment type')
const { from, to } = input
const baseColor =
callExpName === 'close' ? 0x444444 : getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
// Check if segment is fully constrained (only if we have AST and code)
const isFullyConstrained =
callExpName === 'close'
? true
: ast && code
? isSegmentFullyConstrained(pathToNode, ast, code)
: false
const color = getSegmentColor({
theme,
isSelected: !!isSelected,
callExpName,
isFullyConstrained,
})
const baseColor = !isFullyConstrained
? getPrimaryColorForThreeJs()
: callExpName === 'close'
? 0x444444
: getThemeColorForThreeJs(theme)
const meshType = isDraftSegment
? STRAIGHT_SEGMENT_DASH
: STRAIGHT_SEGMENT_BODY
@ -250,12 +330,40 @@ class StraightSegment implements SegmentUtils {
group,
scale = 1,
sceneInfra,
ast,
code,
}) => {
if (input.type !== 'straight-segment')
return new Error('Invalid segment type')
const { from, to } = input
group.userData.from = from
group.userData.to = to
// Check if segment is fully constrained and update color if needed
if (ast && code) {
const pathToNode = group.userData.pathToNode
const isFullyConstrained =
group.userData.callExpName === 'close'
? true
: isSegmentFullyConstrained(pathToNode, ast, code)
const color = getSegmentColor({
theme: sceneInfra._theme,
isSelected: group.userData.isSelected,
callExpName: group.userData.callExpName,
isFullyConstrained,
})
// Update the material color
const straightSegmentBody = group.children.find(
(child) => child.userData.type === STRAIGHT_SEGMENT_BODY
) as Mesh
if (
straightSegmentBody &&
straightSegmentBody.material instanceof MeshBasicMaterial
) {
straightSegmentBody.material.color.set(color)
}
}
const shape = createLineShape(scale)
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const labelGroup = group.getObjectByName(SEGMENT_LENGTH_LABEL) as Group
@ -391,6 +499,8 @@ class TangentialArcToSegment implements SegmentUtils {
theme,
isSelected,
sceneInfra,
ast,
code,
}) => {
if (input.type !== 'straight-segment')
return new Error('Invalid segment type')
@ -409,8 +519,19 @@ class TangentialArcToSegment implements SegmentUtils {
isDashed: isDraftSegment,
scale,
})
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
// Check if segment is fully constrained (only if we have AST and code)
const isFullyConstrained =
ast && code ? isSegmentFullyConstrained(pathToNode, ast, code) : false
const color = getSegmentColor({
theme,
isSelected: !!isSelected,
isFullyConstrained,
})
const baseColor = !isFullyConstrained
? getPrimaryColorForThreeJs()
: getThemeColorForThreeJs(theme)
const body = new MeshBasicMaterial({ color })
const mesh = new Mesh(geometry, body)
const arrowGroup = createArrowhead(scale, theme, color)
@ -453,6 +574,8 @@ class TangentialArcToSegment implements SegmentUtils {
group,
scale = 1,
sceneInfra,
ast,
code,
}) => {
if (input.type !== 'straight-segment')
return new Error('Invalid segment type')
@ -460,6 +583,32 @@ class TangentialArcToSegment implements SegmentUtils {
group.userData.from = from
group.userData.to = to
group.userData.prevSegment = prevSegment
// Check if segment is fully constrained and update color if needed
if (ast && code) {
const pathToNode = group.userData.pathToNode
const isFullyConstrained = isSegmentFullyConstrained(
pathToNode,
ast,
code
)
const color = getSegmentColor({
theme: sceneInfra._theme,
isSelected: group.userData.isSelected,
isFullyConstrained,
})
// Update the material color
const tangentialArcSegmentBody = group.children.find(
(child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY
) as Mesh
if (
tangentialArcSegmentBody &&
tangentialArcSegmentBody.material instanceof MeshBasicMaterial
) {
tangentialArcSegmentBody.material.color.set(color)
}
}
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
@ -588,13 +737,26 @@ class CircleSegment implements SegmentUtils {
theme,
isSelected,
sceneInfra,
ast,
code,
}) => {
if (input.type !== 'arc-segment') {
return new Error('Invalid segment type')
}
const { from, center, radius } = input
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
// Check if segment is fully constrained
const isFullyConstrained =
ast && code ? isSegmentFullyConstrained(pathToNode, ast, code) : false
const color = getSegmentColor({
theme,
isSelected: !!isSelected,
isFullyConstrained,
})
const baseColor = !isFullyConstrained
? getPrimaryColorForThreeJs()
: getThemeColorForThreeJs(theme)
const group = new Group()
const geometry = createArcGeometry({
@ -678,6 +840,8 @@ class CircleSegment implements SegmentUtils {
group,
scale = 1,
sceneInfra,
ast,
code,
}) => {
if (input.type !== 'arc-segment') {
return new Error('Invalid segment type')
@ -687,6 +851,32 @@ class CircleSegment implements SegmentUtils {
group.userData.center = center
group.userData.radius = radius
group.userData.prevSegment = prevSegment
// Check if segment is fully constrained and update color if needed
if (ast && code) {
const pathToNode = group.userData.pathToNode
const isFullyConstrained = isSegmentFullyConstrained(
pathToNode,
ast,
code
)
const color = getSegmentColor({
theme: sceneInfra._theme,
isSelected: group.userData.isSelected,
isFullyConstrained,
})
// Update the material color
const circleSegmentBody = group.children.find(
(child) => child.userData.type === CIRCLE_SEGMENT_BODY
) as Mesh
if (
circleSegmentBody &&
circleSegmentBody.material instanceof MeshBasicMaterial
) {
circleSegmentBody.material.color.set(color)
}
}
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const radiusLengthIndicator = group.getObjectByName(
SEGMENT_LENGTH_LABEL
@ -831,6 +1021,8 @@ class CircleThreePointSegment implements SegmentUtils {
isSelected = false,
sceneInfra,
prevSegment,
ast,
code,
}) => {
if (input.type !== 'circle-three-point-segment') {
return new Error('Invalid segment type')
@ -845,8 +1037,19 @@ class CircleThreePointSegment implements SegmentUtils {
p3[1]
)
const center: [number, number] = [center_x, center_y]
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
// Check if segment is fully constrained
const isFullyConstrained =
ast && code ? isSegmentFullyConstrained(pathToNode, ast, code) : false
const color = getSegmentColor({
theme,
isSelected: !!isSelected,
isFullyConstrained,
})
const baseColor = !isFullyConstrained
? getPrimaryColorForThreeJs()
: getThemeColorForThreeJs(theme)
const group = new Group()
const geometry = createArcGeometry({
@ -919,6 +1122,8 @@ class CircleThreePointSegment implements SegmentUtils {
group,
scale = 1,
sceneInfra,
ast,
code,
}) => {
if (input.type !== 'circle-three-point-segment') {
return new Error('Invalid segment type')
@ -927,6 +1132,32 @@ class CircleThreePointSegment implements SegmentUtils {
group.userData.p1 = p1
group.userData.p2 = p2
group.userData.p3 = p3
// Check if segment is fully constrained and update color if needed
if (ast && code) {
const pathToNode = group.userData.pathToNode
const isFullyConstrained = isSegmentFullyConstrained(
pathToNode,
ast,
code
)
const color = getSegmentColor({
theme: sceneInfra._theme,
isSelected: group.userData.isSelected,
isFullyConstrained,
})
// Update the material color
const circleSegmentBody = group.children.find(
(child) => child.userData.type === CIRCLE_THREE_POINT_SEGMENT_BODY
) as Mesh
if (
circleSegmentBody &&
circleSegmentBody.material instanceof MeshBasicMaterial
) {
circleSegmentBody.material.color.set(color)
}
}
const { center_x, center_y, radius } = calculate_circle_from_3_points(
p1[0],
p1[1],
@ -1048,13 +1279,26 @@ class ArcSegment implements SegmentUtils {
theme,
isSelected,
sceneInfra,
ast,
code,
}) => {
if (input.type !== 'arc-segment') {
return new Error('Invalid segment type')
}
const { from, to, center, radius, ccw } = input
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
// Check if segment is fully constrained
const isFullyConstrained =
ast && code ? isSegmentFullyConstrained(pathToNode, ast, code) : false
const color = getSegmentColor({
theme,
isSelected: !!isSelected,
isFullyConstrained,
})
const baseColor = !isFullyConstrained
? getPrimaryColorForThreeJs()
: getThemeColorForThreeJs(theme)
// Calculate start and end angles
const startAngle = Math.atan2(from[1] - center[1], from[0] - center[0])
@ -1195,6 +1439,8 @@ class ArcSegment implements SegmentUtils {
group,
scale = 1,
sceneInfra,
ast,
code,
}) => {
if (input.type !== 'arc-segment') {
return new Error('Invalid segment type')
@ -1207,6 +1453,32 @@ class ArcSegment implements SegmentUtils {
group.userData.ccw = ccw
group.userData.prevSegment = prevSegment
// Check if segment is fully constrained and update color if needed
if (ast && code) {
const pathToNode = group.userData.pathToNode
const isFullyConstrained = isSegmentFullyConstrained(
pathToNode,
ast,
code
)
const color = getSegmentColor({
theme: sceneInfra._theme,
isSelected: group.userData.isSelected,
isFullyConstrained,
})
// Update the material color
const arcSegmentBody = group.children.find(
(child) => child.userData.type === ARC_SEGMENT_BODY
) as Mesh
if (
arcSegmentBody &&
arcSegmentBody.material instanceof MeshBasicMaterial
) {
arcSegmentBody.material.color.set(color)
}
}
// Calculate start and end angles
const startAngle = Math.atan2(from[1] - center[1], from[0] - center[0])
const endAngle = Math.atan2(to[1] - center[1], to[0] - center[0])
@ -1403,6 +1675,8 @@ class ThreePointArcSegment implements SegmentUtils {
isSelected = false,
sceneInfra,
prevSegment,
ast,
code,
}) => {
if (input.type !== 'circle-three-point-segment') {
return new Error('Invalid segment type')
@ -1417,8 +1691,19 @@ class ThreePointArcSegment implements SegmentUtils {
p3[1]
)
const center: [number, number] = [center_x, center_y]
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
// Check if segment is fully constrained
const isFullyConstrained =
ast && code ? isSegmentFullyConstrained(pathToNode, ast, code) : false
const color = getSegmentColor({
theme,
isSelected: !!isSelected,
isFullyConstrained,
})
const baseColor = !isFullyConstrained
? getPrimaryColorForThreeJs()
: getThemeColorForThreeJs(theme)
// Calculate start and end angles
const startAngle = Math.atan2(p1[1] - center[1], p1[0] - center[0])
@ -1500,6 +1785,8 @@ class ThreePointArcSegment implements SegmentUtils {
group,
scale = 1,
sceneInfra,
ast,
code,
}) => {
if (input.type !== 'circle-three-point-segment') {
return new Error('Invalid segment type')
@ -1523,6 +1810,32 @@ class ThreePointArcSegment implements SegmentUtils {
group.userData.radius = radius
group.userData.prevSegment = prevSegment
// Check if segment is fully constrained and update color if needed
if (ast && code) {
const pathToNode = group.userData.pathToNode
const isFullyConstrained = isSegmentFullyConstrained(
pathToNode,
ast,
code
)
const color = getSegmentColor({
theme: sceneInfra._theme,
isSelected: group.userData.isSelected,
isFullyConstrained,
})
// Update the material color
const arcSegmentBody = group.children.find(
(child) => child.userData.type === THREE_POINT_ARC_SEGMENT_BODY
) as Mesh
if (
arcSegmentBody &&
arcSegmentBody.material instanceof MeshBasicMaterial
) {
arcSegmentBody.material.color.set(color)
}
}
// Calculate start and end angles
const startAngle = Math.atan2(p1[1] - center[1], p1[0] - center[0])
const endAngle = Math.atan2(p3[1] - center[1], p3[0] - center[0])
@ -1619,6 +1932,8 @@ export function createProfileStartHandle({
theme,
isSelected,
size = 12,
ast,
code,
...rest
}: {
from: Coords2d
@ -1626,15 +1941,30 @@ export function createProfileStartHandle({
theme: Themes
isSelected?: boolean
size?: number
ast?: Program
code?: string
} & (
| { isDraft: true }
| { isDraft: false; id: string; pathToNode: PathToNode }
)) {
const group = new Group()
// Check if profile start is fully constrained (only if we have AST, code, and it's not a draft)
const isFullyConstrained =
!isDraft && ast && code && 'pathToNode' in rest
? isSegmentFullyConstrained(rest.pathToNode, ast, code)
: false
const geometry = new BoxGeometry(size, size, size) // in pixels scaled later
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
const color = getSegmentColor({
theme,
isSelected: !!isSelected,
callExpName: 'profileStart',
isFullyConstrained,
})
const baseColor = !isFullyConstrained
? getPrimaryColorForThreeJs()
: getThemeColorForThreeJs(theme)
const body = new MeshBasicMaterial({ color })
const mesh = new Mesh(geometry, body)
@ -1645,6 +1975,7 @@ export function createProfileStartHandle({
from,
isSelected,
baseColor,
isFullyConstrained,
...rest,
}
group.name = isDraft ? DRAFT_POINT : PROFILE_START

View File

@ -7,7 +7,7 @@ import {
LanguageServerClient,
LspWorkerEventType,
} from '@kittycad/codemirror-lsp-client'
import { TEST } from '@src/env'
import { TEST, VITE_KC_API_BASE_URL } from '@src/env'
import React, { createContext, useContext, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import type * as LSP from 'vscode-languageserver-protocol'
@ -28,7 +28,6 @@ import type { FileEntry } from '@src/lib/project'
import { codeManager } from '@src/lib/singletons'
import { err } from '@src/lib/trap'
import { useToken } from '@src/lib/singletons'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
return []
@ -86,7 +85,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
const initEvent: KclWorkerOptions = {
wasmUrl: wasmUrl(),
token: token,
apiBaseUrl: withAPIBaseURL(''),
apiBaseUrl: VITE_KC_API_BASE_URL,
}
lspWorker.postMessage({
worker: LspWorker.Kcl,
@ -179,7 +178,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
const initEvent: CopilotWorkerOptions = {
wasmUrl: wasmUrl(),
token: token,
apiBaseUrl: withAPIBaseURL(''),
apiBaseUrl: VITE_KC_API_BASE_URL,
}
lspWorker.postMessage({
worker: LspWorker.Copilot,

View File

@ -24,12 +24,7 @@ import {
getOperationVariableName,
stdLibMap,
} from '@src/lib/operations'
import {
editorManager,
kclManager,
rustContext,
sceneInfra,
} from '@src/lib/singletons'
import { editorManager, kclManager, rustContext } from '@src/lib/singletons'
import {
featureTreeMachine,
featureTreeMachineDefaultContext,
@ -39,20 +34,11 @@ import {
kclEditorActor,
selectionEventSelector,
} from '@src/machines/kclEditorMachine'
import type { Plane } from '@rust/kcl-lib/bindings/Artifact'
import {
selectDefaultSketchPlane,
selectOffsetSketchPlane,
} from '@src/lib/selections'
import type { DefaultPlaneStr } from '@src/lib/planes'
export const FeatureTreePane = () => {
const isEditorMounted = useSelector(kclEditorActor, editorIsMountedSelector)
const lastSelectionEvent = useSelector(kclEditorActor, selectionEventSelector)
const { send: modelingSend, state: modelingState } = useModelingContext()
const sketchNoFace = modelingState.matches('Sketch no face')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_featureTreeState, featureTreeSend] = useMachine(
featureTreeMachine.provide({
@ -209,7 +195,6 @@ export const FeatureTreePane = () => {
key={key}
item={operation}
send={featureTreeSend}
sketchNoFace={sketchNoFace}
/>
)
})}
@ -266,7 +251,6 @@ const OperationItemWrapper = ({
customSuffix,
className,
selectable = true,
greyedOut = false,
...props
}: React.HTMLAttributes<HTMLButtonElement> & {
icon: CustomIconName
@ -278,19 +262,18 @@ const OperationItemWrapper = ({
menuItems?: ComponentProps<typeof ContextMenu>['items']
errors?: Diagnostic[]
selectable?: boolean
greyedOut?: boolean
}) => {
const menuRef = useRef<HTMLDivElement>(null)
return (
<div
ref={menuRef}
className={`flex select-none items-center group/item my-0 py-0.5 px-1 ${selectable ? 'focus-within:bg-primary/10 hover:bg-primary/5' : ''} ${greyedOut ? 'opacity-50 cursor-not-allowed' : ''}`}
className={`flex select-none items-center group/item my-0 py-0.5 px-1 ${selectable ? 'focus-within:bg-primary/10 hover:bg-primary/5' : ''}`}
data-testid="feature-tree-operation-item"
>
<button
{...props}
className={`reset !py-0.5 !px-1 flex-1 flex items-center gap-2 text-left text-base ${selectable ? 'border-transparent dark:border-transparent' : '!border-transparent cursor-default'} ${className}`}
className={`reset !py-0.5 !px-1 flex-1 flex items-center gap-2 text-left text-base ${selectable ? 'border-transparent dark:border-transparent' : 'border-none cursor-default'} ${className}`}
>
<CustomIcon name={icon} className="w-5 h-5 block" />
<div className="flex flex-1 items-baseline align-baseline">
@ -328,7 +311,6 @@ const OperationItemWrapper = ({
const OperationItem = (props: {
item: Operation
send: Prop<Actor<typeof featureTreeMachine>, 'send'>
sketchNoFace: boolean
}) => {
const kclContext = useKclContext()
const name = getOperationLabel(props.item)
@ -361,22 +343,15 @@ const OperationItem = (props: {
}, [kclContext.diagnostics.length])
function selectOperation() {
if (props.sketchNoFace) {
if (isOffsetPlane(props.item)) {
const artifact = findOperationArtifact(props.item)
void selectOffsetSketchPlane(artifact)
}
} else {
if (props.item.type === 'GroupEnd') {
return
}
props.send({
type: 'selectOperation',
data: {
targetSourceRange: sourceRangeFromRust(props.item.sourceRange),
},
})
if (props.item.type === 'GroupEnd') {
return
}
props.send({
type: 'selectOperation',
data: {
targetSourceRange: sourceRangeFromRust(props.item.sourceRange),
},
})
}
/**
@ -457,20 +432,6 @@ const OperationItem = (props: {
}
}
function startSketchOnOffsetPlane() {
if (isOffsetPlane(props.item)) {
const artifact = findOperationArtifact(props.item)
if (artifact?.id) {
sceneInfra.modelingSend({
type: 'Enter sketch',
data: { forceNewSketch: true },
})
void selectOffsetSketchPlane(artifact)
}
}
}
const menuItems = useMemo(
() => [
<ContextMenuItem
@ -516,13 +477,6 @@ const OperationItem = (props: {
</ContextMenuItem>,
]
: []),
...(isOffsetPlane(props.item)
? [
<ContextMenuItem onClick={startSketchOnOffsetPlane}>
Start Sketch
</ContextMenuItem>,
]
: []),
...(props.item.type === 'StdLibCall' ||
props.item.type === 'VariableDeclaration'
? [
@ -596,63 +550,22 @@ const OperationItem = (props: {
[props.item, props.send]
)
const enabled = !props.sketchNoFace || isOffsetPlane(props.item)
return (
<OperationItemWrapper
selectable={enabled}
icon={getOperationIcon(props.item)}
name={name}
variableName={variableName}
valueDetail={valueDetail}
menuItems={menuItems}
onClick={selectOperation}
onDoubleClick={props.sketchNoFace ? undefined : enterEditFlow} // no double click in "Sketch no face" mode
onDoubleClick={enterEditFlow}
errors={errors}
greyedOut={!enabled}
/>
)
}
const DefaultPlanes = () => {
const { state: modelingState, send } = useModelingContext()
const sketchNoFace = modelingState.matches('Sketch no face')
const onClickPlane = useCallback(
(planeId: string) => {
if (sketchNoFace) {
selectDefaultSketchPlane(planeId)
} else {
const foundDefaultPlane =
rustContext.defaultPlanes !== null &&
Object.entries(rustContext.defaultPlanes).find(
([, plane]) => plane === planeId
)
if (foundDefaultPlane) {
send({
type: 'Set selection',
data: {
selectionType: 'defaultPlaneSelection',
selection: {
name: foundDefaultPlane[0] as DefaultPlaneStr,
id: planeId,
},
},
})
}
}
},
[sketchNoFace]
)
const startSketchOnDefaultPlane = useCallback((planeId: string) => {
sceneInfra.modelingSend({
type: 'Enter sketch',
data: { forceNewSketch: true },
})
selectDefaultSketchPlane(planeId)
}, [])
const defaultPlanes = rustContext.defaultPlanes
if (!defaultPlanes) return null
@ -690,15 +603,7 @@ const DefaultPlanes = () => {
customSuffix={plane.customSuffix}
icon={'plane'}
name={plane.name}
selectable={true}
onClick={() => onClickPlane(plane.id)}
menuItems={[
<ContextMenuItem
onClick={() => startSketchOnDefaultPlane(plane.id)}
>
Start Sketch
</ContextMenuItem>,
]}
selectable={false}
visibilityToggle={{
visible: modelingState.context.defaultPlaneVisibility[plane.key],
onVisibilityChange: () => {
@ -715,17 +620,3 @@ const DefaultPlanes = () => {
</div>
)
}
type StdLibCallOp = Extract<Operation, { type: 'StdLibCall' }>
const isOffsetPlane = (item: Operation): item is StdLibCallOp => {
return item.type === 'StdLibCall' && item.name === 'offsetPlane'
}
const findOperationArtifact = (item: StdLibCallOp) => {
const nodePath = JSON.stringify(item.nodePath)
const artifact = [...kclManager.artifactGraph.values()].find(
(a) => JSON.stringify((a as Plane).codeRef?.nodePath) === nodePath
)
return artifact
}

View File

@ -10,22 +10,13 @@ import {
import { useModelingContext } from '@src/hooks/useModelingContext'
import type { AxisNames } from '@src/lib/constants'
import { VIEW_NAMES_SEMANTIC } from '@src/lib/constants'
import { kclManager, sceneInfra } from '@src/lib/singletons'
import { err, reportRejection } from '@src/lib/trap'
import { sceneInfra } from '@src/lib/singletons'
import { reportRejection } from '@src/lib/trap'
import { useSettings } from '@src/lib/singletons'
import { resetCameraPosition } from '@src/lib/resetCameraPosition'
import type { Selections } from '@src/lib/selections'
import {
selectDefaultSketchPlane,
selectOffsetSketchPlane,
} from '@src/lib/selections'
export function useViewControlMenuItems() {
const { state: modelingState, send: modelingSend } = useModelingContext()
const selectedPlaneId = getCurrentPlaneId(
modelingState.context.selectionRanges
)
const settings = useSettings()
const shouldLockView =
modelingState.matches('Sketch') &&
@ -65,35 +56,9 @@ export function useViewControlMenuItems() {
Center view on selection
</ContextMenuItem>,
<ContextMenuDivider />,
<ContextMenuItem
onClick={() => {
if (selectedPlaneId) {
sceneInfra.modelingSend({
type: 'Enter sketch',
data: { forceNewSketch: true },
})
const defaultSketchPlaneSelected =
selectDefaultSketchPlane(selectedPlaneId)
if (
!err(defaultSketchPlaneSelected) &&
defaultSketchPlaneSelected
) {
return
}
const artifact = kclManager.artifactGraph.get(selectedPlaneId)
void selectOffsetSketchPlane(artifact)
}
}}
disabled={!selectedPlaneId}
>
Start sketch on selection
</ContextMenuItem>,
<ContextMenuDivider />,
<ContextMenuItemRefresh />,
],
[VIEW_NAMES_SEMANTIC, shouldLockView, selectedPlaneId]
[VIEW_NAMES_SEMANTIC, shouldLockView]
)
return menuItems
}
@ -112,21 +77,3 @@ export function ViewControlContextMenu({
/>
)
}
function getCurrentPlaneId(selectionRanges: Selections): string | null {
const defaultPlane = selectionRanges.otherSelections.find(
(selection) => typeof selection === 'object' && 'name' in selection
)
if (defaultPlane) {
return defaultPlane.id
}
const planeSelection = selectionRanges.graphSelections.find(
(selection) => selection.artifact?.type === 'plane'
)
if (planeSelection) {
return planeSelection.artifact?.id || null
}
return null
}

View File

@ -8,14 +8,13 @@ export const NODE_ENV = env.NODE_ENV as string | undefined
export const VITE_KC_API_WS_MODELING_URL = env.VITE_KC_API_WS_MODELING_URL as
| string
| undefined
export const VITE_KITTYCAD_API_BASE_URL = env.VITE_KITTYCAD_API_BASE_URL
export const VITE_KC_API_BASE_URL = env.VITE_KC_API_BASE_URL
export const VITE_KC_SITE_BASE_URL = env.VITE_KC_SITE_BASE_URL
export const VITE_KC_SITE_APP_URL = env.VITE_KC_SITE_APP_URL
export const VITE_KC_SKIP_AUTH = env.VITE_KC_SKIP_AUTH as string | undefined
export const VITE_KC_CONNECTION_TIMEOUT_MS =
env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined
export const VITE_KITTYCAD_API_TOKEN = env.VITE_KITTYCAD_API_TOKEN as
| string
| undefined
export const VITE_KC_DEV_TOKEN = env.VITE_KC_DEV_TOKEN as string | undefined
export const PROD = env.PROD as string | undefined
export const TEST = env.TEST as string | undefined
export const DEV = env.DEV as string | undefined

View File

@ -14,15 +14,13 @@ import {
import { isTopLevelModule } from '@src/lang/util'
import type { CallExpressionKw, PathToNode } from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/sourceRange'
import {
getEventForSelectWithPoint,
selectDefaultSketchPlane,
selectOffsetSketchPlane,
} from '@src/lib/selections'
import type { DefaultPlaneStr } from '@src/lib/planes'
import { getEventForSelectWithPoint } from '@src/lib/selections'
import {
editorManager,
engineCommandManager,
kclManager,
rustContext,
sceneEntitiesManager,
sceneInfra,
} from '@src/lib/singletons'
@ -98,18 +96,131 @@ export function useEngineConnectionSubscriptions() {
;(async () => {
let planeOrFaceId = data.entity_id
if (!planeOrFaceId) return
const defaultSketchPlaneSelected =
selectDefaultSketchPlane(planeOrFaceId)
if (
!err(defaultSketchPlaneSelected) &&
defaultSketchPlaneSelected
rustContext.defaultPlanes?.xy === planeOrFaceId ||
rustContext.defaultPlanes?.xz === planeOrFaceId ||
rustContext.defaultPlanes?.yz === planeOrFaceId ||
rustContext.defaultPlanes?.negXy === planeOrFaceId ||
rustContext.defaultPlanes?.negXz === planeOrFaceId ||
rustContext.defaultPlanes?.negYz === planeOrFaceId
) {
let planeId = planeOrFaceId
const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = {
[rustContext.defaultPlanes.xy]: 'XY',
[rustContext.defaultPlanes.xz]: 'XZ',
[rustContext.defaultPlanes.yz]: 'YZ',
[rustContext.defaultPlanes.negXy]: '-XY',
[rustContext.defaultPlanes.negXz]: '-XZ',
[rustContext.defaultPlanes.negYz]: '-YZ',
}
// TODO can we get this information from rust land when it creates the default planes?
// maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs)
let zAxis: [number, number, number] = [0, 0, 1]
let yAxis: [number, number, number] = [0, 1, 0]
// get unit vector from camera position to target
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
if (rustContext.defaultPlanes?.xy === planeId) {
zAxis = [0, 0, 1]
yAxis = [0, 1, 0]
if (camVector.z < 0) {
zAxis = [0, 0, -1]
planeId = rustContext.defaultPlanes?.negXy || ''
}
} else if (rustContext.defaultPlanes?.yz === planeId) {
zAxis = [1, 0, 0]
yAxis = [0, 0, 1]
if (camVector.x < 0) {
zAxis = [-1, 0, 0]
planeId = rustContext.defaultPlanes?.negYz || ''
}
} else if (rustContext.defaultPlanes?.xz === planeId) {
zAxis = [0, 1, 0]
yAxis = [0, 0, 1]
planeId = rustContext.defaultPlanes?.negXz || ''
if (camVector.y < 0) {
zAxis = [0, -1, 0]
planeId = rustContext.defaultPlanes?.xz || ''
}
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'defaultPlane',
planeId: planeId,
plane: defaultPlaneStrMap[planeId],
zAxis,
yAxis,
},
})
return
}
const artifact = kclManager.artifactGraph.get(planeOrFaceId)
if (await selectOffsetSketchPlane(artifact)) {
if (artifact?.type === 'plane') {
const planeInfo =
await sceneEntitiesManager.getFaceDetails(planeOrFaceId)
// Apply camera-based orientation logic similar to default planes
let zAxis: [number, number, number] = [
planeInfo.z_axis.x,
planeInfo.z_axis.y,
planeInfo.z_axis.z,
]
let yAxis: [number, number, number] = [
planeInfo.y_axis.x,
planeInfo.y_axis.y,
planeInfo.y_axis.z,
]
// Get camera vector to determine which side of the plane we're viewing from
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
// Determine the canonical (absolute) plane orientation
const absZAxis: [number, number, number] = [
Math.abs(zAxis[0]),
Math.abs(zAxis[1]),
Math.abs(zAxis[2]),
]
// Find the dominant axis (like default planes do)
const maxComponent = Math.max(...absZAxis)
const dominantAxisIndex = absZAxis.indexOf(maxComponent)
// Check camera position against canonical orientation (like default planes)
const cameraComponents = [camVector.x, camVector.y, camVector.z]
let negated = cameraComponents[dominantAxisIndex] < 0
if (dominantAxisIndex === 1) {
// offset of the XZ is being weird, not sure if this is a camera bug
negated = !negated
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'offsetPlane',
zAxis,
yAxis,
position: [
planeInfo.origin.x,
planeInfo.origin.y,
planeInfo.origin.z,
].map((num) => num / sceneInfra._baseUnitMultiplier) as [
number,
number,
number,
],
planeId: planeOrFaceId,
pathToNode: artifact.codeRef.pathToNode,
negated,
},
})
return
}

View File

@ -483,13 +483,13 @@ export function sketchOnExtrudedFace(
*/
export function addOffsetPlane({
node,
plane,
defaultPlane,
insertIndex,
offset,
planeName,
}: {
node: Node<Program>
plane: Node<Literal> | Node<Name> // Can be DefaultPlaneStr or string for offsetPlanes
defaultPlane: DefaultPlaneStr
insertIndex?: number
offset: Expr
planeName?: string
@ -500,9 +500,11 @@ export function addOffsetPlane({
const newPlane = createVariableDeclaration(
newPlaneName,
createCallExpressionStdLibKw('offsetPlane', plane, [
createLabeledArg('offset', offset),
])
createCallExpressionStdLibKw(
'offsetPlane',
createLiteral(defaultPlane.toUpperCase()),
[createLabeledArg('offset', offset)]
)
)
const insertAt =

View File

@ -1,4 +1,4 @@
import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { VITE_KC_DEV_TOKEN } from '@src/env'
import { createLiteral } from '@src/lang/create'
import type {
@ -40,9 +40,10 @@ import { isOverlap } from '@src/lib/utils'
beforeAll(async () => {
await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => {
engineCommandManager.start({
token: VITE_KITTYCAD_API_TOKEN,
token: VITE_KC_DEV_TOKEN,
width: 256,
height: 256,
setMediaStream: () => {},

View File

@ -4,15 +4,16 @@ import { initPromise } from '@src/lang/wasmUtils'
import { err } from '@src/lib/trap'
import type { Selection } from '@src/lib/selections'
import { engineCommandManager, kclManager } from '@src/lib/singletons'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { VITE_KC_DEV_TOKEN } from '@src/env'
import { modifyAstWithTagsForSelection } from '@src/lang/modifyAst/tagManagement'
beforeAll(async () => {
await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => {
engineCommandManager.start({
token: VITE_KITTYCAD_API_TOKEN,
token: VITE_KC_DEV_TOKEN,
width: 256,
height: 256,
setMediaStream: () => {},

View File

@ -1,5 +1,5 @@
import type { Models } from '@kittycad/lib'
import { VITE_KC_API_WS_MODELING_URL, VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from '@src/env'
import { jsAppSettings } from '@src/lib/settings/settingsUtils'
import { BSON } from 'bson'
@ -400,7 +400,7 @@ class EngineConnection extends EventTarget {
this.send({
type: 'headers',
headers: {
Authorization: `Bearer ${VITE_KITTYCAD_API_TOKEN}`,
Authorization: `Bearer ${VITE_KC_DEV_TOKEN}`,
},
})
}

View File

@ -1,3 +1,4 @@
import { VITE_KC_API_BASE_URL } from '@src/env'
import { UAParser } from 'ua-parser-js'
import type { OsInfo } from '@rust/kcl-lib/bindings/OsInfo'
@ -10,7 +11,6 @@ import { isDesktop } from '@src/lib/isDesktop'
import type RustContext from '@src/lib/rustContext'
import screenshot from '@src/lib/screenshot'
import { APP_VERSION } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
/* eslint-disable suggest-no-throw/suggest-no-throw --
* All the throws in CoreDumpManager are intentional and should be caught and handled properly
@ -35,7 +35,7 @@ export class CoreDumpManager {
codeManager: CodeManager
rustContext: RustContext
token: string | undefined
baseUrl: string = withAPIBaseURL('')
baseUrl: string = VITE_KC_API_BASE_URL
constructor(
engineCommandManager: EngineCommandManager,

View File

@ -26,7 +26,6 @@ import { err } from '@src/lib/trap'
import type { DeepPartial } from '@src/lib/types'
import { getInVariableCase } from '@src/lib/utils'
import { IS_STAGING } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function renameProjectDirectory(
projectPath: string,
@ -698,9 +697,7 @@ export const readTokenFile = async () => {
export const writeTokenFile = async (token: string) => {
const tokenFilePath = await getTokenFilePath()
if (err(token)) return Promise.reject(token)
const result = window.electron.writeFile(tokenFilePath, token)
console.log('token written to disk')
return result
return window.electron.writeFile(tokenFilePath, token)
}
export const writeTelemetryFile = async (content: string) => {
@ -725,9 +722,12 @@ export const setState = async (state: Project | undefined): Promise<void> => {
appStateStore = state
}
export const getUser = async (token: string): Promise<Models['User_type']> => {
export const getUser = async (
token: string,
hostname: string
): Promise<Models['User_type']> => {
try {
const user = await fetch(withAPIBaseURL('/users/me'), {
const user = await fetch(`${hostname}/users/me`, {
headers: new Headers({
Authorization: `Bearer ${token}`,
}),

View File

@ -1,4 +1,4 @@
import { VITE_KC_SITE_APP_URL } from '@src/env'
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_APP_URL } from '@src/env'
import toast from 'react-hot-toast'
import { stringToBase64 } from '@src/lib/base64'
@ -7,7 +7,6 @@ import {
CREATE_FILE_URL_PARAM,
} from '@src/lib/constants'
import { err } from '@src/lib/trap'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export interface FileLinkParams {
code: string
@ -97,7 +96,7 @@ export async function createShortlink(
if (password) {
body.password = password
}
const response = await fetch(withAPIBaseURL('/user/shortlinks'), {
const response = await fetch(`${VITE_KC_API_BASE_URL}/user/shortlinks`, {
method: 'POST',
headers: {
'Content-type': 'application/json',

View File

@ -1,7 +1,7 @@
import type { SelectionRange } from '@codemirror/state'
import { EditorSelection, Transaction } from '@codemirror/state'
import type { Models } from '@kittycad/lib'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_BASE_URL } from '@src/env'
import { diffLines } from 'diff'
import toast from 'react-hot-toast'
import type { TextToCadMultiFileIteration_type } from '@kittycad/lib/dist/types/src/models'
@ -28,7 +28,6 @@ import { uuidv4 } from '@src/lib/utils'
import type { File as KittyCadLibFile } from '@kittycad/lib/dist/types/src/models'
import type { FileMeta } from '@src/lib/types'
import type { RequestedKCLFile } from '@src/machines/systemIO/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
type KclFileMetaMap = {
[execStateFileNamesIndex: number]: Extract<FileMeta, { type: 'kcl' }>
@ -78,7 +77,7 @@ async function submitTextToCadRequest(
})
const response = await fetch(
withAPIBaseURL('/ml/text-to-cad/multi-file/iteration'),
`${VITE_KC_API_BASE_URL}/ml/text-to-cad/multi-file/iteration`,
{
method: 'POST',
headers: {
@ -305,7 +304,7 @@ export async function getPromptToEditResult(
id: string,
token?: string
): Promise<Models['TextToCadMultiFileIteration_type'] | Error> {
const url = withAPIBaseURL(`/async/operations/${id}`)
const url = VITE_KC_API_BASE_URL + '/async/operations/' + id
const data: Models['TextToCadMultiFileIteration_type'] | Error =
await crossPlatformFetch(
url,
@ -336,6 +335,14 @@ export async function doPromptEdit({
const toastId = toast.loading('Submitting to Text-to-CAD API...')
let submitResult
// work around for @kittycad/lib not really being built for the browser
;(window as any).process = {
env: {
ZOO_API_TOKEN: token,
ZOO_HOST: VITE_KC_API_BASE_URL,
},
}
try {
submitResult = await submitPromptToEditToQueue({
prompt,

View File

@ -34,7 +34,6 @@ import {
kclManager,
rustContext,
sceneEntitiesManager,
sceneInfra,
} from '@src/lib/singletons'
import { err } from '@src/lib/trap'
import {
@ -804,156 +803,3 @@ export function getSemanticSelectionType(selectionType: Artifact['type'][]) {
return Array.from(semanticSelectionType)
}
export function selectDefaultSketchPlane(
defaultPlaneId: string
): Error | boolean {
const defaultPlanes = rustContext.defaultPlanes
if (!defaultPlanes) {
return new Error('No default planes defined in rustContext')
}
if (
![
defaultPlanes.xy,
defaultPlanes.xz,
defaultPlanes.yz,
defaultPlanes.negXy,
defaultPlanes.negXz,
defaultPlanes.negYz,
].includes(defaultPlaneId)
) {
// Supplied defaultPlaneId is not a valid default plane id
return false
}
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
// TODO can we get this information from rust land when it creates the default planes?
// maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs)
let zAxis: [number, number, number] = [0, 0, 1]
let yAxis: [number, number, number] = [0, 1, 0]
if (defaultPlanes?.xy === defaultPlaneId) {
zAxis = [0, 0, 1]
yAxis = [0, 1, 0]
if (camVector.z < 0) {
zAxis = [0, 0, -1]
defaultPlaneId = defaultPlanes?.negXy || ''
}
} else if (defaultPlanes?.yz === defaultPlaneId) {
zAxis = [1, 0, 0]
yAxis = [0, 0, 1]
if (camVector.x < 0) {
zAxis = [-1, 0, 0]
defaultPlaneId = defaultPlanes?.negYz || ''
}
} else if (defaultPlanes?.xz === defaultPlaneId) {
zAxis = [0, 1, 0]
yAxis = [0, 0, 1]
defaultPlaneId = defaultPlanes?.negXz || ''
if (camVector.y < 0) {
zAxis = [0, -1, 0]
defaultPlaneId = defaultPlanes?.xz || ''
}
}
const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = {
[defaultPlanes.xy]: 'XY',
[defaultPlanes.xz]: 'XZ',
[defaultPlanes.yz]: 'YZ',
[defaultPlanes.negXy]: '-XY',
[defaultPlanes.negXz]: '-XZ',
[defaultPlanes.negYz]: '-YZ',
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'defaultPlane',
planeId: defaultPlaneId,
plane: defaultPlaneStrMap[defaultPlaneId],
zAxis,
yAxis,
},
})
return true
}
export async function selectOffsetSketchPlane(artifact: Artifact | undefined) {
return new Promise((resolve) => {
if (artifact?.type === 'plane') {
const planeId = artifact.id
void sceneEntitiesManager
.getFaceDetails(planeId)
.then((planeInfo) => {
// Apply camera-based orientation logic similar to default planes
let zAxis: [number, number, number] = [
planeInfo.z_axis.x,
planeInfo.z_axis.y,
planeInfo.z_axis.z,
]
let yAxis: [number, number, number] = [
planeInfo.y_axis.x,
planeInfo.y_axis.y,
planeInfo.y_axis.z,
]
// Get camera vector to determine which side of the plane we're viewing from
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
// Determine the canonical (absolute) plane orientation
const absZAxis: [number, number, number] = [
Math.abs(zAxis[0]),
Math.abs(zAxis[1]),
Math.abs(zAxis[2]),
]
// Find the dominant axis (like default planes do)
const maxComponent = Math.max(...absZAxis)
const dominantAxisIndex = absZAxis.indexOf(maxComponent)
// Check camera position against canonical orientation (like default planes)
const cameraComponents = [camVector.x, camVector.y, camVector.z]
let negated = cameraComponents[dominantAxisIndex] < 0
if (dominantAxisIndex === 1) {
// offset of the XZ is being weird, not sure if this is a camera bug
negated = !negated
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'offsetPlane',
zAxis,
yAxis,
position: [
planeInfo.origin.x,
planeInfo.origin.y,
planeInfo.origin.z,
].map((num) => num / sceneInfra._baseUnitMultiplier) as [
number,
number,
number,
],
planeId,
pathToNode: artifact.codeRef.pathToNode,
negated,
},
})
resolve(true)
})
.catch((error) => {
console.error('Error getting face details:', error)
resolve(false)
})
} else {
// selectOffsetSketchPlane called with an invalid artifact type',
resolve(false)
}
})
}

View File

@ -1,4 +1,4 @@
import { withAPIBaseURL } from '@src/lib/withBaseURL'
import { VITE_KC_API_BASE_URL } from '@src/env'
import EditorManager from '@src/editor/manager'
import { KclManager } from '@src/lang/KclSingleton'
@ -171,7 +171,7 @@ const appMachine = setup({
systemId: BILLING,
input: {
...BILLING_CONTEXT_DEFAULTS,
urlUserService: withAPIBaseURL(''),
urlUserService: VITE_KC_API_BASE_URL,
},
}),
],

View File

@ -1,4 +1,5 @@
import type { Models } from '@kittycad/lib'
import { VITE_KC_API_BASE_URL } from '@src/env'
import toast from 'react-hot-toast'
import type { NavigateFunction } from 'react-router-dom'
import {
@ -18,7 +19,6 @@ import { err, reportRejection } from '@src/lib/trap'
import { toSync } from '@src/lib/utils'
import { getAllSubDirectoriesAtProjectRoot } from '@src/machines/systemIO/snapshotContext'
import { joinOSPaths } from '@src/lib/paths'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function submitTextToCadPrompt(
prompt: string,
@ -32,7 +32,7 @@ export async function submitTextToCadPrompt(
kcl_version: kclManager.kclVersion,
}
// Glb has a smaller footprint than gltf, should we want to render it.
const url = withAPIBaseURL('/ai/text-to-cad/glb?kcl=true')
const url = VITE_KC_API_BASE_URL + '/ai/text-to-cad/glb?kcl=true'
const data: Models['TextToCad_type'] | Error = await crossPlatformFetch(
url,
{
@ -58,7 +58,7 @@ export async function getTextToCadResult(
id: string,
token?: string
): Promise<Models['TextToCad_type'] | Error> {
const url = withAPIBaseURL(`/user/text-to-cad/${id}`)
const url = VITE_KC_API_BASE_URL + '/user/text-to-cad/' + id
const data: Models['TextToCad_type'] | Error = await crossPlatformFetch(
url,
{

View File

@ -1,13 +1,14 @@
import type { Models } from '@kittycad/lib/dist/types/src'
import { VITE_KC_API_BASE_URL } from '@src/env'
import crossPlatformFetch from '@src/lib/crossPlatformFetch'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function sendTelemetry(
id: string,
feedback: Models['MlFeedback_type'],
token?: string
): Promise<void> {
const url = withAPIBaseURL(`/user/text-to-cad/${id}?feedback=${feedback}`)
const url =
VITE_KC_API_BASE_URL + '/user/text-to-cad/' + id + '?feedback=' + feedback
await crossPlatformFetch(
url,
{

View File

@ -1,4 +1,5 @@
import type { AppTheme } from '@rust/kcl-lib/bindings/AppTheme'
import { converter } from 'culori'
/** A media query matcher for dark mode */
export const darkModeMatcher =
@ -58,6 +59,84 @@ export function getOppositeTheme(theme: Themes) {
return resolvedTheme === Themes.Dark ? Themes.Light : Themes.Dark
}
/**
* Converts OKLCH values to RGB using Culori library
* @param l - Lightness (0-1)
* @param c - Chroma (0-1)
* @param h - Hue (0-360 degrees)
* @returns RGB values as [r, g, b] where each component is 0-255
*/
function oklchToRgb(l: number, c: number, h: number): [number, number, number] {
// Create a converter from OKLCH to RGB using Culori
const toRgb = converter('rgb')
// Convert OKLCH to RGB using Culori
const rgb = toRgb({ mode: 'oklch', l, c, h })
if (!rgb) {
// Fallback if conversion fails
return [255, 255, 255]
}
// Clamp values. When OKLCH values represent colors outside the sRGB gamut, the RGB values can be negative or greater than 1.
const clampedR = Math.max(0, Math.min(1, rgb.r))
const clampedG = Math.max(0, Math.min(1, rgb.g))
const clampedB = Math.max(0, Math.min(1, rgb.b))
// Convert from 0-1 range to 0-255 range
return [
Math.round(clampedR * 255),
Math.round(clampedG * 255),
Math.round(clampedB * 255),
]
}
/**
* Gets the primary color from CSS custom properties and converts it to Three.js hex format
* @returns Primary color as a hex number for Three.js, or fallback purple if unable to get CSS value
*/
export function getPrimaryColorForThreeJs(): number {
if (typeof globalThis.window === 'undefined' || !globalThis.document) {
// Fallback for SSR or when DOM is not available
return 0x7c3aed // Default purple
}
try {
const computedStyle = getComputedStyle(document.documentElement)
// Get the individual primary color components
const hue = parseFloat(
computedStyle.getPropertyValue('--primary-hue').trim()
)
const chroma = parseFloat(
computedStyle.getPropertyValue('--primary-chroma').trim()
)
const lightness =
parseFloat(
computedStyle
.getPropertyValue('--primary-lightness')
.replace('%', '')
.trim()
) / 100
if (Number.isNaN(hue) || Number.isNaN(chroma) || Number.isNaN(lightness)) {
console.warn(
'Unable to parse primary color components from CSS, using fallback'
)
return 0x7c3aed // Default purple
}
// Convert OKLCH to RGB
const [r, g, b] = oklchToRgb(lightness, chroma, hue)
// Convert RGB to hex
return (r << 16) | (g << 8) | b
} catch (error) {
console.warn('Error getting primary color from CSS:', error)
return 0x7c3aed // Default purple fallback
}
}
/**
* The engine takes RGBA values from 0-1
* So we convert from the conventional 0-255 found in Figma

View File

@ -1,34 +0,0 @@
import { withAPIBaseURL } from '@src/lib/withBaseURL'
describe('withBaseURL', () => {
/**
* running in the development environment
* the .env.development should load
*/
describe('withAPIBaseUrl', () => {
it('should return base url', () => {
const expected = 'https://api.dev.zoo.dev'
const actual = withAPIBaseURL('')
expect(actual).toBe(expected)
})
it('should return base url with /users', () => {
const expected = 'https://api.dev.zoo.dev/users'
const actual = withAPIBaseURL('/users')
expect(actual).toBe(expected)
})
it('should return a longer base url with /oauth2/token/revoke', () => {
const expected = 'https://api.dev.zoo.dev/oauth2/token/revoke'
const actual = withAPIBaseURL('/oauth2/token/revoke')
expect(actual).toBe(expected)
})
it('should ensure base url does not have ending slash', () => {
const expected = 'https://api.dev.zoo.dev'
const actual = withAPIBaseURL('')
expect(actual).toBe(expected)
const expectedEndsWith = expected[expected.length - 1]
const actualEndsWith = actual[actual.length - 1]
expect(actual).toBe(expected)
expect(actualEndsWith).toBe(expectedEndsWith)
})
})
})

View File

@ -1,5 +1,5 @@
import { VITE_KITTYCAD_API_BASE_URL } from '@src/env'
import { VITE_KC_API_BASE_URL } from '@src/env'
export function withAPIBaseURL(path: string): string {
return VITE_KITTYCAD_API_BASE_URL + path
export default function withBaseUrl(path: string): string {
return VITE_KC_API_BASE_URL + path
}

View File

@ -1,5 +1,10 @@
import type { Models } from '@kittycad/lib'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import {
DEV,
VITE_KC_API_BASE_URL,
VITE_KC_DEV_TOKEN,
VITE_KC_SKIP_AUTH,
} from '@src/env'
import { assign, fromPromise, setup } from 'xstate'
import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants'
@ -10,9 +15,32 @@ import {
} from '@src/lib/desktop'
import { isDesktop } from '@src/lib/isDesktop'
import { markOnce } from '@src/lib/performance'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
import {
default as withBaseURL,
default as withBaseUrl,
} from '@src/lib/withBaseURL'
import { ACTOR_IDS } from '@src/machines/machineConstants'
const SKIP_AUTH = VITE_KC_SKIP_AUTH === 'true' && DEV
const LOCAL_USER: Models['User_type'] = {
id: '8675309',
name: 'Test User',
email: 'kittycad.sidebar.test@example.com',
image: 'https://placekitten.com/200/200',
created_at: 'yesteryear',
updated_at: 'today',
company: 'Test Company',
discord: 'Test User#1234',
github: 'testuser',
phone: '555-555-5555',
first_name: 'Test',
last_name: 'User',
can_train_on_data: false,
is_service_account: false,
deletion_scheduled: false,
}
export interface UserContext {
user?: Models['User_type']
token: string
@ -28,21 +56,11 @@ export type Events =
}
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
/**
* Determine which token do we have persisted to initialize the auth machine
*/
const persistedCookie = getCookie(COOKIE_NAME)
const persistedLocalStorage = localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
const persistedDevToken = VITE_KITTYCAD_API_TOKEN
export const persistedToken =
persistedDevToken || persistedCookie || persistedLocalStorage
console.log('Initial persisted token')
console.table([
['cookie', !!persistedCookie],
['local storage', !!persistedLocalStorage],
['api token', !!persistedDevToken],
])
VITE_KC_DEV_TOKEN ||
getCookie(COOKIE_NAME) ||
localStorage?.getItem(TOKEN_PERSIST_KEY) ||
''
export const authMachine = setup({
types: {} as {
@ -139,7 +157,7 @@ export const authMachine = setup({
async function getUser(input: { token?: string }) {
const token = await getAndSyncStoredToken(input)
const url = withAPIBaseURL('/user')
const url = withBaseURL('/user')
const headers: { [key: string]: string } = {
'Content-Type': 'application/json',
}
@ -147,8 +165,21 @@ async function getUser(input: { token?: string }) {
if (!token && isDesktop()) return Promise.reject(new Error('No token found'))
if (token) headers['Authorization'] = `Bearer ${token}`
if (SKIP_AUTH) {
// For local tests
if (localStorage.getItem('FORCE_NO_IMAGE')) {
LOCAL_USER.image = ''
}
markOnce('code/didAuth')
return {
user: LOCAL_USER,
token,
}
}
const userPromise = isDesktop()
? getUserDesktop(token)
? getUserDesktop(token, VITE_KC_API_BASE_URL)
: fetch(url, {
method: 'GET',
credentials: 'include',
@ -197,28 +228,16 @@ async function getAndSyncStoredToken(input: {
token?: string
}): Promise<string> {
// dev mode
if (VITE_KITTYCAD_API_TOKEN) {
console.log('Token used for authentication')
console.table([['api token', !!VITE_KITTYCAD_API_TOKEN]])
return VITE_KITTYCAD_API_TOKEN
}
if (VITE_KC_DEV_TOKEN) return VITE_KC_DEV_TOKEN
const inputToken = input.token && input.token !== '' ? input.token : ''
const cookieToken = getCookie(COOKIE_NAME)
const localStorageToken = localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
const token = inputToken || cookieToken || localStorageToken
console.log('Token used for authentication')
console.table([
['persisted token', !!inputToken],
['cookie', !!cookieToken],
['local storage', !!localStorageToken],
['api token', !!VITE_KITTYCAD_API_TOKEN],
])
const token =
input.token && input.token !== ''
? input.token
: getCookie(COOKIE_NAME) || localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
if (token) {
// has just logged in, update storage
localStorage.setItem(TOKEN_PERSIST_KEY, token)
if (isDesktop()) {
// has just logged in, update storage
localStorage.setItem(TOKEN_PERSIST_KEY, token)
await writeTokenFile(token)
}
return token
@ -240,7 +259,7 @@ async function logout() {
if (token) {
try {
await fetch(withAPIBaseURL('/oauth2/token/revoke'), {
await fetch(withBaseUrl('/oauth2/token/revoke'), {
method: 'POST',
credentials: 'include',
headers: {
@ -263,7 +282,7 @@ async function logout() {
}
}
return fetch(withAPIBaseURL('/logout'), {
return fetch(withBaseUrl('/logout'), {
method: 'POST',
credentials: 'include',
})

View File

@ -11,7 +11,7 @@ import {
engineCommandManager,
kclManager,
} from '@src/lib/singletons'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { VITE_KC_DEV_TOKEN } from '@src/env'
import { getConstraintInfoKw } from '@src/lang/std/sketch'
import { getNodeFromPath } from '@src/lang/queryAst'
import type { Node } from '@rust/kcl-lib/bindings/Node'
@ -29,9 +29,10 @@ import { removeSingleConstraintInfo } from '@src/lang/modifyAst'
beforeAll(async () => {
await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => {
engineCommandManager.start({
token: VITE_KITTYCAD_API_TOKEN,
token: VITE_KC_DEV_TOKEN,
width: 256,
height: 256,
setMediaStream: () => {},

View File

@ -2609,15 +2609,15 @@ export const modelingMachine = setup({
insertIndex = nodeToEdit[1][0]
}
const selectedPlane = getSelectedPlane(selection)
if (!selectedPlane) {
// Extract the default plane from selection
const plane = selection.otherSelections[0]
if (!(plane && plane instanceof Object && 'name' in plane))
return trap('No plane selected')
}
// Get the default plane name from the selection
const offsetPlaneResult = addOffsetPlane({
node: ast,
plane: selectedPlane,
defaultPlane: plane.name,
offset:
'variableName' in distance
? distance.variableIdentifierAst
@ -5520,33 +5520,6 @@ export function isEditingExistingSketch({
return (hasStartProfileAt && maybePipeExpression.body.length > 1) || hasCircle
}
const getSelectedPlane = (
selection: Selections
): Node<Name> | Node<Literal> | undefined => {
const defaultPlane = selection.otherSelections[0]
if (
defaultPlane &&
defaultPlane instanceof Object &&
'name' in defaultPlane
) {
return createLiteral(defaultPlane.name.toUpperCase())
}
const offsetPlane = selection.graphSelections[0]
if (offsetPlane.artifact?.type === 'plane') {
const artifactId = offsetPlane.artifact?.id
const variableName = Object.entries(kclManager.variables).find(
([_, value]) => {
return value?.type === 'Plane' && value.value?.artifactId === artifactId
}
)
const offsetPlaneName = variableName?.[0]
return offsetPlaneName ? createLocalName(offsetPlaneName) : undefined
}
return undefined
}
export function pipeHasCircle({
sketchDetails,
}: {

View File

@ -1,5 +1,5 @@
import { engineCommandManager, kclManager } from '@src/lib/singletons'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { VITE_KC_DEV_TOKEN } from '@src/env'
import { getModuleIdByFileName, isArray } from '@src/lib/utils'
import { vi, inject } from 'vitest'
import { assertParse } from '@src/lang/wasm'
@ -355,9 +355,10 @@ cases.push(
beforeAll(async () => {
await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => {
engineCommandManager.start({
token: VITE_KITTYCAD_API_TOKEN,
token: VITE_KC_DEV_TOKEN,
width: 256,
height: 256,
setMediaStream: () => {},

View File

@ -70,10 +70,12 @@ dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
// default vite values based on mode
process.env.NODE_ENV ??= viteEnv.MODE
process.env.BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL
process.env.VITE_KC_API_WS_MODELING_URL ??= viteEnv.VITE_KC_API_WS_MODELING_URL
process.env.VITE_KITTYCAD_API_BASE_URL ??= viteEnv.VITE_KITTYCAD_API_BASE_URL
process.env.VITE_KC_API_BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL
process.env.VITE_KC_SITE_BASE_URL ??= viteEnv.VITE_KC_SITE_BASE_URL
process.env.VITE_KC_SITE_APP_URL ??= viteEnv.VITE_KC_SITE_APP_URL
process.env.VITE_KC_SKIP_AUTH ??= viteEnv.VITE_KC_SKIP_AUTH
process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??=
viteEnv.VITE_KC_CONNECTION_TIMEOUT_MS

View File

@ -289,11 +289,12 @@ contextBridge.exposeInMainWorld('electron', {
exposeProcessEnvs([
'NODE_ENV',
'VITE_KC_API_WS_MODELING_URL',
'VITE_KITTYCAD_API_BASE_URL',
'VITE_KC_API_BASE_URL',
'VITE_KC_SITE_BASE_URL',
'VITE_KC_SITE_APP_URL',
'VITE_KC_SKIP_AUTH',
'VITE_KC_CONNECTION_TIMEOUT_MS',
'VITE_KITTYCAD_API_TOKEN',
'VITE_KC_DEV_TOKEN',
'IS_PLAYWRIGHT',

View File

@ -6,7 +6,7 @@ import { Link } from 'react-router-dom'
import { ActionButton } from '@src/components/ActionButton'
import { CustomIcon } from '@src/components/CustomIcon'
import { Logo } from '@src/components/Logo'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_BASE_URL } from '@src/env'
import { APP_NAME } from '@src/lib/constants'
import { isDesktop } from '@src/lib/isDesktop'
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
@ -15,7 +15,6 @@ import { reportRejection } from '@src/lib/trap'
import { toSync } from '@src/lib/utils'
import { authActor, useSettings } from '@src/lib/singletons'
import { APP_VERSION, generateSignInUrl } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
const subtleBorder =
'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
@ -55,7 +54,7 @@ const SignIn = () => {
const signInDesktop = async () => {
// We want to invoke our command to login via device auth.
const userCodeToDisplay = await window.electron
.startDeviceFlow(withAPIBaseURL(location.search))
.startDeviceFlow(VITE_KC_API_BASE_URL + location.search)
.catch(reportError)
if (!userCodeToDisplay) {
console.error('No user code received while trying to log in')