Compare commits

..

13 Commits

Author SHA1 Message Date
5bea90ad9a add API fallback for engine utils lib 2024-10-22 12:58:35 -07:00
d23ddc19eb Merge branch 'main' into mike/engine-utils-wasm 2024-10-22 10:27:12 -07:00
e65358f635 add needed tangential arc params to the serializable path 2024-10-11 11:11:27 -07:00
0a1201e680 cleanup 2024-10-11 10:55:55 -07:00
9db013e672 fmt 2024-10-11 10:47:17 -07:00
0196d72a2d mini code cleanup 2024-10-11 10:44:46 -07:00
e6af4078bd actual working arcs using engine utils 2024-10-10 18:19:26 -07:00
2b233dc705 it works! 2024-10-10 16:58:12 -07:00
b11e8af9c7 _ 2024-10-10 11:52:33 -07:00
c017847d7b Fix to convert JS Promise to async Rust 2024-10-09 00:09:19 -04:00
9635eea8c1 walking away 2024-10-08 13:15:24 -07:00
5a2df642b1 thrashing 2024-10-08 12:00:34 -07:00
621e41080e delme 2024-10-07 11:36:33 -07:00
48 changed files with 1126 additions and 987 deletions

BIN
..env.development.local.swp Normal file

Binary file not shown.

View File

@ -1,3 +1,4 @@
src/wasm-lib/* src/wasm-lib/*
src/lib/engine-utils/engine.js
*.typegen.ts *.typegen.ts
packages/codemirror-lsp-client/dist/* packages/codemirror-lsp-client/dist/*

View File

@ -15,7 +15,6 @@ on:
env: env:
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }} CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }} BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -26,6 +25,7 @@ jobs:
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows) runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
outputs: outputs:
version: ${{ steps.export_version.outputs.version }} version: ${{ steps.export_version.outputs.version }}
notes: ${{ steps.export_version.outputs.notes }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -55,6 +55,8 @@ jobs:
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json # TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json
- name: Generate release notes - name: Generate release notes
env:
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
run: | run: |
echo "$NOTES" > release-notes.md echo "$NOTES" > release-notes.md
cat release-notes.md cat release-notes.md
@ -82,6 +84,9 @@ jobs:
path: | path: |
electron-builder.yml electron-builder.yml
- id: export_notes
run: echo "notes=`cat release-notes.md'`" >> "$GITHUB_OUTPUT"
- name: Prepare electron-builder.yml file for updater test - name: Prepare electron-builder.yml file for updater test
if: ${{ env.CUT_RELEASE_PR == 'true' }} if: ${{ env.CUT_RELEASE_PR == 'true' }}
run: | run: |
@ -257,6 +262,7 @@ jobs:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }} VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }} VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }} PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
NOTES: ${{ needs.prepare-files.outputs.notes }}
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }} BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }} WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }} URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}

4
.gitignore vendored
View File

@ -66,3 +66,7 @@ venv
# electron # electron
out/ out/
# engine wasm utils
src/lib/engine-utils/engine.wasm
src/lib/engine-utils/engine.js

File diff suppressed because it is too large Load Diff

View File

@ -16,8 +16,8 @@ A sketch is a collection of paths.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No | | `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No |
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No | | `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No | | `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No | | `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No | | `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |

View File

@ -25,8 +25,8 @@ A sketch is a collection of paths.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `sketch`| | No | | `type` |enum: `sketch`| | No |
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No | | `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No |
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No | | `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No | | `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No | | `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No | | `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |

View File

@ -55,53 +55,6 @@ test.describe('Onboarding tests', () => {
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
}) })
test(
'Desktop: fresh onboarding executes and loads',
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
appSettings: {
app: {
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
})
const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 }
await page.setViewportSize(viewportSize)
// Locators and constants
const newProjectButton = page.getByRole('button', { name: 'New project' })
const projectLink = page.getByTestId('project-link')
await test.step(`Create a project and open to the onboarding`, async () => {
await newProjectButton.click()
await projectLink.click()
await test.step(`Ensure the engine connection works by testing the sketch button`, async () => {
await u.waitForPageLoad()
})
})
await test.step(`Ensure we see the onboarding stuff`, async () => {
// Test that the onboarding pane loaded
await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
// *and* that the code is shown in the editor
await expect(page.locator('.cm-content')).toContainText(
'// Shelf Bracket'
)
})
await electronApp.close()
}
)
test('Code resets after confirmation', async ({ page }) => { test('Code resets after confirmation', async ({ page }) => {
const initialCode = `sketch001 = startSketchOn('XZ')` const initialCode = `sketch001 = startSketchOn('XZ')`

View File

@ -888,17 +888,7 @@ export async function setupElectron({
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME) const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
const settingsOverrides = TOML.stringify( const settingsOverrides = TOML.stringify(
appSettings appSettings
? { ? { settings: appSettings }
settings: {
...TEST_SETTINGS,
...appSettings,
app: {
...TEST_SETTINGS.app,
projectDirectory: projectDirName,
...appSettings.app,
},
},
}
: { : {
settings: { settings: {
...TEST_SETTINGS, ...TEST_SETTINGS,

View File

@ -292,7 +292,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
await test.step(`Verify the camera moved`, async () => { await test.step(`Verify the camera moved`, async () => {
await scene.expectState({ await scene.expectState({
camera: { camera: {
position: [0, -23865.37, 11073.53], position: [0, -23865.37, 11073.54],
target: [0, 0, 0], target: [0, 0, 0],
}, },
}) })

View File

@ -430,6 +430,7 @@ test.describe('Testing settings', () => {
await test.step('Check color of logo changed when in modeling view', async () => { await test.step('Check color of logo changed when in modeling view', async () => {
await page.getByRole('button', { name: 'New project' }).click() await page.getByRole('button', { name: 'New project' }).click()
await page.getByTestId('project-link').first().click() await page.getByTestId('project-link').first().click()
await page.getByRole('button', { name: 'Dismiss' }).click()
await changeColor('58') await changeColor('58')
await expect(logoLink).toHaveCSS('--primary-hue', '58') await expect(logoLink).toHaveCSS('--primary-hue', '58')
}) })

View File

@ -107,13 +107,6 @@
}, },
"type": "array" "type": "array"
}, },
"loaded_filament_idx": {
"description": "The currently loaded filament index.",
"format": "uint",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"nozzle_diameter": { "nozzle_diameter": {
"description": "Diameter of the extrusion nozzle, in mm.", "description": "Diameter of the extrusion nozzle, in mm.",
"format": "double", "format": "double",
@ -292,21 +285,6 @@
"type" "type"
], ],
"type": "object" "type": "object"
},
{
"description": "Unknown material",
"properties": {
"type": {
"enum": [
"unknown"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
} }
] ]
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "zoo-modeling-app", "name": "zoo-modeling-app",
"version": "0.26.1", "version": "0.26.0",
"private": true, "private": true,
"productName": "Zoo Modeling App", "productName": "Zoo Modeling App",
"author": { "author": {
@ -161,7 +161,7 @@
"@types/isomorphic-fetch": "^0.0.39", "@types/isomorphic-fetch": "^0.0.39",
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
"@types/mocha": "^10.0.6", "@types/mocha": "^10.0.6",
"@types/node": "^22.7.8", "@types/node": "^22.5.0",
"@types/pixelmatch": "^5.2.6", "@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/react": "^18.3.4", "@types/react": "^18.3.4",

View File

@ -92,7 +92,6 @@ export class CameraControls {
target: Vector3 target: Vector3
domElement: HTMLCanvasElement domElement: HTMLCanvasElement
isDragging: boolean isDragging: boolean
wasDragging: boolean
mouseDownPosition: Vector2 mouseDownPosition: Vector2
mouseNewPosition: Vector2 mouseNewPosition: Vector2
rotationSpeed = 0.3 rotationSpeed = 0.3
@ -234,7 +233,6 @@ export class CameraControls {
this.target = new Vector3() this.target = new Vector3()
this.domElement = domElement this.domElement = domElement
this.isDragging = false this.isDragging = false
this.wasDragging = false
this.mouseDownPosition = new Vector2() this.mouseDownPosition = new Vector2()
this.mouseNewPosition = new Vector2() this.mouseNewPosition = new Vector2()
@ -365,8 +363,6 @@ export class CameraControls {
onMouseDown = (event: PointerEvent) => { onMouseDown = (event: PointerEvent) => {
this.domElement.setPointerCapture(event.pointerId) this.domElement.setPointerCapture(event.pointerId)
this.isDragging = true this.isDragging = true
// Reset the wasDragging flag to false when starting a new drag
this.wasDragging = false
this.mouseDownPosition.set(event.clientX, event.clientY) this.mouseDownPosition.set(event.clientX, event.clientY)
let interaction = this.getInteractionType(event) let interaction = this.getInteractionType(event)
if (interaction === 'none') return if (interaction === 'none') return
@ -396,10 +392,6 @@ export class CameraControls {
const interaction = this.getInteractionType(event) const interaction = this.getInteractionType(event)
if (interaction === 'none') return if (interaction === 'none') return
// If there's a valid interaction and the mouse is moving,
// our past (and current) interaction was a drag.
this.wasDragging = true
if (this.syncDirection === 'engineToClient') { if (this.syncDirection === 'engineToClient') {
this.moveSender.send(() => { this.moveSender.send(() => {
this.doMove(interaction, [event.clientX, event.clientY]) this.doMove(interaction, [event.clientX, event.clientY])
@ -407,7 +399,6 @@ export class CameraControls {
return return
} }
// else "clientToEngine" (Sketch Mode) or forceUpdate
// Implement camera movement logic here based on deltaMove // Implement camera movement logic here based on deltaMove
// For example, for rotating the camera around the target: // For example, for rotating the camera around the target:
if (interaction === 'rotate') { if (interaction === 'rotate') {
@ -436,9 +427,6 @@ export class CameraControls {
* under the cursor. This recently moved from being handled in App.tsx. * under the cursor. This recently moved from being handled in App.tsx.
* This might not be the right spot, but it is more consolidated. * This might not be the right spot, but it is more consolidated.
*/ */
// Clear any previous drag state
this.wasDragging = false
if (this.syncDirection === 'engineToClient') { if (this.syncDirection === 'engineToClient') {
const newCmdId = uuidv4() const newCmdId = uuidv4()

View File

@ -338,11 +338,6 @@ export class SceneEntities {
sceneInfra.setCallbacks({ sceneInfra.setCallbacks({
onClick: async (args) => { onClick: async (args) => {
if (!args) return if (!args) return
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args.mouseEvent.which !== 1) return if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args const { intersectionPoint } = args
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) return if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) return
@ -412,7 +407,7 @@ export class SceneEntities {
if (err(sketch)) return Promise.reject(sketch) if (err(sketch)) return Promise.reject(sketch)
if (!sketch) return Promise.reject('sketch not found') if (!sketch) return Promise.reject('sketch not found')
if (!isArray(sketch?.paths)) if (!isArray(sketch?.value))
return { return {
truncatedAst, truncatedAst,
programMemoryOverride, programMemoryOverride,
@ -440,7 +435,7 @@ export class SceneEntities {
maybeModdedAst, maybeModdedAst,
sketch.start.__geoMeta.sourceRange sketch.start.__geoMeta.sourceRange
) )
if (sketch?.paths?.[0]?.type !== 'Circle') { if (sketch?.value?.[0]?.type !== 'Circle') {
const _profileStart = createProfileStartHandle({ const _profileStart = createProfileStartHandle({
from: sketch.start.from, from: sketch.start.from,
id: sketch.start.__geoMeta.id, id: sketch.start.__geoMeta.id,
@ -456,16 +451,16 @@ export class SceneEntities {
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
} }
const callbacks: (() => SegmentOverlayPayload | null)[] = [] const callbacks: (() => SegmentOverlayPayload | null)[] = []
sketch.paths.forEach((segment, index) => { sketch.value.forEach((segment, index) => {
let segPathToNode = getNodePathFromSourceRange( let segPathToNode = getNodePathFromSourceRange(
maybeModdedAst, maybeModdedAst,
segment.__geoMeta.sourceRange segment.__geoMeta.sourceRange
) )
if ( if (
draftExpressionsIndices && draftExpressionsIndices &&
(sketch.paths[index - 1] || sketch.start) (sketch.value[index - 1] || sketch.start)
) { ) {
const previousSegment = sketch.paths[index - 1] || sketch.start const previousSegment = sketch.value[index - 1] || sketch.start
const previousSegmentPathToNode = getNodePathFromSourceRange( const previousSegmentPathToNode = getNodePathFromSourceRange(
maybeModdedAst, maybeModdedAst,
previousSegment.__geoMeta.sourceRange previousSegment.__geoMeta.sourceRange
@ -516,7 +511,7 @@ export class SceneEntities {
to: segment.to, to: segment.to,
} }
const result = initSegment({ const result = initSegment({
prevSegment: sketch.paths[index - 1], prevSegment: sketch.value[index - 1],
callExpName, callExpName,
input, input,
id: segment.__geoMeta.id, id: segment.__geoMeta.id,
@ -615,9 +610,9 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sg)) return Promise.reject(sg) if (err(sg)) return Promise.reject(sg)
const lastSeg = sg?.paths?.slice(-1)[0] || sg.start const lastSeg = sg?.value?.slice(-1)[0] || sg.start
const index = sg.paths.length // because we've added a new segment that's not in the memory yet, no need for `-1` const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
const mod = addNewSketchLn({ const mod = addNewSketchLn({
node: _ast, node: _ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
@ -650,13 +645,7 @@ export class SceneEntities {
sceneInfra.setCallbacks({ sceneInfra.setCallbacks({
onClick: async (args) => { onClick: async (args) => {
if (!args) return if (!args) return
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args.mouseEvent.which !== 1) return if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args const { intersectionPoint } = args
let intersection2d = intersectionPoint?.twoD let intersection2d = intersectionPoint?.twoD
const profileStart = args.intersects const profileStart = args.intersects
@ -665,7 +654,7 @@ export class SceneEntities {
let modifiedAst let modifiedAst
if (profileStart) { if (profileStart) {
const lastSegment = sketch.paths.slice(-1)[0] const lastSegment = sketch.value.slice(-1)[0]
modifiedAst = addCallExpressionsToPipe({ modifiedAst = addCallExpressionsToPipe({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
@ -697,7 +686,7 @@ export class SceneEntities {
}) })
if (trap(modifiedAst)) return Promise.reject(modifiedAst) if (trap(modifiedAst)) return Promise.reject(modifiedAst)
} else if (intersection2d) { } else if (intersection2d) {
const lastSegment = sketch.paths.slice(-1)[0] const lastSegment = sketch.value.slice(-1)[0]
const tmp = addNewSketchLn({ const tmp = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
@ -828,7 +817,7 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return Promise.reject(sketch) if (err(sketch)) return Promise.reject(sketch)
const sgPaths = sketch.paths const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch) this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
@ -837,11 +826,6 @@ export class SceneEntities {
) )
}, },
onClick: async (args) => { onClick: async (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
// Commit the rectangle to the full AST/code and return to sketch.idle // Commit the rectangle to the full AST/code and return to sketch.idle
const cornerPoint = args.intersectionPoint?.twoD const cornerPoint = args.intersectionPoint?.twoD
if (!cornerPoint || args.mouseEvent.button !== 0) return if (!cornerPoint || args.mouseEvent.button !== 0) return
@ -884,7 +868,7 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return if (err(sketch)) return
const sgPaths = sketch.paths const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
// Update the starting segment of the THREEjs scene // Update the starting segment of the THREEjs scene
@ -1001,7 +985,7 @@ export class SceneEntities {
variableDeclarationName variableDeclarationName
) )
if (err(sketch)) return if (err(sketch)) return
const sgPaths = sketch.paths const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch) this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
@ -1010,11 +994,6 @@ export class SceneEntities {
) )
}, },
onClick: async (args) => { onClick: async (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
// Commit the rectangle to the full AST/code and return to sketch.idle // Commit the rectangle to the full AST/code and return to sketch.idle
const cornerPoint = args.intersectionPoint?.twoD const cornerPoint = args.intersectionPoint?.twoD
if (!cornerPoint || args.mouseEvent.button !== 0) return if (!cornerPoint || args.mouseEvent.button !== 0) return
@ -1126,7 +1105,7 @@ export class SceneEntities {
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
if (addingNewSegmentStatus === 'nothing') { if (addingNewSegmentStatus === 'nothing') {
const prevSegment = sketch.paths[pipeIndex - 2] const prevSegment = sketch.value[pipeIndex - 2]
const mod = addNewSketchLn({ const mod = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
@ -1178,11 +1157,6 @@ export class SceneEntities {
}, },
onMove: () => {}, onMove: () => {},
onClick: (args) => { onClick: (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args?.mouseEvent.which !== 1) return if (args?.mouseEvent.which !== 1) return
if (!args || !args.selected) { if (!args || !args.selected) {
sceneInfra.modelingSend({ sceneInfra.modelingSend({
@ -1371,7 +1345,7 @@ export class SceneEntities {
} }
if (!sketch) return if (!sketch) return
const sgPaths = sketch.paths const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment( this.updateSegment(
@ -1419,7 +1393,7 @@ export class SceneEntities {
modifiedAst, modifiedAst,
segment.__geoMeta.sourceRange segment.__geoMeta.sourceRange
) )
const sgPaths = sketch.paths const sgPaths = sketch.value
const originalPathToNodeStr = JSON.stringify(segPathToNode) const originalPathToNodeStr = JSON.stringify(segPathToNode)
segPathToNode[1][0] = varDecIndex segPathToNode[1][0] = varDecIndex
const pathToNodeStr = JSON.stringify(segPathToNode) const pathToNodeStr = JSON.stringify(segPathToNode)
@ -1727,7 +1701,7 @@ function prepareTruncatedMemoryAndAst(
variableDeclarationName variableDeclarationName
) )
if (err(sg)) return sg if (err(sg)) return sg
const lastSeg = sg?.paths.slice(-1)[0] const lastSeg = sg?.value.slice(-1)[0]
if (draftSegment) { if (draftSegment) {
// truncatedAst needs to setup with another segment at the end // truncatedAst needs to setup with another segment at the end
let newSegment let newSegment

View File

@ -58,7 +58,7 @@ import { err } from 'lib/trap'
interface CreateSegmentArgs { interface CreateSegmentArgs {
input: SegmentInputs input: SegmentInputs
prevSegment: Sketch['paths'][number] prevSegment: Sketch['value'][number]
id: string id: string
pathToNode: PathToNode pathToNode: PathToNode
isDraftSegment?: boolean isDraftSegment?: boolean
@ -72,7 +72,7 @@ interface CreateSegmentArgs {
interface UpdateSegmentArgs { interface UpdateSegmentArgs {
input: SegmentInputs input: SegmentInputs
prevSegment: Sketch['paths'][number] prevSegment: Sketch['value'][number]
group: Group group: Group
sceneInfra: SceneInfra sceneInfra: SceneInfra
scale?: number scale?: number

View File

@ -644,7 +644,6 @@ export const ModelingMachineProvider = ({
input.plane input.plane
) )
await kclManager.updateAst(modifiedAst, false) await kclManager.updateAst(modifiedAst, false)
sceneInfra.camControls.enableRotate = false
sceneInfra.camControls.syncDirection = 'clientToEngine' sceneInfra.camControls.syncDirection = 'clientToEngine'
await letEngineAnimateAndSyncCamAfter( await letEngineAnimateAndSyncCamAfter(

View File

@ -95,7 +95,7 @@ export const processMemory = (programMemory: ProgramMemory) => {
return rest return rest
}) })
} else if (!err(sg)) { } else if (!err(sg)) {
processedMemory[key] = sg.paths.map(({ __geoMeta, ...rest }: Path) => { processedMemory[key] = sg.value.map(({ __geoMeta, ...rest }: Path) => {
return rest return rest
}) })
} else if ((val.type as any) === 'Function') { } else if ((val.type as any) === 'Function') {

View File

@ -255,14 +255,10 @@ export const Stream = () => {
}, [mediaStream]) }, [mediaStream])
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => { const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
// If we've got no stream or connection, don't do anything
if (!isNetworkOkay) return if (!isNetworkOkay) return
if (!videoRef.current) return if (!videoRef.current) return
// If we're in sketch mode, don't send a engine-side select event
if (state.matches('Sketch')) return if (state.matches('Sketch')) return
if (state.matches({ idle: 'showPlanes' })) return if (state.matches({ idle: 'showPlanes' })) return
// If we're mousing up from a camera drag, don't send a select event
if (sceneInfra.camControls.wasDragging === true) return
if (btnName(e.nativeEvent).left) { if (btnName(e.nativeEvent).left) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises

View File

@ -32,7 +32,7 @@ const mySketch001 = startSketchOn('XY')
sourceRange: [46, 71], sourceRange: [46, 71],
}, },
}, },
paths: [ value: [
{ {
type: 'ToPoint', type: 'ToPoint',
tag: null, tag: null,
@ -96,7 +96,7 @@ const mySketch001 = startSketchOn('XY')
on: expect.any(Object), on: expect.any(Object),
start: expect.any(Object), start: expect.any(Object),
type: 'Sketch', type: 'Sketch',
paths: [ value: [
{ {
type: 'ToPoint', type: 'ToPoint',
from: [0, 0], from: [0, 0],
@ -202,7 +202,7 @@ const sk2 = startSketchOn('XY')
info: expect.any(Object), info: expect.any(Object),
}, },
}, },
paths: [ value: [
{ {
type: 'ToPoint', type: 'ToPoint',
from: [0, 0], from: [0, 0],
@ -294,7 +294,7 @@ const sk2 = startSketchOn('XY')
info: expect.any(Object), info: expect.any(Object),
}, },
}, },
paths: [ value: [
{ {
type: 'ToPoint', type: 'ToPoint',
from: [0, 0], from: [0, 0],

View File

@ -58,7 +58,7 @@ const newVar = myVar + 1`
` `
const mem = await exe(code) const mem = await exe(code)
// geo is three js buffer geometry and is very bloated to have in tests // geo is three js buffer geometry and is very bloated to have in tests
const minusGeo = mem.get('mySketch')?.value?.paths const minusGeo = mem.get('mySketch')?.value?.value
expect(minusGeo).toEqual([ expect(minusGeo).toEqual([
{ {
type: 'ToPoint', type: 'ToPoint',
@ -175,7 +175,7 @@ const newVar = myVar + 1`
info: expect.any(Object), info: expect.any(Object),
}, },
}, },
paths: [ value: [
{ {
type: 'ToPoint', type: 'ToPoint',
to: [1, 1], to: [1, 1],
@ -367,7 +367,7 @@ describe('testing math operators', () => {
const mem = await exe(code) const mem = await exe(code)
const sketch = sketchFromKclValue(mem.get('part001'), 'part001') const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
// result of `-legLen(5, min(3, 999))` should be -4 // result of `-legLen(5, min(3, 999))` should be -4
const yVal = (sketch as Sketch).paths?.[0]?.to?.[1] const yVal = (sketch as Sketch).value?.[0]?.to?.[1]
expect(yVal).toBe(-4) expect(yVal).toBe(-4)
}) })
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => { it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
@ -385,8 +385,8 @@ describe('testing math operators', () => {
const mem = await exe(code) const mem = await exe(code)
const sketch = sketchFromKclValue(mem.get('part001'), 'part001') const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
// expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0 // expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0
expect((sketch as Sketch).paths?.[1]?.from).toEqual([3, 4]) expect((sketch as Sketch).value?.[1]?.from).toEqual([3, 4])
expect((sketch as Sketch).paths?.[1]?.to).toEqual([6, 0]) expect((sketch as Sketch).value?.[1]?.to).toEqual([6, 0])
const removedUnaryExp = code.replace( const removedUnaryExp = code.replace(
`-legLen(segLen(seg01), myVar)`, `-legLen(segLen(seg01), myVar)`,
`legLen(segLen(seg01), myVar)` `legLen(segLen(seg01), myVar)`
@ -398,7 +398,7 @@ describe('testing math operators', () => {
) )
// without the minus sign, the y value should be 8 // without the minus sign, the y value should be 8
expect((removedUnaryExpMemSketch as Sketch).paths?.[1]?.to).toEqual([6, 8]) expect((removedUnaryExpMemSketch as Sketch).value?.[1]?.to).toEqual([6, 8])
}) })
it('with nested callExpression and binaryExpression', async () => { it('with nested callExpression and binaryExpression', async () => {
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))' const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'

View File

@ -717,7 +717,7 @@ export function isLinesParallelAndConstrained(
constraintType === 'angle' || constraintLevel === 'full' constraintType === 'angle' || constraintLevel === 'full'
// get the previous segment // get the previous segment
const prevSegment = sg.paths[secondaryIndex - 1] const prevSegment = sg.value[secondaryIndex - 1]
const prevSourceRange = prevSegment.__geoMeta.sourceRange const prevSourceRange = prevSegment.__geoMeta.sourceRange
const isParallelAndConstrained = const isParallelAndConstrained =

View File

@ -64,7 +64,7 @@ const ARC_SEGMENT_ERR = new Error('Invalid input, expected "arc-segment"')
export type Coords2d = [number, number] export type Coords2d = [number, number]
export function getCoordsFromPaths(skGroup: Sketch, index = 0): Coords2d { export function getCoordsFromPaths(skGroup: Sketch, index = 0): Coords2d {
const currentPath = skGroup?.paths?.[index] const currentPath = skGroup?.value?.[index]
if (!currentPath && skGroup?.start) { if (!currentPath && skGroup?.start) {
return skGroup.start.to return skGroup.start.to
} else if (!currentPath) { } else if (!currentPath) {
@ -1704,7 +1704,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
varName varName
) )
if (err(sketch)) return sketch if (err(sketch)) return sketch
const intersectPath = sketch.paths.find( const intersectPath = sketch.value.find(
({ tag }: Path) => tag && tag.value === intersectTagName ({ tag }: Path) => tag && tag.value === intersectTagName
) )
let offset = 0 let offset = 0

View File

@ -18,7 +18,7 @@ export function getSketchSegmentFromPathToNode(
pathToNode: PathToNode pathToNode: PathToNode
): ):
| { | {
segment: Sketch['paths'][number] segment: Sketch['value'][number]
index: number index: number
} }
| Error { | Error {
@ -39,15 +39,15 @@ export function getSketchSegmentFromSourceRange(
[rangeStart, rangeEnd]: SourceRange [rangeStart, rangeEnd]: SourceRange
): ):
| { | {
segment: Sketch['paths'][number] segment: Sketch['value'][number]
index: number index: number
} }
| Error { | Error {
const lineIndex = sketch.paths.findIndex( const lineIndex = sketch.value.findIndex(
({ __geoMeta: { sourceRange } }: Path) => ({ __geoMeta: { sourceRange } }: Path) =>
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
) )
const line = sketch.paths[lineIndex] const line = sketch.value[lineIndex]
if (line) { if (line) {
return { return {
segment: line, segment: line,

View File

@ -1732,7 +1732,7 @@ export function transformAstSketchLines({
if (err(_segment)) return _segment if (err(_segment)) return _segment
referencedSegment = _segment.segment referencedSegment = _segment.segment
} else { } else {
referencedSegment = sketch.paths.find( referencedSegment = sketch.value.find(
(path) => path.tag?.value === _referencedSegmentName (path) => path.tag?.value === _referencedSegmentName
) )
} }

View File

@ -110,6 +110,7 @@ const initialise = async () => {
const fullUrl = wasmUrl() const fullUrl = wasmUrl()
const input = await fetch(fullUrl) const input = await fetch(fullUrl)
const buffer = await input.arrayBuffer() const buffer = await input.arrayBuffer()
return await init(buffer) return await init(buffer)
} catch (e) { } catch (e) {
console.log('Error initialising WASM', e) console.log('Error initialising WASM', e)

31
src/lib/engineUtils.ts Normal file
View File

@ -0,0 +1,31 @@
import EngineUtils from '@engine-utils'
type KCEngineUtilsEvaluatePath = {
(sketch: string, t: number): string
}
let kcEngineUtilsEvaluatePath: KCEngineUtilsEvaluatePath
export async function init() {
return await new Promise((resolve, reject) => {
try {
EngineUtils().then((module) => {
kcEngineUtilsEvaluatePath = module.cwrap(
'kcEngineUtilsEvaluatePath',
'string',
['string', 'number']
)
resolve(true)
})
} catch (e) {
reject(e)
}
})
}
export async function getTruePathEndPos(sketch: string) {
if (!kcEngineUtilsEvaluatePath) {
await init()
}
return kcEngineUtilsEvaluatePath(sketch, 1.0)
}

View File

@ -138,11 +138,6 @@ export interface components {
FdmHardwareConfiguration: { FdmHardwareConfiguration: {
/** @description The filaments the printer has access to. */ /** @description The filaments the printer has access to. */
filaments: components['schemas']['Filament'][] filaments: components['schemas']['Filament'][]
/**
* Format: uint
* @description The currently loaded filament index.
*/
loaded_filament_idx?: number | null
/** /**
* Format: double * Format: double
* @description Diameter of the extrusion nozzle, in mm. * @description Diameter of the extrusion nozzle, in mm.
@ -196,10 +191,6 @@ export interface components {
/** @enum {string} */ /** @enum {string} */
type: 'composite' type: 'composite'
} }
| {
/** @enum {string} */
type: 'unknown'
}
/** @description The hardware configuration of a machine. */ /** @description The hardware configuration of a machine. */
HardwareConfiguration: HardwareConfiguration:
| { | {

View File

@ -23,9 +23,6 @@ import { codeManager, editorManager, kclManager } from 'lib/singletons'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { toSync } from 'lib/utils' import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { EngineConnectionStateType } from 'lang/std/engineConnection'
export const kbdClasses = export const kbdClasses =
'py-0.5 px-1 text-sm rounded bg-chalkboard-10 dark:bg-chalkboard-100 border border-chalkboard-50 border-b-2' 'py-0.5 px-1 text-sm rounded bg-chalkboard-10 dark:bg-chalkboard-100 border border-chalkboard-50 border-b-2'
@ -83,20 +80,8 @@ export const onboardingRoutes = [
] ]
export function useDemoCode() { export function useDemoCode() {
const { overallState, immediateState } = useNetworkContext()
useEffect(() => { useEffect(() => {
// Don't run if the editor isn't loaded or the code is already the bracket if (!editorManager.editorView || codeManager.code === bracket) return
if (!editorManager.editorView || codeManager.code === bracket) {
return
}
// Don't run if the network isn't healthy or the connection isn't established
if (
overallState !== NetworkHealthState.Ok ||
immediateState.type !== EngineConnectionStateType.ConnectionEstablished
) {
return
}
setTimeout( setTimeout(
toSync(async () => { toSync(async () => {
codeManager.updateCodeStateEditor(bracket) codeManager.updateCodeStateEditor(bracket)
@ -104,7 +89,7 @@ export function useDemoCode() {
await codeManager.writeToFile() await codeManager.writeToFile()
}, reportRejection) }, reportRejection)
) )
}, [editorManager.editorView, immediateState, overallState]) }, [editorManager.editorView])
} }
export function useNextClick(newStatus: string) { export function useNextClick(newStatus: string) {

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

@ -176,7 +176,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -187,7 +187,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -204,7 +204,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -465,7 +465,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -656,7 +656,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -667,7 +667,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -722,7 +722,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
"synstructure", "synstructure",
] ]
@ -751,7 +751,7 @@ dependencies = [
"rustfmt-wrapper", "rustfmt-wrapper",
"serde", "serde",
"serde_tokenstream", "serde_tokenstream",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -762,7 +762,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -789,7 +789,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -827,7 +827,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -988,7 +988,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -1084,7 +1084,7 @@ dependencies = [
"inflections", "inflections",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -1612,7 +1612,7 @@ dependencies = [
"pretty_assertions", "pretty_assertions",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -1716,7 +1716,7 @@ dependencies = [
"kittycad-modeling-cmds-macros-impl", "kittycad-modeling-cmds-macros-impl",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -1727,7 +1727,7 @@ checksum = "6607507a8a0e4273b943179f0a3ef8e90712308d1d3095246040c29cfdbf985b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -2112,7 +2112,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"structmeta", "structmeta",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -2126,7 +2126,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"structmeta", "structmeta",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -2166,7 +2166,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -2224,7 +2224,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -2337,9 +2337,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.89" version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -2391,7 +2391,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-macros-backend", "pyo3-macros-backend",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -2404,7 +2404,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-build-config", "pyo3-build-config",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -2925,7 +2925,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -2965,9 +2965,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.213" version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -2983,13 +2983,13 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.213" version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3000,7 +3000,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3024,7 +3024,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3045,7 +3045,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3182,7 +3182,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"structmeta-derive", "structmeta-derive",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3193,7 +3193,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3215,7 +3215,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3237,9 +3237,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.85" version = "2.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3263,7 +3263,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3326,22 +3326,22 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.65" version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.65" version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3437,7 +3437,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3579,7 +3579,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3607,7 +3607,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3689,7 +3689,7 @@ checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
"termcolor", "termcolor",
] ]
@ -3865,7 +3865,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]
@ -3927,7 +3927,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -3962,7 +3962,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -4328,7 +4328,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.85", "syn 2.0.79",
] ]
[[package]] [[package]]

View File

@ -83,6 +83,6 @@ name = "modify"
path = "tests/modify/main.rs" path = "tests/modify/main.rs"
# Example: how to point modeling-api at a different repo (e.g. a branch or a local clone) # Example: how to point modeling-api at a different repo (e.g. a branch or a local clone)
#[patch.crates-io] #[patch."https://github.com/KittyCAD/modeling-api"]
#kittycad-modeling-cmds = { path = "../../../modeling-api/modeling-cmds" } #kittycad-modeling-cmds = { path = "../../../modeling-api/modeling-cmds" }
#kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" } #kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }

View File

@ -18,12 +18,12 @@ once_cell = "1.20.2"
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
regex = "1.10" regex = "1.10"
serde = { version = "1.0.213", features = ["derive"] } serde = { version = "1.0.210", features = ["derive"] }
serde_tokenstream = "0.2" serde_tokenstream = "0.2"
syn = { version = "2.0.85", features = ["full"] } syn = { version = "2.0.79", features = ["full"] }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.91" anyhow = "1.0.89"
expectorate = "1.1.0" expectorate = "1.1.0"
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"
rustfmt-wrapper = "0.2.1" rustfmt-wrapper = "0.2.1"

View File

@ -15,7 +15,7 @@ databake = "0.1.8"
kcl-lib = { path = "../kcl" } kcl-lib = { path = "../kcl" }
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
syn = { version = "2.0.85", features = ["full"] } syn = { version = "2.0.79", features = ["full"] }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"

View File

@ -6,10 +6,10 @@ edition = "2021"
license = "MIT" license = "MIT"
[dependencies] [dependencies]
anyhow = "1.0.91" anyhow = "1.0.89"
hyper = { version = "0.14.29", features = ["http1", "server", "tcp"] } hyper = { version = "0.14.29", features = ["http1", "server", "tcp"] }
kcl-lib = { version = "0.2", path = "../kcl" } kcl-lib = { version = "0.2", path = "../kcl" }
pico-args = "0.5.0" pico-args = "0.5.0"
serde = { version = "1.0.213", features = ["derive"] } serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128" serde_json = "1.0.128"
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }

View File

@ -11,7 +11,7 @@ keywords = ["kcl", "KittyCAD", "CAD"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = { version = "1.0.91", features = ["backtrace"] } anyhow = { version = "1.0.89", features = ["backtrace"] }
async-recursion = "1.1.1" async-recursion = "1.1.1"
async-trait = "0.1.83" async-trait = "0.1.83"
base64 = "0.22.1" base64 = "0.22.1"
@ -38,11 +38,11 @@ pyo3 = { version = "0.22.5", optional = true }
reqwest = { version = "0.12", default-features = false, features = ["stream", "rustls-tls"] } reqwest = { version = "0.12", default-features = false, features = ["stream", "rustls-tls"] }
ropey = "1.6.1" ropey = "1.6.1"
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1", "preserve_order"] } schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1", "preserve_order"] }
serde = { version = "1.0.213", features = ["derive"] } serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128" serde_json = "1.0.128"
sha2 = "0.10.8" sha2 = "0.10.8"
tabled = { version = "0.15.0", optional = true } tabled = { version = "0.15.0", optional = true }
thiserror = "1.0.65" thiserror = "1.0.64"
toml = "0.8.19" toml = "0.8.19"
ts-rs = { version = "10.0.0", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] } ts-rs = { version = "10.0.0", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] }
url = { version = "2.5.2", features = ["serde"] } url = { version = "2.5.2", features = ["serde"] }
@ -68,7 +68,7 @@ tokio-tungstenite = { version = "0.24.0", features = ["rustls-tls-native-roots"]
tower-lsp = { version = "0.20.0", features = ["proposed"] } tower-lsp = { version = "0.20.0", features = ["proposed"] }
[features] [features]
default = ["engine"] default = ["engine"] # add wasm-engine-utils here when we're ready
cli = ["dep:clap"] cli = ["dep:clap"]
# For the lsp server, when run with stdout for rpc we want to disable println. # For the lsp server, when run with stdout for rpc we want to disable println.
# This is used for editor extensions that use the lsp server. # This is used for editor extensions that use the lsp server.
@ -77,6 +77,10 @@ engine = []
pyo3 = ["dep:pyo3"] pyo3 = ["dep:pyo3"]
# Helper functions also used in benchmarks. # Helper functions also used in benchmarks.
lsp-test-util = [] lsp-test-util = []
#if enabled, kcl will link directly against a wasm build of the engine utils lib to save latency
wasm-engine-utils = []
#if enabled, kcl will link directly against a native build of the engine utils lib to save latency (not yet functional)
native-engine-utils = []
tabled = ["dep:tabled"] tabled = ["dep:tabled"]

View File

@ -0,0 +1,57 @@
//! Functions for calling into the engine-utils library (a set of C++ utilities containing various logic for client-side CAD processing)
//! Note that this binary may not be available to all builds of kcl, so fallbacks that call the engine API should be implemented
use crate::{
errors::{KclError, KclErrorDetails},
std::Args,
};
use anyhow::Result;
use std::ffi::{CString, CStr};
use kittycad_modeling_cmds::{length_unit::LengthUnit, shared::Point3d};
mod cpp {
use std::os::raw::c_char;
extern "C" {
pub fn kcEngineUtilsEvaluatePath(sketch: *const c_char, t: f64) -> *const c_char;
}
}
pub fn is_available() -> bool {
true
}
pub async fn get_true_path_end_pos(sketch: String, args: &Args) -> Result<Point3d<LengthUnit>, KclError> {
let c_string = CString::new(sketch).map_err(|e| {
KclError::Internal(KclErrorDetails {
message: format!("{:?}", e),
source_ranges: vec![args.source_range],
})
})?;
let arg = c_string.into_raw();
let result_string: String;
unsafe {
let result = cpp::kcEngineUtilsEvaluatePath(arg, 1.0);
let result_cstr = CStr::from_ptr(result);
let str_slice: &str = result_cstr.to_str().map_err(|e| {
KclError::Internal(KclErrorDetails {
message: format!("{:?}", e),
source_ranges: vec![args.source_range],
})
})?;
let str_buf: String = str_slice.to_owned();
result_string = str_buf.clone();
let _ = CString::from_raw(arg);
}
let point: Point3d<f64> = serde_json::from_str(&result_string).map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to path position from json: {}", e),
source_ranges: vec![args.source_range],
})
})?;
Ok(Point3d::<f64>::from(point).map(LengthUnit))
}

View File

@ -0,0 +1,35 @@
//! Functions for calling into the engine-utils library (a set of C++ utilities containing various logic for client-side CAD processing)
//! Note that this binary may not be available to all builds of kcl, so fallbacks that call the engine API should be implemented
use crate::{
errors::{KclError, KclErrorDetails},
std::Args,
};
use crate::engine::kcmc::{each_cmd as mcmd, ModelingCmd};
use anyhow::Result;
use kittycad_modeling_cmds::{length_unit::LengthUnit, ok_response::OkModelingCmdResponse, shared::Point3d, websocket::OkWebSocketResponseData};
pub fn is_available() -> bool {
true
}
pub async fn get_true_path_end_pos(sketch: String, args: &Args) -> Result<Point3d<LengthUnit>, KclError> {
let id = uuid::Uuid::new_v4();
let resp = args.send_modeling_cmd(id, ModelingCmd::from(mcmd::EngineUtilEvaluatePath {
path_json: sketch,
t: 1.0,
})).await?;
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::EngineUtilEvaluatePath(point),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("mcmd::EngineUtilEvaluatePath response was not as expected: {:?}", resp),
source_ranges: vec![args.source_range],
}));
};
Ok(point.pos)
}

View File

@ -0,0 +1,56 @@
//! Functions for calling into the engine-utils library (a set of C++ utilities containing various logic for client-side CAD processing)
//! Note that this binary may not be available to all builds of kcl, so fallbacks that call the engine API should be implemented
use crate::{
errors::{KclError, KclErrorDetails},
std::Args,
};
use anyhow::Result;
use kittycad_modeling_cmds::{length_unit::LengthUnit, shared::Point3d};
mod cpp {
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen(module = "/../../lib/engineUtils.ts")]
extern "C" {
#[wasm_bindgen(js_name = getTruePathEndPos, catch)]
pub fn get_true_path_end_pos(sketch: String) -> Result<js_sys::Promise, js_sys::Error>;
}
}
pub fn is_available() -> bool {
true
}
async fn call_cpp<F>(args: &Args, f: F) -> Result<String, KclError>
where
F: FnOnce() -> Result<js_sys::Promise, js_sys::Error>,
{
let promise = f().map_err(|e| {
KclError::Internal(KclErrorDetails {
message: format!("{:?}", e),
source_ranges: vec![args.source_range],
})
})?;
let result = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
KclError::Internal(KclErrorDetails {
message: format!("{:?}", e),
source_ranges: vec![args.source_range],
})
})?;
Ok(result.as_string().unwrap_or_default())
}
pub async fn get_true_path_end_pos(sketch: String, args: &Args) -> Result<Point3d<LengthUnit>, KclError> {
let result_str = call_cpp(args, || cpp::get_true_path_end_pos(sketch.into())).await?;
let point: Point3d<f64> = serde_json::from_str(&result_str).map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to path position from json: {}", e),
source_ranges: vec![args.source_range],
})
})?;
Ok(Point3d::<f64>::from(point).map(LengthUnit))
}

View File

@ -8,6 +8,17 @@ pub mod conn_mock;
#[cfg(feature = "engine")] #[cfg(feature = "engine")]
pub mod conn_wasm; pub mod conn_wasm;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "native-engine-utils")]
pub mod engine_utils;
#[cfg(target_arch = "wasm32")]
#[cfg(feature = "wasm-engine-utils")]
pub mod engine_utils_wasm;
#[cfg(feature = "engine")]
#[cfg(any(not(target_arch = "wasm32"), all(not(feature = "native-engine-utils"), not(feature = "wasm-engine-utils"))))]
pub mod engine_utils_api;
use std::{ use std::{
collections::HashMap, collections::HashMap,
sync::{Arc, Mutex}, sync::{Arc, Mutex},

View File

@ -1137,10 +1137,10 @@ pub struct TagEngineInfo {
#[ts(export)] #[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")] #[serde(tag = "type", rename_all = "camelCase")]
pub struct Sketch { pub struct Sketch {
/// The id of the sketch (this will change when the engine's reference to it changes). /// The id of the sketch (this will change when the engine's reference to it changes.
pub id: uuid::Uuid, pub id: uuid::Uuid,
/// The paths in the sketch. /// The paths in the sketch.
pub paths: Vec<Path>, pub value: Vec<Path>,
/// What the sketch is on (can be a plane or a face). /// What the sketch is on (can be a plane or a face).
pub on: SketchSurface, pub on: SketchSurface,
/// The starting path. /// The starting path.
@ -1215,7 +1215,7 @@ impl Sketch {
/// Get the path most recently sketched. /// Get the path most recently sketched.
pub(crate) fn latest_path(&self) -> Option<&Path> { pub(crate) fn latest_path(&self) -> Option<&Path> {
self.paths.last() self.value.last()
} }
/// The "pen" is an imaginary pen drawing the path. /// The "pen" is an imaginary pen drawing the path.
@ -1601,6 +1601,19 @@ pub enum Path {
#[serde(flatten)] #[serde(flatten)]
base: BasePath, base: BasePath,
}, },
/// An arc (only used for engine-utils arg serialization for now)
Arc {
#[serde(flatten)]
base: BasePath,
/// angle range
#[ts(type = "[number, number]")]
angle_range: [f64; 2],
/// center
#[ts(type = "[number, number]")]
center: [f64; 2],
/// the arc's radius
radius: f64,
},
/// A arc that is tangential to the last path segment that goes to a point /// A arc that is tangential to the last path segment that goes to a point
TangentialArcTo { TangentialArcTo {
#[serde(flatten)] #[serde(flatten)]
@ -1620,6 +1633,10 @@ pub enum Path {
center: [f64; 2], center: [f64; 2],
/// arc's direction /// arc's direction
ccw: bool, ccw: bool,
/// the arc's radius
radius: f64,
/// the arc's angle offset
offset: f64,
}, },
// TODO: consolidate segment enums, remove Circle. https://github.com/KittyCAD/modeling-app/issues/3940 // TODO: consolidate segment enums, remove Circle. https://github.com/KittyCAD/modeling-app/issues/3940
/// a complete arc /// a complete arc
@ -1668,6 +1685,7 @@ impl Path {
Path::TangentialArcTo { base, .. } => base.geo_meta.id, Path::TangentialArcTo { base, .. } => base.geo_meta.id,
Path::TangentialArc { base, .. } => base.geo_meta.id, Path::TangentialArc { base, .. } => base.geo_meta.id,
Path::Circle { base, .. } => base.geo_meta.id, Path::Circle { base, .. } => base.geo_meta.id,
Path::Arc { base, .. } => base.geo_meta.id,
} }
} }
@ -1680,6 +1698,7 @@ impl Path {
Path::TangentialArcTo { base, .. } => base.tag.clone(), Path::TangentialArcTo { base, .. } => base.tag.clone(),
Path::TangentialArc { base, .. } => base.tag.clone(), Path::TangentialArc { base, .. } => base.tag.clone(),
Path::Circle { base, .. } => base.tag.clone(), Path::Circle { base, .. } => base.tag.clone(),
Path::Arc { base, .. } => base.tag.clone(),
} }
} }
@ -1692,6 +1711,7 @@ impl Path {
Path::TangentialArcTo { base, .. } => base, Path::TangentialArcTo { base, .. } => base,
Path::TangentialArc { base, .. } => base, Path::TangentialArc { base, .. } => base,
Path::Circle { base, .. } => base, Path::Circle { base, .. } => base,
Path::Arc { base, .. } => base,
} }
} }
@ -1704,6 +1724,7 @@ impl Path {
Path::TangentialArcTo { base, .. } => Some(base), Path::TangentialArcTo { base, .. } => Some(base),
Path::TangentialArc { base, .. } => Some(base), Path::TangentialArc { base, .. } => Some(base),
Path::Circle { base, .. } => Some(base), Path::Circle { base, .. } => Some(base),
Path::Arc { base, .. } => Some(base),
} }
} }
} }

View File

@ -141,14 +141,14 @@ pub(crate) async fn do_post_extrude(
) )
.await?; .await?;
if sketch.paths.is_empty() { if sketch.value.is_empty() {
return Err(KclError::Type(KclErrorDetails { return Err(KclError::Type(KclErrorDetails {
message: "Expected a non-empty sketch".to_string(), message: "Expected a non-empty sketch".to_string(),
source_ranges: vec![args.source_range], source_ranges: vec![args.source_range],
})); }));
} }
let edge_id = sketch.paths.iter().find_map(|segment| match segment { let edge_id = sketch.value.iter().find_map(|segment| match segment {
Path::ToPoint { base } | Path::Circle { base, .. } => Some(base.geo_meta.id), Path::ToPoint { base } | Path::Circle { base, .. } => Some(base.geo_meta.id),
_ => None, _ => None,
}); });
@ -229,7 +229,7 @@ pub(crate) async fn do_post_extrude(
} = analyze_faces(exec_state, &args, face_infos); } = analyze_faces(exec_state, &args, face_infos);
// Iterate over the sketch.value array and add face_id to GeoMeta // Iterate over the sketch.value array and add face_id to GeoMeta
let new_value = sketch let new_value = sketch
.paths .value
.iter() .iter()
.flat_map(|path| { .flat_map(|path| {
if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) { if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
@ -256,6 +256,7 @@ pub(crate) async fn do_post_extrude(
}); });
Some(extrude_surface) Some(extrude_surface)
} }
Path::Arc { .. } => todo!(),
} }
} else if args.ctx.is_mock() { } else if args.ctx.is_mock() {
// Only pre-populate the extrude surface if we are in mock mode. // Only pre-populate the extrude surface if we are in mock mode.

View File

@ -109,7 +109,7 @@ pub async fn last_segment_x(_exec_state: &mut ExecState, args: Args) -> Result<K
}] }]
fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<f64, KclError> { fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<f64, KclError> {
let last_line = sketch let last_line = sketch
.paths .value
.last() .last()
.ok_or_else(|| { .ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
@ -149,7 +149,7 @@ pub async fn last_segment_y(_exec_state: &mut ExecState, args: Args) -> Result<K
}] }]
fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<f64, KclError> { fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<f64, KclError> {
let last_line = sketch let last_line = sketch
.paths .value
.last() .last()
.ok_or_else(|| { .ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
@ -289,7 +289,7 @@ fn inner_angle_to_match_length_x(
let length = ((path.from[1] - path.to[1]).powi(2) + (path.from[0] - path.to[0]).powi(2)).sqrt(); let length = ((path.from[1] - path.to[1]).powi(2) + (path.from[0] - path.to[0]).powi(2)).sqrt();
let last_line = sketch let last_line = sketch
.paths .value
.last() .last()
.ok_or_else(|| { .ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
@ -353,7 +353,7 @@ fn inner_angle_to_match_length_y(
let length = ((path.from[1] - path.to[1]).powi(2) + (path.from[0] - path.to[0]).powi(2)).sqrt(); let length = ((path.from[1] - path.to[1]).powi(2) + (path.from[0] - path.to[0]).powi(2)).sqrt();
let last_line = sketch let last_line = sketch
.paths .value
.last() .last()
.ok_or_else(|| { .ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {

View File

@ -134,7 +134,7 @@ async fn inner_circle(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.paths.push(current_path); new_sketch.value.push(current_path);
args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: new_sketch.id })) args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: new_sketch.id }))
.await?; .await?;

View File

@ -6,7 +6,7 @@ use anyhow::Result;
use derive_docs::stdlib; use derive_docs::stdlib;
use kcmc::shared::Point2d as KPoint2d; // Point2d is already defined in this pkg, to impl ts_rs traits. use kcmc::shared::Point2d as KPoint2d; // Point2d is already defined in this pkg, to impl ts_rs traits.
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, ModelingCmd}; use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, ModelingCmd};
use kittycad_modeling_cmds as kcmc; use kittycad_modeling_cmds::{self as kcmc, units};
use kittycad_modeling_cmds::shared::PathSegment; use kittycad_modeling_cmds::shared::PathSegment;
use parse_display::{Display, FromStr}; use parse_display::{Display, FromStr};
use schemars::JsonSchema; use schemars::JsonSchema;
@ -21,13 +21,26 @@ use crate::{
}, },
std::{ std::{
utils::{ utils::{
arc_angles, arc_center_and_end, get_tangent_point_from_previous_arc, get_tangential_arc_to_info, arc_angles, arc_center_and_end, arc_start_center_and_end, get_tangent_point_from_previous_arc,
get_x_component, get_y_component, intersection_with_parallel_line, TangentialArcInfoInput, get_tangential_arc_to_info, get_x_component, get_y_component, intersection_with_parallel_line,
TangentialArcInfoInput,
}, },
Args, Args,
}, },
}; };
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "native-engine-utils")]
use crate::engine::engine_utils;
#[cfg(target_arch = "wasm32")]
#[cfg(feature = "wasm-engine-utils")]
use crate::engine::engine_utils_wasm as engine_utils;
#[cfg(feature = "engine")]
#[cfg(any(not(target_arch = "wasm32"), all(not(feature = "native-engine-utils"), not(feature = "wasm-engine-utils"))))]
use crate::engine::engine_utils_api as engine_utils;
/// A tag for a face. /// A tag for a face.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
@ -154,7 +167,7 @@ async fn inner_line_to(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.paths.push(current_path); new_sketch.value.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -323,7 +336,7 @@ async fn inner_line(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.paths.push(current_path); new_sketch.value.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -506,7 +519,7 @@ async fn inner_angled_line(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.paths.push(current_path); new_sketch.value.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1237,7 +1250,7 @@ pub(crate) async fn inner_start_profile_at(
id: path_id, id: path_id,
original_id: path_id, original_id: path_id,
on: sketch_surface.clone(), on: sketch_surface.clone(),
paths: vec![], value: vec![],
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
tags: if let Some(tag) = &tag { tags: if let Some(tag) = &tag {
let mut tag_identifier: TagIdentifier = tag.into(); let mut tag_identifier: TagIdentifier = tag.into();
@ -1411,7 +1424,7 @@ pub(crate) async fn inner_close(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.paths.push(current_path); new_sketch.value.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1495,7 +1508,45 @@ pub(crate) async fn inner_arc(
} => { } => {
let a_start = Angle::from_degrees(*angle_start); let a_start = Angle::from_degrees(*angle_start);
let a_end = Angle::from_degrees(*angle_end); let a_end = Angle::from_degrees(*angle_end);
let (center, end) = arc_center_and_end(from, a_start, a_end, *radius);
let (_, center, mut end) = arc_start_center_and_end(from, a_start, a_end, *radius);
if engine_utils::is_available() {
let mut path_plus_arc = sketch.clone();
let to = [0.0, 0.0];
let arc = Path::Arc {
base: BasePath {
from: from.into(),
to,
tag: None,
geo_meta: GeoMeta {
id: uuid::Uuid::new_v4(),
metadata: args.source_range.into(),
},
},
angle_range: [*angle_start, *angle_end],
center: [center.x, center.y],
radius: *radius,
};
path_plus_arc.value.push(arc);
let sketch_json_value = serde_json::to_string(&path_plus_arc).map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to convert sketch to json: {}", e),
source_ranges: vec![args.source_range],
})
})?;
//???? someone double check me on this unit conversion - mike
let units = units::UnitLength::Millimeters;
let result_end = engine_utils::get_true_path_end_pos(sketch_json_value, &args).await?;
end = Point2d {
x: result_end.x.to_millimeters(units),
y: result_end.y.to_millimeters(units),
};
}
(center, a_start, a_end, *radius, end) (center, a_start, a_end, *radius, end)
} }
ArcData::CenterToRadius { center, to, radius } => { ArcData::CenterToRadius { center, to, radius } => {
@ -1545,7 +1596,7 @@ pub(crate) async fn inner_arc(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.paths.push(current_path); new_sketch.value.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1616,7 +1667,7 @@ async fn inner_tangential_arc(
let id = exec_state.id_generator.next_uuid(); let id = exec_state.id_generator.next_uuid();
let (center, to, ccw) = match data { let (center, to, ccw, radius, offset) = match data {
TangentialArcData::RadiusAndOffset { radius, offset } => { TangentialArcData::RadiusAndOffset { radius, offset } => {
// KCL stdlib types use degrees. // KCL stdlib types use degrees.
let offset = Angle::from_degrees(offset); let offset = Angle::from_degrees(offset);
@ -1654,13 +1705,15 @@ async fn inner_tangential_arc(
}), }),
) )
.await?; .await?;
(center, to.into(), ccw) (center, to.into(), ccw, radius, offset)
} }
}; };
let current_path = Path::TangentialArc { let current_path = Path::TangentialArc {
ccw, ccw,
center: center.into(), center: center.into(),
radius,
offset: offset.to_degrees(),
base: BasePath { base: BasePath {
from: from.into(), from: from.into(),
to, to,
@ -1677,7 +1730,7 @@ async fn inner_tangential_arc(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.paths.push(current_path); new_sketch.value.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1773,7 +1826,7 @@ async fn inner_tangential_arc_to(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.paths.push(current_path); new_sketch.value.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1858,7 +1911,7 @@ async fn inner_tangential_arc_to_relative(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.paths.push(current_path); new_sketch.value.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }
@ -1951,7 +2004,7 @@ async fn inner_bezier_curve(
new_sketch.add_tag(tag, &current_path); new_sketch.add_tag(tag, &current_path);
} }
new_sketch.paths.push(current_path); new_sketch.value.push(current_path);
Ok(new_sketch) Ok(new_sketch)
} }

View File

@ -221,6 +221,33 @@ pub fn arc_center_and_end(from: Point2d, start_angle: Angle, end_angle: Angle, r
(center, end) (center, end)
} }
pub fn arc_start_center_and_end(
from: Point2d,
start_angle: Angle,
end_angle: Angle,
radius: f64,
) -> (Point2d, Point2d, Point2d) {
let start_angle = start_angle.to_radians();
let end_angle = end_angle.to_radians();
let center = Point2d {
x: -1.0 * (radius * start_angle.cos() - from.x),
y: -1.0 * (radius * start_angle.sin() - from.y),
};
let start = Point2d {
x: center.x + radius * start_angle.cos(),
y: center.y + radius * start_angle.sin(),
};
let end = Point2d {
x: center.x + radius * end_angle.cos(),
y: center.y + radius * end_angle.sin(),
};
(start, center, end)
}
pub fn arc_angles( pub fn arc_angles(
from: Point2d, from: Point2d,
to: Point2d, to: Point2d,

View File

@ -1,5 +1,5 @@
fn square = (pos, scale) => { fn cube = (pos, scale) => {
sg = startSketchOn('XY') const sg = startSketchOn('XY')
|> startProfileAt(pos, %) |> startProfileAt(pos, %)
|> line([0, scale], %) |> line([0, scale], %)
|> line([scale, 0], %) |> line([scale, 0], %)
@ -9,9 +9,9 @@ fn square = (pos, scale) => {
return sg return sg
} }
sq = square([0,0], 10) const b1 = cube([0,0], 10)
cb = square([3,3], 4) const b2 = cube([3,3], 4)
|> extrude(10, %) |> extrude(10, %)
pt1 = sq.paths[0] const pt1 = b1.value[0]
pt2 = cb.value[0] const pt2 = b2.value[0]

View File

@ -58,6 +58,7 @@ const config = defineConfig({
resolve: { resolve: {
alias: { alias: {
'@kittycad/codemirror-lsp-client': '/packages/codemirror-lsp-client/src', '@kittycad/codemirror-lsp-client': '/packages/codemirror-lsp-client/src',
'@engine-utils': '/src/lib/engine-utils/engine.js',
}, },
}, },
plugins: [react(), viteTsconfigPaths(), eslint(), version(), lezer()], plugins: [react(), viteTsconfigPaths(), eslint(), version(), lezer()],

View File

@ -2597,10 +2597,10 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433"
integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==
"@types/node@*", "@types/node@^22.7.8": "@types/node@*", "@types/node@^22.5.0":
version "22.7.8" version "22.5.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.8.tgz#a1dbf0dc5f71bdd2642fc89caef65d58747ce825" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.0.tgz#10f01fe9465166b4cab72e75f60d8b99d019f958"
integrity sha512-a922jJy31vqR5sk+kAdIENJjHblqcZ4RmERviFsER4WJcEONqxKcjNOlk0q7OUfrF5sddT+vng070cdfMlrPLg== integrity sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==
dependencies: dependencies:
undici-types "~6.19.2" undici-types "~6.19.2"
@ -8785,16 +8785,7 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
"string-width-cjs@npm:string-width@^4.2.0": "string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -8888,14 +8879,7 @@ string_decoder@~1.1.1:
dependencies: dependencies:
safe-buffer "~5.1.0" safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1": "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -9769,16 +9753,7 @@ word-wrap@^1.2.3, word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==