Compare commits

...

35 Commits

Author SHA1 Message Date
2f36b6b7cb set selection as top level event only 2024-04-02 16:55:40 +11:00
a37ccec39e trigger ci 2024-04-02 16:32:08 +11:00
6287c943dd A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) 2024-04-02 04:50:31 +00:00
6ef052cb7f convert scaling to be based on pixels 2024-04-02 15:42:15 +11:00
7f10ea7371 Merge branch 'main' into kurt-segments-between-2 2024-04-02 08:39:33 +11:00
00dcd3ac78 more test fix 2024-04-02 08:39:06 +11:00
76480f1a43 Only send one SetSceneUnits call (#1981)
We only intended to send this once, at the start of execution. But the execute function is recursive, so it was sending many SetSceneUnits calls.

Fixes https://github.com/KittyCAD/modeling-app/issues/1971
2024-04-01 21:37:05 +00:00
1baa3819db A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) 2024-04-01 21:30:25 +00:00
448db9d48c Merge branch 'main' into kurt-segments-between-2 2024-04-02 08:20:38 +11:00
f850f80de1 Update 20-20 snapshot outputs (#1982)
Engine added vantage-independent lighting, tests must be rerun.
2024-04-01 21:19:53 +00:00
23b484d365 try and fix sketch on face in CI 2024-04-02 08:19:32 +11:00
672cd652a8 Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"
This reverts commit b8ceea179c.
2024-04-02 07:37:04 +11:00
cad4e18530 Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"
This reverts commit 5dc1adacae.
2024-04-02 07:36:44 +11:00
484717a354 Add to tangential arcs 2024-04-02 07:26:20 +11:00
15ebbe6947 Pipelines cause Z-fighting (#1976)
Bug: You can see here that the two programs under tests/ are equivalent, just one uses 
pipelines and one always assigns to a new sketchgroup. However, the pipeline
produces weird visual bugs. Jess did a git bisect to figure out this was the problem that
Mike was experiencing, around weird visual artifacts with filleting.

Ultimately the bug was that my rewritten `execute_pipe_body` function was executing
the first expression of the pipeline body twice! In most unit tests this didn't matter,
because the first expression in a pipeline was startSketchAt. No big deal to run that
twice. However, in Mike's program, the first expression was `make_circle` or `pentagon`,
user-defined functions that sent a lot of API calls. This meant the pipeline duplicated a lot
of geometry, causing Z-fighting and weird artifacts.
2024-04-01 19:48:57 +00:00
c989340bcf Make sure this works on setup and update of segments 2024-04-02 06:34:20 +11:00
5dc1adacae A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) 2024-04-01 19:21:23 +00:00
f2ea91b1ba side small segment handles on resize 2024-04-02 06:12:14 +11:00
01beba42da Allow two 'serial_test_' to run simultaneously (#1978)
Running two modeling sessions simultaneously will:

 - Speed up unit testing
 - Stress test our API/engine better
2024-04-01 17:36:27 +00:00
b8ceea179c A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) 2024-04-01 10:41:15 +00:00
149130d264 caluclate segment length in screen-space/in-pixels 2024-04-01 21:32:59 +11:00
b9e544d410 fix sketch on face test 2024-03-30 11:45:22 +11:00
a4e39ce2e9 fix deleted line 2024-03-30 11:30:48 +11:00
18cf6113d5 Merge branch 'main' into kurt-segments-between-2 2024-03-30 09:49:24 +11:00
5dea7fd042 fix regression 2024-03-30 09:49:03 +11:00
509e372ed2 Add plumbus test (#1975)
* add plumbus test

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

* plumbus image

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

* twenty twenty

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-03-29 15:06:11 -07:00
b0417114af New test case: Paul's riddle, shortened (#1970)
This is discussed in https://github.com/KittyCAD/modeling-app/issues/1969
2024-03-29 20:56:32 +00:00
809ea86bfa use partical texture for plus button 2024-03-30 07:01:03 +11:00
0360a4021b Onboarding updates (#1967)
* Make onboarding line references dynamic and error if they aren't found
Fixes https://github.com/KittyCAD/modeling-app/issues/1918

* More clear and correct Sketch onboarding step
Fixes https://github.com/KittyCAD/modeling-app/issues/1790
Fixes https://github.com/KittyCAD/modeling-app/issues/1789

* Make sample code line references dynamic and error on app start if they break so we can know before release
Fixes https://github.com/KittyCAD/modeling-app/issues/1918

* Better error message for searchText failure

* JB onboarding feedback

* Make list more explicit, instruct to hold down key first
2024-03-29 12:56:32 -04:00
6f36371e6d Cut release v0.17.1 (#1958) 2024-03-28 20:54:06 -04:00
ebcc19e757 Bump clap from 4.5.3 to 4.5.4 in /src/wasm-lib (#1946)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.3 to 4.5.4.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.3...v4.5.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-28 22:42:04 +00:00
84cbcddff1 Bump kcl, use newer execution-plan crates (#1957)
* Bump KCL lib

* Use all execution-plan crates from crates.io
2024-03-28 22:29:42 +00:00
c23b046c5e only show extra handle on hover 2024-03-28 21:04:45 +11:00
456d4912ca setup dots properly 2024-03-28 20:32:11 +11:00
522352ec75 get branch up to where it was before 2024-03-28 20:09:02 +11:00
120 changed files with 803 additions and 169 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 165 KiB

View File

@ -1470,9 +1470,13 @@ test('Sketch on face', async ({ page, context }) => {
await page.getByText('startProfileAt([1.03, 1.03], %)').click() await page.getByText('startProfileAt([1.03, 1.03], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible() await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.setViewportSize({ width: 1200, height: 1200 })
await u.openAndClearDebugPanel()
await u.updateCamPosition([452, -152, 1166])
await u.closeDebugPanel()
await page.waitForTimeout(200) await page.waitForTimeout(200)
const pointToDragFirst = [691, 237] const pointToDragFirst = [787, 565]
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1]) await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
await page.mouse.down() await page.mouse.down()
await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], { await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
@ -1486,7 +1490,9 @@ test('Sketch on face', async ({ page, context }) => {
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01') .toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([1.03, 1.03], %) |> startProfileAt([1.03, 1.03], %)
|> line([2.81, -0.33], %) |> line([${process?.env?.CI ? 2.74 : 2.93}, -${
process?.env?.CI ? 0.24 : 0.2
}], %)
|> line([-4.44, -2.13], %) |> line([-4.44, -2.13], %)
|> close(%)`) |> close(%)`)
@ -1509,7 +1515,9 @@ test('Sketch on face', async ({ page, context }) => {
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01') .toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([1.03, 1.03], %) |> startProfileAt([1.03, 1.03], %)
|> line([2.81, -0.33], %) |> line([${process?.env?.CI ? 2.74 : 2.93}, -${
process?.env?.CI ? 0.24 : 0.2
}], %)
|> line([-4.44, -2.13], %) |> line([-4.44, -2.13], %)
|> close(%) |> close(%)
|> extrude(5 + 7, %)`) |> extrude(5 + 7, %)`)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -1,6 +1,6 @@
{ {
"name": "untitled-app", "name": "untitled-app",
"version": "0.17.0", "version": "0.17.1",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.15.0", "@codemirror/autocomplete": "^6.15.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

View File

@ -7,7 +7,7 @@
}, },
"package": { "package": {
"productName": "zoo-modeling-app", "productName": "zoo-modeling-app",
"version": "0.17.0" "version": "0.17.1"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@ -28,12 +28,15 @@ export function createGridHelper({
gridHelper.rotation.x = Math.PI / 2 gridHelper.rotation.x = Math.PI / 2
return gridHelper return gridHelper
} }
const fudgeFactor = 72.66985970437086
export const orthoScale = (cam: OrthographicCamera | PerspectiveCamera) => export const orthoScale = (cam: OrthographicCamera | PerspectiveCamera) =>
0.55 / cam.zoom (0.55 * fudgeFactor) / cam.zoom / window.innerHeight
export const perspScale = (cam: PerspectiveCamera, group: Group | Mesh) => export const perspScale = (cam: PerspectiveCamera, group: Group | Mesh) =>
(group.position.distanceTo(cam.position) * cam.fov) / 4000 (group.position.distanceTo(cam.position) * cam.fov * fudgeFactor) /
4000 /
window.innerHeight
export function isQuaternionVertical(q: Quaternion) { export function isQuaternionVertical(q: Quaternion) {
const v = new Vector3(0, 0, 1).applyQuaternion(q) const v = new Vector3(0, 0, 1).applyQuaternion(q)

View File

@ -12,6 +12,7 @@ import {
OrthographicCamera, OrthographicCamera,
PerspectiveCamera, PerspectiveCamera,
PlaneGeometry, PlaneGeometry,
Points,
Quaternion, Quaternion,
Scene, Scene,
Shape, Shape,
@ -87,14 +88,17 @@ import { EngineCommandManager } from 'lang/std/engineConnection'
type DraftSegment = 'line' | 'tangentialArcTo' type DraftSegment = 'line' | 'tangentialArcTo'
export const EXTRA_SEGMENT_HANDLE = 'extraSegmentHandle'
export const EXTRA_SEGMENT_OFFSET_PX = 8
export const PROFILE_START = 'profile-start'
export const STRAIGHT_SEGMENT = 'straight-segment' export const STRAIGHT_SEGMENT = 'straight-segment'
export const STRAIGHT_SEGMENT_BODY = 'straight-segment-body' export const STRAIGHT_SEGMENT_BODY = 'straight-segment-body'
export const STRAIGHT_SEGMENT_DASH = 'straight-segment-body-dashed' export const STRAIGHT_SEGMENT_DASH = 'straight-segment-body-dashed'
export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
export const TANGENTIAL_ARC_TO__SEGMENT_DASH = export const TANGENTIAL_ARC_TO__SEGMENT_DASH =
'tangential-arc-to-segment-body-dashed' 'tangential-arc-to-segment-body-dashed'
export const PROFILE_START = 'profile-start' export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
export const MIN_SEGMENT_LENGTH = 60 // in pixels
// This singleton Class is responsible for all of the things the user sees and interacts with. // This singleton Class is responsible for all of the things the user sees and interacts with.
// That mostly mean sketch elements. // That mostly mean sketch elements.
@ -111,8 +115,12 @@ export class SceneEntities {
this.engineCommandManager = engineCommandManager this.engineCommandManager = engineCommandManager
this.scene = sceneInfra?.scene this.scene = sceneInfra?.scene
sceneInfra?.camControls.subscribeToCamChange(this.onCamChange) sceneInfra?.camControls.subscribeToCamChange(this.onCamChange)
window.addEventListener('resize', this.onWindowResize)
} }
onWindowResize = () => {
this.onCamChange()
}
onCamChange = () => { onCamChange = () => {
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -282,7 +290,6 @@ export class SceneEntities {
sketchGroup: SketchGroup sketchGroup: SketchGroup
variableDeclarationName: string variableDeclarationName: string
}> { }> {
sceneInfra.resetMouseListeners()
this.createIntersectionPlane() this.createIntersectionPlane()
const { truncatedAst, programMemoryOverride, variableDeclarationName } = const { truncatedAst, programMemoryOverride, variableDeclarationName } =
@ -295,7 +302,7 @@ export class SceneEntities {
}) })
const sketchGroup = sketchGroupFromPathToNode({ const sketchGroup = sketchGroupFromPathToNode({
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
ast: kclManager.ast, ast: maybeModdedAst,
programMemory, programMemory,
}) })
if (!Array.isArray(sketchGroup?.value)) if (!Array.isArray(sketchGroup?.value))
@ -383,6 +390,7 @@ export class SceneEntities {
pathToNode: segPathToNode, pathToNode: segPathToNode,
isDraftSegment, isDraftSegment,
scale: factor, scale: factor,
texture: sceneInfra.extraSegmentTexture,
}) })
} else { } else {
seg = straightSegment({ seg = straightSegment({
@ -393,6 +401,7 @@ export class SceneEntities {
isDraftSegment, isDraftSegment,
scale: factor, scale: factor,
callExpName, callExpName,
texture: sceneInfra.extraSegmentTexture,
}) })
} }
seg.layers.set(SKETCH_LAYER) seg.layers.set(SKETCH_LAYER)
@ -435,6 +444,7 @@ export class SceneEntities {
) => { ) => {
await kclManager.updateAst(modifiedAst, false) await kclManager.updateAst(modifiedAst, false)
await this.tearDownSketch({ removeAxis: false }) await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners()
await this.setupSketch({ await this.setupSketch({
sketchPathToNode, sketchPathToNode,
forward, forward,
@ -442,7 +452,12 @@ export class SceneEntities {
position: origin, position: origin,
maybeModdedAst: kclManager.ast, maybeModdedAst: kclManager.ast,
}) })
this.setupSketchIdleCallbacks(sketchPathToNode) this.setupSketchIdleCallbacks({
forward,
up,
position: origin,
pathToNode: sketchPathToNode,
})
} }
setUpDraftSegment = async ( setUpDraftSegment = async (
sketchPathToNode: PathToNode, sketchPathToNode: PathToNode,
@ -467,19 +482,20 @@ export class SceneEntities {
const index = sg.value.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`
let modifiedAst = addNewSketchLn({ const mod = addNewSketchLn({
node: kclManager.ast, node: _ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
to: [lastSeg.to[0], lastSeg.to[1]], to: [lastSeg.to[0], lastSeg.to[1]],
from: [lastSeg.to[0], lastSeg.to[1]], from: [lastSeg.to[0], lastSeg.to[1]],
fnName: segmentName, fnName: segmentName,
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
}).modifiedAst })
modifiedAst = parse(recast(modifiedAst)) const modifiedAst = parse(recast(mod.modifiedAst))
const draftExpressionsIndices = { start: index, end: index } const draftExpressionsIndices = { start: index, end: index }
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false }) if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners()
const { truncatedAst, programMemoryOverride, sketchGroup } = const { truncatedAst, programMemoryOverride, sketchGroup } =
await this.setupSketch({ await this.setupSketch({
sketchPathToNode, sketchPathToNode,
@ -549,10 +565,101 @@ export class SceneEntities {
...mouseEnterLeaveCallbacks(), ...mouseEnterLeaveCallbacks(),
}) })
} }
setupSketchIdleCallbacks = (pathToNode: PathToNode) => { setupSketchIdleCallbacks = ({
pathToNode,
up,
forward,
position,
}: {
pathToNode: PathToNode
forward: [number, number, number]
up: [number, number, number]
position?: [number, number, number]
}) => {
let addingNewSegmentStatus: 'nothing' | 'pending' | 'added' = 'nothing'
sceneInfra.setCallbacks({ sceneInfra.setCallbacks({
onDrag: ({ selected, intersectionPoint, mouseEvent, intersects }) => { onDragEnd: async () => {
if (addingNewSegmentStatus !== 'nothing') {
await this.tearDownSketch({ removeAxis: false })
this.setupSketch({
sketchPathToNode: pathToNode,
maybeModdedAst: kclManager.ast,
up,
forward,
position,
})
// setting up the callbacks again resets value in closures
this.setupSketchIdleCallbacks({
pathToNode,
up,
forward,
position,
})
}
},
onDrag: async ({
selected,
intersectionPoint,
mouseEvent,
intersects,
}) => {
if (mouseEvent.which !== 1) return if (mouseEvent.which !== 1) return
const group = getParentGroup(selected, [EXTRA_SEGMENT_HANDLE])
if (group?.name === EXTRA_SEGMENT_HANDLE) {
const segGroup = getParentGroup(selected)
const pathToNode: PathToNode = segGroup?.userData?.pathToNode
const pathToNodeIndex = pathToNode.findIndex(
(x) => x[1] === 'PipeExpression'
)
const sketchGroup = sketchGroupFromPathToNode({
pathToNode,
ast: kclManager.ast,
programMemory: kclManager.programMemory,
})
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
if (addingNewSegmentStatus === 'nothing') {
const prevSegment = sketchGroup.value[pipeIndex - 2]
const mod = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
from: [prevSegment.from[0], prevSegment.from[1]],
// TODO assuming it's always a straight segments being added
// as this is easiest, and we'll need to add "tabbing" behavior
// to support other segment types
fnName: 'line',
pathToNode: pathToNode,
spliceBetween: true,
})
addingNewSegmentStatus = 'pending'
await kclManager.executeAstMock(mod.modifiedAst, {
updates: 'code',
})
await this.tearDownSketch({ removeAxis: false })
this.setupSketch({
sketchPathToNode: pathToNode,
maybeModdedAst: kclManager.ast,
up,
forward,
position,
})
addingNewSegmentStatus = 'added'
} else if (addingNewSegmentStatus === 'added') {
const pathToNodeForNewSegment = pathToNode.slice(0, pathToNodeIndex)
pathToNodeForNewSegment.push([pipeIndex - 2, 'index'])
this.onDragSegment({
sketchPathToNode: pathToNodeForNewSegment,
object: selected,
intersection2d: intersectionPoint.twoD,
intersects,
})
}
return
}
this.onDragSegment({ this.onDragSegment({
object: selected, object: selected,
intersection2d: intersectionPoint.twoD, intersection2d: intersectionPoint.twoD,
@ -755,8 +862,7 @@ export class SceneEntities {
group.userData.to = to group.userData.to = to
group.userData.prevSegment = prevSegment group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
arrowGroup.position.set(to[0], to[1], 0)
const previousPoint = const previousPoint =
prevSegment?.type === 'TangentialArcTo' prevSegment?.type === 'TangentialArcTo'
@ -774,13 +880,40 @@ export class SceneEntities {
obtuse: true, obtuse: true,
}) })
const arrowheadAngle = const pxLength = arcInfo.arcLength / scale
arcInfo.endAngle + (Math.PI / 2) * (arcInfo.ccw ? 1 : -1) const shouldHide = pxLength < MIN_SEGMENT_LENGTH
arrowGroup.quaternion.setFromUnitVectors(
new Vector3(0, 1, 0), if (arrowGroup) {
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0) arrowGroup.position.set(to[0], to[1], 0)
)
arrowGroup.scale.set(scale, scale, scale) const arrowheadAngle =
arcInfo.endAngle + (Math.PI / 2) * (arcInfo.ccw ? 1 : -1)
arrowGroup.quaternion.setFromUnitVectors(
new Vector3(0, 1, 0),
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
)
arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = !shouldHide
}
if (extraSegmentGroup) {
const circumferenceInPx = (2 * Math.PI * arcInfo.radius) / scale
const extraSegmentAngleDelta =
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
const extraSegmentAngle =
arcInfo.startAngle + (arcInfo.ccw ? 1 : -1) * extraSegmentAngleDelta
const extraSegmentOffset = new Vector2(
Math.cos(extraSegmentAngle) * arcInfo.radius,
Math.sin(extraSegmentAngle) * arcInfo.radius
)
extraSegmentGroup.position.set(
arcInfo.center[0] + extraSegmentOffset.x,
arcInfo.center[1] + extraSegmentOffset.y,
0
)
extraSegmentGroup.scale.set(scale, scale, scale)
extraSegmentGroup.visible = !shouldHide
}
const tangentialArcToSegmentBody = group.children.find( const tangentialArcToSegmentBody = group.children.find(
(child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY (child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY
@ -827,10 +960,17 @@ export class SceneEntities {
group.userData.from = from group.userData.from = from
group.userData.to = to group.userData.to = to
const shape = new Shape() const shape = new Shape()
shape.moveTo(0, -0.08 * scale) shape.moveTo(0, -1.2 * scale) // The width of the line in px (2.4px in this case)
shape.lineTo(0, 0.08 * scale) // The width of the line shape.lineTo(0, 1.2 * scale)
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const length = Math.sqrt(
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
)
const pxLength = length / scale
const shouldHide = pxLength < MIN_SEGMENT_LENGTH
if (arrowGroup) { if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0) arrowGroup.position.set(to[0], to[1], 0)
@ -842,6 +982,21 @@ export class SceneEntities {
.normalize() .normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir) arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
arrowGroup.scale.set(scale, scale, scale) arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = !shouldHide
}
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (extraSegmentGroup) {
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
.normalize()
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
extraSegmentGroup.position.set(
from[0] + offsetFromBase.x,
from[1] + offsetFromBase.y,
0
)
extraSegmentGroup.scale.set(scale, scale, scale)
extraSegmentGroup.visible = !shouldHide
} }
const straightSegmentBody = group.children.find( const straightSegmentBody = group.children.find(
@ -1160,7 +1315,7 @@ function colorSegment(object: any, color: number) {
]) ])
if (straightSegmentBody) { if (straightSegmentBody) {
straightSegmentBody.traverse((child) => { straightSegmentBody.traverse((child) => {
if (child instanceof Mesh) { if (child instanceof Mesh && !child.userData.ignoreColorChange) {
child.material.color.set(color) child.material.color.set(color)
} }
}) })
@ -1264,7 +1419,7 @@ function massageFormats(a: any): Vector3 {
function mouseEnterLeaveCallbacks() { function mouseEnterLeaveCallbacks() {
return { return {
onMouseEnter: ({ selected }: OnMouseEnterLeaveArgs) => { onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) { if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial const mat = obj.material as MeshBasicMaterial
@ -1286,6 +1441,14 @@ function mouseEnterLeaveCallbacks() {
sceneInfra.highlightCallback([node.start, node.end]) sceneInfra.highlightCallback([node.start, node.end])
const yellow = 0xffff00 const yellow = 0xffff00
colorSegment(selected, yellow) colorSegment(selected, yellow)
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (extraSegmentGroup) {
extraSegmentGroup.traverse((child) => {
if (child instanceof Points || child instanceof Mesh) {
child.material.opacity = dragSelected ? 0 : 1
}
})
}
return return
} }
sceneInfra.highlightCallback([0, 0]) sceneInfra.highlightCallback([0, 0])
@ -1302,6 +1465,14 @@ function mouseEnterLeaveCallbacks() {
selected, selected,
isSelected ? 0x0000ff : parent?.userData?.baseColor || 0xffffff isSelected ? 0x0000ff : parent?.userData?.baseColor || 0xffffff
) )
const extraSegmentGroup = parent?.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (extraSegmentGroup) {
extraSegmentGroup.traverse((child) => {
if (child instanceof Points || child instanceof Mesh) {
child.material.opacity = 0
}
})
}
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) { if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial const mat = obj.material as MeshBasicMaterial

View File

@ -18,6 +18,8 @@ import {
Intersection, Intersection,
Object3D, Object3D,
Object3DEventMap, Object3DEventMap,
TextureLoader,
Texture,
} from 'three' } from 'three'
import { compareVec2Epsilon2 } from 'lang/std/sketch' import { compareVec2Epsilon2 } from 'lang/std/sketch'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
@ -54,6 +56,7 @@ export const ARROWHEAD = 'arrowhead'
export interface OnMouseEnterLeaveArgs { export interface OnMouseEnterLeaveArgs {
selected: Object3D<Object3DEventMap> selected: Object3D<Object3DEventMap>
dragSelected?: Object3D<Object3DEventMap>
mouseEvent: MouseEvent mouseEvent: MouseEvent
} }
@ -98,18 +101,25 @@ export class SceneInfra {
isFovAnimationInProgress = false isFovAnimationInProgress = false
_baseUnit: BaseUnit = 'mm' _baseUnit: BaseUnit = 'mm'
_baseUnitMultiplier = 1 _baseUnitMultiplier = 1
extraSegmentTexture: Texture
onDragStartCallback: (arg: OnDragCallbackArgs) => void = () => {}
onDragEndCallback: (arg: OnDragCallbackArgs) => void = () => {}
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {} onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {} onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
onClickCallback: (arg: OnClickCallbackArgs) => void = () => {} onClickCallback: (arg: OnClickCallbackArgs) => void = () => {}
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {} onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {} onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
setCallbacks = (callbacks: { setCallbacks = (callbacks: {
onDragStart?: (arg: OnDragCallbackArgs) => void
onDragEnd?: (arg: OnDragCallbackArgs) => void
onDrag?: (arg: OnDragCallbackArgs) => void onDrag?: (arg: OnDragCallbackArgs) => void
onMove?: (arg: OnMoveCallbackArgs) => void onMove?: (arg: OnMoveCallbackArgs) => void
onClick?: (arg: OnClickCallbackArgs) => void onClick?: (arg: OnClickCallbackArgs) => void
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
}) => { }) => {
this.onDragStartCallback = callbacks.onDragStart || this.onDragStartCallback
this.onDragEndCallback = callbacks.onDragEnd || this.onDragEndCallback
this.onDragCallback = callbacks.onDrag || this.onDragCallback this.onDragCallback = callbacks.onDrag || this.onDragCallback
this.onMoveCallback = callbacks.onMove || this.onMoveCallback this.onMoveCallback = callbacks.onMove || this.onMoveCallback
this.onClickCallback = callbacks.onClick || this.onClickCallback this.onClickCallback = callbacks.onClick || this.onClickCallback
@ -128,6 +138,8 @@ export class SceneInfra {
} }
resetMouseListeners = () => { resetMouseListeners = () => {
this.setCallbacks({ this.setCallbacks({
onDragStart: () => {},
onDragEnd: () => {},
onDrag: () => {}, onDrag: () => {},
onMove: () => {}, onMove: () => {},
onClick: () => {}, onClick: () => {},
@ -212,6 +224,13 @@ export class SceneInfra {
const light = new AmbientLight(0x505050) // soft white light const light = new AmbientLight(0x505050) // soft white light
this.scene.add(light) this.scene.add(light)
const textureLoader = new TextureLoader()
this.extraSegmentTexture = textureLoader.load(
'/clientSideSceneAssets/extra-segment-texture.png'
)
this.extraSegmentTexture.anisotropy =
this.renderer?.capabilities?.getMaxAnisotropy?.()
SceneInfra.instance = this SceneInfra.instance = this
} }
@ -360,6 +379,7 @@ export class SceneInfra {
this.hoveredObject = firstIntersectObject this.hoveredObject = firstIntersectObject
this.onMouseEnter({ this.onMouseEnter({
selected: this.hoveredObject, selected: this.hoveredObject,
dragSelected: this.selected?.object,
mouseEvent: mouseEvent, mouseEvent: mouseEvent,
}) })
} }
@ -367,6 +387,7 @@ export class SceneInfra {
if (this.hoveredObject) { if (this.hoveredObject) {
this.onMouseLeave({ this.onMouseLeave({
selected: this.hoveredObject, selected: this.hoveredObject,
dragSelected: this.selected?.object,
mouseEvent: mouseEvent, mouseEvent: mouseEvent,
}) })
this.hoveredObject = null this.hoveredObject = null
@ -455,8 +476,16 @@ export class SceneInfra {
if (this.selected) { if (this.selected) {
if (this.selected.hasBeenDragged) { if (this.selected.hasBeenDragged) {
// this is where we could fire a onDragEnd event // TODO do the types properly here
// console.log('onDragEnd', this.selected) this.onDragEndCallback({
intersectionPoint: {
twoD: planeIntersectPoint?.twoD as any,
threeD: planeIntersectPoint?.threeD as any,
},
intersects,
mouseEvent,
selected: this.selected as any,
})
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) { } else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
// fire onClick event as there was no drags // fire onClick event as there was no drags
this.onClickCallback({ this.onClickCallback({

View File

@ -12,14 +12,20 @@ import {
Mesh, Mesh,
MeshBasicMaterial, MeshBasicMaterial,
NormalBufferAttributes, NormalBufferAttributes,
Points,
PointsMaterial,
Shape, Shape,
SphereGeometry, SphereGeometry,
Texture,
Vector2, Vector2,
Vector3, Vector3,
} from 'three' } from 'three'
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js' import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm' import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
import { import {
EXTRA_SEGMENT_HANDLE,
EXTRA_SEGMENT_OFFSET_PX,
MIN_SEGMENT_LENGTH,
PROFILE_START, PROFILE_START,
STRAIGHT_SEGMENT, STRAIGHT_SEGMENT,
STRAIGHT_SEGMENT_BODY, STRAIGHT_SEGMENT_BODY,
@ -44,7 +50,7 @@ export function profileStart({
}) { }) {
const group = new Group() const group = new Group()
const geometry = new BoxGeometry(0.8, 0.8, 0.8) const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
const body = new MeshBasicMaterial({ color: 0xffffff }) const body = new MeshBasicMaterial({ color: 0xffffff })
const mesh = new Mesh(geometry, body) const mesh = new Mesh(geometry, body)
@ -71,6 +77,7 @@ export function straightSegment({
isDraftSegment, isDraftSegment,
scale = 1, scale = 1,
callExpName, callExpName,
texture,
}: { }: {
from: Coords2d from: Coords2d
to: Coords2d to: Coords2d
@ -79,12 +86,13 @@ export function straightSegment({
isDraftSegment?: boolean isDraftSegment?: boolean
scale?: number scale?: number
callExpName: string callExpName: string
texture: Texture
}): Group { }): Group {
const group = new Group() const group = new Group()
const shape = new Shape() const shape = new Shape()
shape.moveTo(0, -0.08 * scale) shape.moveTo(0, -1.2 * scale)
shape.lineTo(0, 0.08 * scale) // The width of the line shape.lineTo(0, 1.2 * scale)
let geometry let geometry
if (isDraftSegment) { if (isDraftSegment) {
@ -122,24 +130,44 @@ export function straightSegment({
} }
group.name = STRAIGHT_SEGMENT group.name = STRAIGHT_SEGMENT
const length = Math.sqrt(
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
)
const arrowGroup = createArrowhead(scale) const arrowGroup = createArrowhead(scale)
arrowGroup.position.set(to[0], to[1], 0) arrowGroup.position.set(to[0], to[1], 0)
const dir = new Vector3() const dir = new Vector3()
.subVectors(new Vector3(to[0], to[1], 0), new Vector3(from[0], from[1], 0)) .subVectors(new Vector3(to[0], to[1], 0), new Vector3(from[0], from[1], 0))
.normalize() .normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir) arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
const pxLength = length / scale
const shouldHide = pxLength < MIN_SEGMENT_LENGTH
arrowGroup.visible = !shouldHide
group.add(mesh) group.add(mesh)
if (callExpName !== 'close') group.add(arrowGroup) if (callExpName !== 'close') group.add(arrowGroup)
const extraSegmentGroup = createExtraSegmentHandle(scale, texture)
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
.normalize()
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
extraSegmentGroup.position.set(
from[0] + offsetFromBase.x,
from[1] + offsetFromBase.y,
0
)
extraSegmentGroup.visible = !shouldHide
group.add(extraSegmentGroup)
return group return group
} }
function createArrowhead(scale = 1): Group { function createArrowhead(scale = 1): Group {
const arrowMaterial = new MeshBasicMaterial({ color: 0xffffff }) const arrowMaterial = new MeshBasicMaterial({ color: 0xffffff })
const arrowheadMesh = new Mesh(new ConeGeometry(0.31, 1.5, 12), arrowMaterial) // specify the size of the geometry in pixels (i.e. cone height = 20px, cone radius = 4.5px)
arrowheadMesh.position.set(0, -0.6, 0) // we'll scale the group to the correct size later to match these sizes in screen space
const sphereMesh = new Mesh(new SphereGeometry(0.27, 12, 12), arrowMaterial) const arrowheadMesh = new Mesh(new ConeGeometry(4.5, 20, 12), arrowMaterial)
arrowheadMesh.position.set(0, -9, 0)
const sphereMesh = new Mesh(new SphereGeometry(4, 12, 12), arrowMaterial)
const arrowGroup = new Group() const arrowGroup = new Group()
arrowGroup.userData.type = ARROWHEAD arrowGroup.userData.type = ARROWHEAD
@ -150,6 +178,36 @@ function createArrowhead(scale = 1): Group {
return arrowGroup return arrowGroup
} }
function createExtraSegmentHandle(scale: number, texture: Texture): Group {
const particleMaterial = new PointsMaterial({
size: 12, // in pixels
map: texture,
transparent: true,
opacity: 0,
depthTest: false,
})
const mat = new MeshBasicMaterial({
transparent: true,
color: 0xffffff,
opacity: 0,
})
const particleGeometry = new BufferGeometry().setFromPoints([
new Vector3(0, 0, 0),
])
const sphereMesh = new Mesh(new SphereGeometry(6, 12, 12), mat) // sphere radius in pixels
const particle = new Points(particleGeometry, particleMaterial)
particle.userData.ignoreColorChange = true
particle.userData.type = EXTRA_SEGMENT_HANDLE
const extraSegmentGroup = new Group()
extraSegmentGroup.userData.type = EXTRA_SEGMENT_HANDLE
extraSegmentGroup.name = EXTRA_SEGMENT_HANDLE
extraSegmentGroup.add(sphereMesh)
extraSegmentGroup.add(particle)
extraSegmentGroup.scale.set(scale, scale, scale)
return extraSegmentGroup
}
export function tangentialArcToSegment({ export function tangentialArcToSegment({
prevSegment, prevSegment,
from, from,
@ -158,6 +216,7 @@ export function tangentialArcToSegment({
pathToNode, pathToNode,
isDraftSegment, isDraftSegment,
scale = 1, scale = 1,
texture,
}: { }: {
prevSegment: SketchGroup['value'][number] prevSegment: SketchGroup['value'][number]
from: Coords2d from: Coords2d
@ -166,6 +225,7 @@ export function tangentialArcToSegment({
pathToNode: PathToNode pathToNode: PathToNode
isDraftSegment?: boolean isDraftSegment?: boolean
scale?: number scale?: number
texture: Texture
}): Group { }): Group {
const group = new Group() const group = new Group()
@ -178,12 +238,13 @@ export function tangentialArcToSegment({
) )
: prevSegment.from : prevSegment.from
const { center, radius, startAngle, endAngle, ccw } = getTangentialArcToInfo({ const { center, radius, startAngle, endAngle, ccw, arcLength } =
arcStartPoint: from, getTangentialArcToInfo({
arcEndPoint: to, arcStartPoint: from,
tanPreviousPoint: previousPoint, arcEndPoint: to,
obtuse: true, tanPreviousPoint: previousPoint,
}) obtuse: true,
})
const geometry = createArcGeometry({ const geometry = createArcGeometry({
center, center,
@ -219,8 +280,28 @@ export function tangentialArcToSegment({
new Vector3(0, 1, 0), new Vector3(0, 1, 0),
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0) new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
) )
const pxLength = arcLength / scale
const shouldHide = pxLength < MIN_SEGMENT_LENGTH
arrowGroup.visible = !shouldHide
group.add(mesh, arrowGroup) const extraSegmentGroup = createExtraSegmentHandle(scale, texture)
const circumferenceInPx = (2 * Math.PI * radius) / scale
const extraSegmentAngleDelta =
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
const extraSegmentAngle = startAngle + (ccw ? 1 : -1) * extraSegmentAngleDelta
const extraSegmentOffset = new Vector2(
Math.cos(extraSegmentAngle) * radius,
Math.sin(extraSegmentAngle) * radius
)
extraSegmentGroup.position.set(
center[0] + extraSegmentOffset.x,
center[1] + extraSegmentOffset.y,
0
)
extraSegmentGroup.visible = !shouldHide
group.add(mesh, arrowGroup, extraSegmentGroup)
return group return group
} }
@ -242,8 +323,8 @@ export function createArcGeometry({
isDashed?: boolean isDashed?: boolean
scale?: number scale?: number
}): BufferGeometry { }): BufferGeometry {
const dashSize = 1.2 * scale const dashSizePx = 18 * scale
const gapSize = 1.2 * scale const gapSizePx = 18 * scale
const arcStart = new EllipseCurve( const arcStart = new EllipseCurve(
center[0], center[0],
center[1], center[1],
@ -265,8 +346,8 @@ export function createArcGeometry({
0 0
) )
const shape = new Shape() const shape = new Shape()
shape.moveTo(0, -0.08 * scale) shape.moveTo(0, -1.2 * scale)
shape.lineTo(0, 0.08 * scale) // The width of the line shape.lineTo(0, 1.2 * scale) // The width of the line
if (!isDashed) { if (!isDashed) {
const points = arcStart.getPoints(50) const points = arcStart.getPoints(50)
@ -281,7 +362,7 @@ export function createArcGeometry({
} }
const length = arcStart.getLength() const length = arcStart.getLength()
const totalDashes = length / (dashSize + gapSize) // rounding makes the dashes jittery since the new dash is suddenly appears instead of growing into place const totalDashes = length / (dashSizePx + gapSizePx) // rounding makes the dashes jittery since the new dash is suddenly appears instead of growing into place
const dashesAtEachEnd = Math.min(100, totalDashes / 2) // Assuming we want 50 dashes total, 25 at each end const dashesAtEachEnd = Math.min(100, totalDashes / 2) // Assuming we want 50 dashes total, 25 at each end
const dashGeometries = [] const dashGeometries = []
@ -289,8 +370,8 @@ export function createArcGeometry({
// Function to create a dash at a specific t value (0 to 1 along the curve) // Function to create a dash at a specific t value (0 to 1 along the curve)
const createDashAt = (t: number, curve: EllipseCurve) => { const createDashAt = (t: number, curve: EllipseCurve) => {
const startVec = curve.getPoint(t) const startVec = curve.getPoint(t)
const endVec = curve.getPoint(Math.min(0.5, t + dashSize / length)) const endVec = curve.getPoint(Math.min(0.5, t + dashSizePx / length))
const midVec = curve.getPoint(Math.min(0.5, t + dashSize / length / 2)) const midVec = curve.getPoint(Math.min(0.5, t + dashSizePx / length / 2))
const dashCurve = new CurvePath<Vector3>() const dashCurve = new CurvePath<Vector3>()
dashCurve.add( dashCurve.add(
new CatmullRomCurve3([ new CatmullRomCurve3([
@ -314,7 +395,8 @@ export function createArcGeometry({
} }
// fill in the remaining arc // fill in the remaining arc
const remainingArcLength = length - dashesAtEachEnd * 2 * (dashSize + gapSize) const remainingArcLength =
length - dashesAtEachEnd * 2 * (dashSizePx + gapSizePx)
if (remainingArcLength > 0) { if (remainingArcLength > 0) {
const remainingArcStartT = dashesAtEachEnd / totalDashes const remainingArcStartT = dashesAtEachEnd / totalDashes
const remainingArcEndT = 1 - remainingArcStartT const remainingArcEndT = 1 - remainingArcStartT
@ -359,8 +441,8 @@ export function dashedStraight(
shape: Shape, shape: Shape,
scale = 1 scale = 1
): BufferGeometry<NormalBufferAttributes> { ): BufferGeometry<NormalBufferAttributes> {
const dashSize = 1.2 * scale const dashSize = 18 * scale
const gapSize = 1.2 * scale // todo: gabSize is not respected const gapSize = 18 * scale // todo: gabSize is not respected
const dashLine = new LineCurve3( const dashLine = new LineCurve3(
new Vector3(from[0], from[1], 0), new Vector3(from[0], from[1], 0),
new Vector3(to[0], to[1], 0) new Vector3(to[0], to[1], 0)

View File

@ -162,6 +162,7 @@ export const line: SketchLineHelper = {
replaceExisting, replaceExisting,
referencedSegment, referencedSegment,
createCallback, createCallback,
spliceBetween,
}) => { }) => {
const _node = { ...node } const _node = { ...node }
const { node: pipe } = getNodeFromPath<PipeExpression | CallExpression>( const { node: pipe } = getNodeFromPath<PipeExpression | CallExpression>(
@ -178,6 +179,30 @@ export const line: SketchLineHelper = {
const newXVal = createLiteral(roundOff(to[0] - from[0], 2)) const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
const newYVal = createLiteral(roundOff(to[1] - from[1], 2)) const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
if (spliceBetween && !createCallback && pipe.type === 'PipeExpression') {
const callExp = createCallExpression('line', [
createArrayExpression([newXVal, newYVal]),
createPipeSubstitution(),
])
const pathToNodeIndex = pathToNode.findIndex(
(x) => x[1] === 'PipeExpression'
)
const pipeIndex = pathToNode[pathToNodeIndex + 1][0]
if (typeof pipeIndex === 'undefined' || typeof pipeIndex === 'string') {
throw new Error('pipeIndex is undefined')
// return
}
pipe.body = [
...pipe.body.slice(0, pipeIndex),
callExp,
...pipe.body.slice(pipeIndex),
]
return {
modifiedAst: _node,
pathToNode,
}
}
if (replaceExisting && createCallback && pipe.type !== 'CallExpression') { if (replaceExisting && createCallback && pipe.type !== 'CallExpression') {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback( const { callExp, valueUsedInTransform } = createCallback(
@ -1023,15 +1048,6 @@ export function changeSketchArguments(
throw new Error(`not a sketch line helper: ${callExpression?.callee?.name}`) throw new Error(`not a sketch line helper: ${callExpression?.callee?.name}`)
} }
interface CreateLineFnCallArgs {
node: Program
programMemory: ProgramMemory
to: [number, number]
from: [number, number]
fnName: ToolTip
pathToNode: PathToNode
}
export function compareVec2Epsilon( export function compareVec2Epsilon(
vec1: [number, number], vec1: [number, number],
vec2: [number, number], vec2: [number, number],
@ -1056,6 +1072,16 @@ export function compareVec2Epsilon2(
return distance < compareEpsilon return distance < compareEpsilon
} }
interface CreateLineFnCallArgs {
node: Program
programMemory: ProgramMemory
to: [number, number]
from: [number, number]
fnName: ToolTip
pathToNode: PathToNode
spliceBetween?: boolean
}
export function addNewSketchLn({ export function addNewSketchLn({
node: _node, node: _node,
programMemory: previousProgramMemory, programMemory: previousProgramMemory,
@ -1063,6 +1089,7 @@ export function addNewSketchLn({
fnName, fnName,
pathToNode, pathToNode,
from, from,
spliceBetween = false,
}: CreateLineFnCallArgs): { }: CreateLineFnCallArgs): {
modifiedAst: Program modifiedAst: Program
pathToNode: PathToNode pathToNode: PathToNode
@ -1083,6 +1110,7 @@ export function addNewSketchLn({
to, to,
from, from,
replaceExisting: false, replaceExisting: false,
spliceBetween,
}) })
} }

View File

@ -35,6 +35,8 @@ interface addCall extends ModifyAstBase {
referencedSegment?: Path referencedSegment?: Path
replaceExisting?: boolean replaceExisting?: boolean
createCallback?: TransformCallback // TODO: #29 probably should not be optional createCallback?: TransformCallback // TODO: #29 probably should not be optional
/// defaults to false, normal behavior is to add a new callExpression to the end of the pipeExpression
spliceBetween?: boolean
} }
interface updateArgs extends ModifyAstBase { interface updateArgs extends ModifyAstBase {

View File

@ -251,6 +251,7 @@ export function getTangentialArcToInfo({
startAngle: number startAngle: number
endAngle: number endAngle: number
ccw: boolean ccw: boolean
arcLength: number
} { } {
const result = get_tangential_arc_to_info( const result = get_tangential_arc_to_info(
arcStartPoint[0], arcStartPoint[0],
@ -268,6 +269,7 @@ export function getTangentialArcToInfo({
startAngle: result.start_angle, startAngle: result.start_angle,
endAngle: result.end_angle, endAngle: result.end_angle,
ccw: result.ccw > 0, ccw: result.ccw > 0,
arcLength: result.arc_length,
} }
} }

View File

@ -34,5 +34,28 @@ const bracket = startSketchOn('XY')
|> fillet({ |> fillet({
radius: filletR + thickness, radius: filletR + thickness,
tags: [getNextAdjacentEdge('outerEdge', %)] tags: [getNextAdjacentEdge('outerEdge', %)]
}, %) }, %)`
`
function findLineInExampleCode({
searchText,
example = bracket,
}: {
searchText: string
example?: string
}) {
const lines = example.split('\n')
const lineNumber = lines.findIndex((l) => l.includes(searchText)) + 1
if (lineNumber === 0) {
throw new Error(
`Could not find the line with search text "${searchText}" in the example code. Was it removed?`
)
}
return lineNumber
}
export const bracketWidthConstantLine = findLineInExampleCode({
searchText: 'const width',
})
export const bracketThicknessCalculationLine = findLineInExampleCode({
searchText: 'const thickness',
})

View File

@ -133,7 +133,7 @@ export type MoveDesc = { line: number; snippet: string }
export const modelingMachine = createMachine( export const modelingMachine = createMachine(
{ {
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBLWFJDWV5hVSZZTrlBUKjS1FZiUrDeWVDasaQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iaFlmZWZSmZRaKyGQzKYQaDYIGSmDSzdKyDSGcQ0KrAk5nTxtDpdAi3e5PWCvdgfKiGSLMVhjbh-BCCaw0WaQrZqdGLAr6RAZGSSWzCZSiIEKNFi5TY5q4y4E658dgPACuGC+NNi4wSQgqM3hNFEwNEGlEYthENELJkCjWplMFpsGlRUo8rVlNz4LAe7DV0Vpvy1jKtCkkVuZMjsDpNsOEwjKUIkcxNqWhomd5za3jJH2IZEomEzb0+9BGfs1oESEOtkmM0K0ahMplkZqOZUrYrRlgUJrTMoL5Pekj7HwAklcegEgl0BuFi99S-SA4ZoWUdWsTSLlsozZlzNkZPrG0a5F2e66hwPz6OCcgSK8+lAALZgO7+ABuj045BImB9PzL-CMeFW2qTQhU0ZMzVRKR0RoVJ6nyJQNFPC5z0HLN3ivbobzvIJH2fAJ3lQB5sAAL24dhv1-ed4nLQDoUkVRrX1CF8ihGQzXECFymNLJIU4-U8mQjN0LQwtMOIbhYEVEg8H8QjiLIu5v38CBsCk3MwCojUF1ohArCNCwNDSDFENMI51h5OEzGUCwKgtbJRDMtEhNE-tXJHMciEk6TZPfL1sC-TAVLUiiKE02d1TpGiAL05ZgztK0RVBYxNHYyzw1qcprCODQY2EZYjRc1DL087yHhk3B-AAQQAIW8fwAA0tKihkrC7JExEMGxOPhNJYXhAy7ShRY4NMWoGlcU5pTPESSoJLzcCk8rZNq+qAE1mv9XS2u2aw7GUOR9mFA7YU5YN8rstlHEdIrZvQ8SFqWir-DIKAuk2-8K2RaQlBA+QxWEUx+otXV1zyg1AbG27C3cjDSsWnzKq6fB2CLalfW06KvvEGYVANDE0TSfZ+sOc7gVRQHHSUKFobcubukexH-CYR4WdwVTyCVTASCeVT1LCj6dJiqwrVmW0eoyI5VC3dKxURWMqjG-LHVy1NJpxGaYfpiSEeWyr5NI8jv0wPQXpwKAhgijGWsXZZ+S7TQu3SI0aBWM0kpDO1QPysQVgmpoXRQu6xPhp7fI-ALjdN79sAtwWsbo8wcitQGkrMEUzQO7YTTGzFqiqWpaY+WGHrK57YFwEgmH8dhUEa+PWs4qRlnERYFDG6ozH6vcQzkeZ5n1RxwyLi97tDpmK6rmu642q2-yFr60QsIVOJjHHjBhdKupmI0h8cpc5hyEeS-HvX-DAABHJVlORqBUYb23G3KbfAayPIJG5Iol3RJETFg-VjqmGPtrRmZ8mA82NtQOe1FG75AsOGFY+xGwU03l-RsMwTCcUOLaTIag1YB3TLDE+80y6yQeGAB8qB3z+HIKQu4sAH7bWsMGQ41gDRWlRCacQZolBlEsMCfcOROQHWAWPAkAAlMAggwB8BCEqe4jDhZ1CkGiVQ+kdQGlhCCGyjk14misGZbIoiQ5yivtgauAAZPAYBp6oB-NAzGrV246JKKCW0CZ1CwiUNnTIe05CJghMY-s4lrhmOrqFGAdxsDKR5uQaeijEj7WDK3A0WD-rKObJYSQYFVDpMdKCY+VUADuMkCJEUNkpIKfNQqUH8HgAAZqgAgEBuBgHaLgV8qBXiSBgOwQQBtFIUUwIIBpqAEmIGMoifJGQxpmCVtw9KqJzBdlDFg2wENCklI4HJcpgzlLVI0nU3AjSCCPAeERSQTBubsEaQ8B8PS-D9N2UbYZozxlWX1NWLqqI0TGitGNfqrd+TZFdrBOorFDCbNKW+COgVgr81qaM5prT2mdO6b0wQflPzfhGccsZDiba6SJvyNEih7BQkMIKfqVopCOHfnYUo+UoXbKxZHKpIVDlIrORcq5JAblEXuRi1lgVcWNPecSyQix5D7zEJoalRlqzHVSK3ECShmUBFWo1I5JyWm4DaXgNFbSMUkAAEawEEHwUV+L0bzwTlZSE2SDqwXDLsCyRR4QHWrBkVEoJZAHULuraaQcYbFOhZqhq2qmncoeJc65tzBWPNNeay1byCVbRikTGYQj1B1FMPqLQ-UzI2TqNaCQG9oRAMDYHYSIatkarqv4NakbkV6tRV0o1iazWCD0Fa8VWDpAaMcvCHGxpTquyzYrC0XD83quqg2ptXKHjnJjby-ldyHl9KTd23tabPoTNXvAle+V5jpEhKdSEiJXbaJdaSmQs7XpdGbbq-VHT20bsEA+qRqabUwIDJmlIEJwWOTtIYYGBoBTogtOaE0UJhD3vwI+xdy7Y18vje+z9O6f2OL-Zxc62Q2pWDUGZT+EyRSAjRPIOC+oASztvqjJ9KKDVvoxXR94mGSzYaJZxbYyczAoP3PkEm7VXaMpUEuKwtHnx33eM26NKG10Jr6ax9jc5OMZv7cYEw+i1i5TSu60E-J5jMIRGIGMBpZ0sweGzDmXMebwpqTYpFz623oseZZ6zAVbMPEEAcsKKnIrpsSXbAUXDrAHTinIfq1RtjrzmMKdxch-ZTWrUQ0N2z3PPhs9zXmHKwqyaXTyuNAr30ZfZp57LPncuUH89bQLEzgtexROFtYkX0p5WkLGOLwH35JY1sGtyVi9W2MwOOPoU44jiv3EnQjRxMhHDyAs91Y1qxewxMwxid6q2ENQoNmxtc7GEjCTXV6+FolBVifE3dC8JkxkRPsMwsF1CKxlkUJWSIcaOCyCCY+u3huSGHLgDgBBJsqElSk2bCI5BA0smsMoaR24PZxoDNIP3rF-YB0DykWHCUZvyPyC0VoL2EeBNGQGswVgSHWcCWC+DkvbZEr9-bmBJC4AFd+Ubk4QgTau3a6wmVZDqClsyA0L3ECghmMuVWcsoKQq272BnaOmeSAAHJ1wAAqoDwOwWABAqoQAgIECiXpmaa7uOK+YiI80GkckZRYoJYTzCTmYV+mQc0KFR0NpXqv-Aa61zr0gYV7HY7q3pTQu5ZBAoxFoE0brEC8TFtg0oVoVjHwx+wYHPPWr8NmF2LIKhqNR7NGkKQcwhTLFBcsJ0cvNZuTT8DqkHGccVmYpKvNea5iyFgl1IvZlslOzmECbqkpq-9eLgAFRO1EmJDw4m1w5-0bnwe91wgOuYYESg7TolsPCIvqJJUXv3JYY03YR81rchP-Ap3p+z6aaEpU5j-CM9QHY83jpf6KEchaE6lkEQ2RjOGOoLIV2UvY+JUdmOudSL0c8AAeVwBbRfUNUkCqm8DH0EDAJaUEEgPYBgMtiX2uz0hKGDDyFqCckdDqDUFhCTAFGwTUFylbhKDg1P1hn8FZ38HqRIEoB6HGxUjAHYK5gCF5T1XeSZC4iOARCRy0ChBsFEAd2WH3zSByEhiFBsBHhYLrnYM4N8AnAX0GHeSFC+R0wkDmWyEOFhAdAgxKDsCUAGhXhcjIGwAfD5VaGnmZm5j1XgJczaXsMcPuEEFrkEA0PCjwLtQqGAldxBSWEyFkOSSsBKFSHbmhHUDsMBx8OcNrlcLIG6C0LGy510Mzz-Xyi+TkCh0YmhEoK0GrFWDsFghWBqGSIcKcPwBcJkRCmcNJELA8KY26W8L5SkX8PaP7HeSzngX9SqH2EUL00QFwwYihFbhBCe1yndxOFZwwHgCiD6ygEbxD0EEWDKFyEUHz3UC0EWyEEWElXwyFFrBxkOhcnxDAC2OX0EFBCIKhFmU0BNFyi8TFHKE5Hu1iMsErQIXl0LAePwPmJz0o3zzyELxhwYkaz-ljBMhuiYOKjEXuNUybyMAyGbjGlylsHeMBljz0kpTu1RGMj9jz16yDTP2LjSzKQUheXs05TxVBLtSlmDEdAxEUFjBqFSEBWSGdhgkd3yFlyBJr1pLrRhX8jhV80RRZIxJD3MhJVUEsGUVX1A3SjkBZFSFRDlnmFCNnXDUjVZKcSfg5AMRUGqIxELU4h2GGjUVyWOBRJEjpLnXWmNIVOXzEPxw4Vwz3DHWFF7jsjECqHzngzekc3lICy9Pbm2BWFgiESqDyEbGBjUElXhwqFUDLzqEkxRhk1GRNMXHbhZGxOtzFEbH7hJlB0BkpUcFeONBT2dNrWhVKyyzs1lMjMaULO2i7D-xBAtHAhsD9n6jJWyVrP3h1G+ybIG0V2f0wG7IzXsGXkIKNFDLWEoNUBDFRDmDrOHTUA9z2znP+0B3YAXMSX2D4XGi7BsHUHXJh2SEhBWHyQezmEBLp2BJnM9yPNZzuW-DPImRsCzTXj1EWFkEmIQHjyHMpST2tFkAPL+29193oX-LhGNGbl4TBBMDSDdksjLxDHbDRCOGULFFTxPJQuMEcgYmNCMmxI3270skrFbHMkA07BPzFNHwHAv0iU4Gv2nhQuPHlmPUtNz1tG3DyBDDWAWEow3Fpw2KIXQIgKN2wPQlgPIptB+N3lSVC2hyKCNBsgH1sGP2GnM2nI+DULYI4PROjPwKZAyDBwBHkFyloJkMsmovKAPC70yFTmsHqNSKaPSMEKstqy9JWAFCFHbztB5NdlkJshMHUCFF3k5EYPYraB6M4H8rrhaKkjaPQnIuW19mFCyAjE4lsCixZAHmLOZGtCtBcBcCAA */ /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwAgBRXHYwACdAgGs-cgALCN4Yji540ETBGWVhSStlU2UtK0NDVY1CxBlTDUkcnOtDcRoqzcaQdxb8ds7uvoHh2DH2SapDSLMVhzbi8JbWGjnfaWURqG4KTJHBAZGSSWzCZSiDYKa445QPJ6eNodLq9PjsQYAVww0yisziYKEFTWJxook2og0ohxSL2sMkMgUygUplMsJsGg0hgJzSJr1JPT4LEG7FpQNi80ZCGW6gF1RRdgl3KRwmEZVWEg0mIR+S5Mo8rUk3h+k2IZEomGd4ym9BmwIZCSMhiF60ykoRCLMsl5wbKe3Uexuwe59uebS9vwmTpdEwAkm8CMgSGNAmAoABbMD9fwANyGnHIJEwaui-s1gYQVlMceqmgxmlSBX0RilUhuNFS9XySg0qblGcm2e9+dJRZLQQrVYCE1Qg2wAC9uOwmy36e3FkZbZJVEK2Xt8qsZLzxHtylysvsX2y8nPHQus-+K7dEQ3CwBSJB4P4O57oe-RNv4EDYGB7pgKebagh2VichYGhpLcM7duoSJpFsFgVLC2SiN21y-i8-5LpmQHEKB4GQXWKrYI2mAIUhx4UKhvp0uhCz8JewponkChYtsxiaE+w4IDI+RSHkUp9qaKycrR6Y5gxkxMSBuBgYMEG4P4ACCABC3j+AAGmhGoYRenaCqI5ziGIhg2C+JxpMRYpnGKqwIpOpi1A0riPLKf66YBBaGcZpkWdZ-gAJoOSCImJFYUkCrh+xyIY7LsvJRTwgokjCHIFTQo4kraXpAE5gZLEmZBZBQF0GUBs51w5NISi9vIOLCKYxGwiy3LyKa7KjWFDX0XFpIJaxZldPg7A+oCraOVlRglGsKjsrc1xpEVxHKMGlWbFKo3hiowgLbFzXxa1SVMEMH24Ih5CUpgJDDIhyH8d156iS5grnKKPkZMGqjKMROJnGaVRhVVkoaGIT3eo1LVGatUG7geR5Npgej+E22BQLgoNOeDGyolJmhSeknI0Mo4i8tJApiup+TsiU2OZrjr3421ZnsQ2pPk5T1O03tLmje5gqjdJZhYryyhyOUIqjUK1RVLUQuLktwFvZBsC4CQTD+OwqB2fLWrXNcAocxGYXVGYxHZKiSnssoAdso4SnG01y6i4lFtWzbdtpY7mEvlImQc4YpoecYhwKYaaycsHVGp1aOShyLy3m2ZYAAI6UvB61QJt8e9ScKleaaYVsnIHm8rUkI5CYE5spi9yRYSMU46bzFi+9AOk9QgnqplTuyGsqQrC+VHpAXvKmB5FilJdoqZGoojF+PK3i-4gxgOWqB1v45Bl+wsAN-T1gVZd1glZoRU6ApwYvhY+xbDZBKGoLWJ8XqkgAEpgEEGAPgIRKQDGftlOoUhriqCwsydkSItjKHKGjOoOITgZ3AeHBUVdsA2wADJ4DALbVAqBmxzx2gvTCIo8ESBUDYMU6QiIKSUG5Ca1g-ZWixNKYe0U6LPTIe8ChMcOpbmwPBAG5B6HIOOPsCq4gpIp0xIoVBMZLDXVvMNdIhdi7mQAO4QW3ETWCx5uJAz4pQfweAABmqACAQG4GAdouAayoDGJIGA7BBDQWJnBTAgh3GoHUYpG4ZxJTbFuLUUUadiJSnMFJQUZ1NhyFWBY6xHBCYwRJo43iKFXG4A8QQIYgxdySCYP9dgHjBjlmCX4MJdiynROqbE5hZ46aJDwm5PYkpExckFGFYi2jUTZHZhOOoD5xFNAdFInGVibG1nrJxeCTjKkxK8T4vxASgkhMEJLXZUSYlxLOqia4ih7CrEMOiYigopCODyHka4L4qqFK2ZcriPFgYuMOXUhpTSSAtN3O085gKmy9I8bcnykgETyHzmITQbzcLrEHqkbRvYlD-OKVZGytkqk1O8bgXxeBTm+POSQAARrAQQfBEX9O2oMhWuTroyAnH7Qh-ktbrAyFKbYsgtZGwkWsnSGyikBFJXZClnjwWDEac01psLOlMpZWym5AzhJajOmsHIBK6imDZFofy+xpDckxF+TGNhiUKpSqlZVRzqUnMCfS7VzLBB6HZcim1dR0VUSbsILkSIbirChiKWE3IbhaWlWmRqkhNkktde61V6qoWao6aEnV-rA0Gt2ka35FgHweRWBzMVUb9hnHZrgv2DyZDOopvgLo7qqU0v8d6-NggOpdGLZyw1HZjUpETF80UGss4VDwdvNklgip2tNG2wddCwWDHqWqyF0K2n9vXcOv0pax0vgqlkKUgorBqG7EOIohU1iTqyHIduBTk3zl0umgItdNpduObSvt5yf0TCPUJE9zkzpuUuGYW6uFBwXVyuzUotRLpmlbe+0ewsv3+GA1mrdEKNUwv7cB0D88ergx5cYEw3JNGY1KscbYqIA6v1OGIGa+IMPrKw-K-wH1BhfR+n9AGwLnEbr6R6ntdL+18YE5xITgxBD7P4qRlh5HhkrFRLzWQVolLCjkIjbW6crSYmnR3NtMmqyCf+oDCp-E8PbpzXurVoSLPfTk9ZxTtnKAqa5UajTEklDWC1isaoohiKmkOmaYzVExRmc47K4WNDqX0MYb0ORtsFH9CUdxFRaiS2sIg6aM4RUzATnUKjBGCk0buQ8o4LIWxi5JboXbRhkhcy4A4AQW5aQ8GDSxAiU4cgxoKWFGUNIIpSseVGmkRrtCUuYDax19gXWATHoKxR-IqJYRXpknyzYJolZWlQbYdkFq2SzeSy1hbAA5e2AAFVAeBH4EHMhACAgRjwql449-otyA6BQHlRXCCJthIgDuYHIYU26KEGhd5rDCbv3Z+890g-EmEjvA-TTQ5gTDjb5acJmCgTRGJsPvUogoObF3a51uJxgbVaHkCYVQeRbiZyKFYEo5xVha3hqkGSVOlsrYx+t7Kd5UUWotUdvllg2eXm7JVFmVoNjeQ46slN9EAAqmXODKMGKou2aXKSUP8E1+bf3JTuUyFJMU7N6NJCtJVKqaRGfsytI9eLqbKTfXtshFU-4ADyuAJNeqCeZbwGvBBe+8YIX37AA803y2p-a2jyjKWopKOoagkTckhFsF5ahMbaIOqHfwuB7ZuJIJQHwwRQgITABXv6ARIXUricsV8wZThTa0AcMUYOVioo5zkOaGInUe7INgcsULWj0N4-9alweANBPH5PgYgg7aCAr5QOJNV1gIkUPMleiIRuQx8iAqZEb1ANWX1P-AM+4G8Wn98b0C-e1L46yvmB6+n+ZjiVrNyfP8gqgioh87cz1rxVhtEthytMYFAXBIoy8MB4AogR58A1sk9tQEQyhch9EbxmZOZf5xIld1Ah88l0M1c5QSQwA0CwYlhtgKpZowptEuQ+wkQExyh4QSsOdLBTBQ5qChlEAoDzgpIsgVB25Wdxo3I88LVuR8dMgaIPdFoIEqCwMRcRwTAeYI1ptNBKgidf4XlitL1o0DoTAIpyDMNFxsNwl7E9kvMxMPE+CFY4YKpJQUlHArBTRUgZlkhWZxxwd8gVkooZVU1sN4VykQU7DUAHCnZ1B7lVBLBUEtZuw3l+RUg1IvIA4ao21FVyUYkoi2Ft4LAlAuwVA7AJxht71t4yguRgoMFmdqgsjM1ciVD0CO8ttBQchgwfYo12Y3I-YsEqgDY10O0Ii8jeo41rxxxTUqg8ht5xo1BUVxs51k5nc21cMmiyMaCjARQe5qNrQXkVAA4LoVBKpuxDZVhTgBZzNPpLN3NhMlNQU+lRisd2QTjMQC9sgtgShwscRKoXlrdl1RpVdAj1ddJTcrsnjhl7ALBwopIbB1BhRs8fjt4gDUhbhNJTDgSP0cYwSEdFsOAISNEsRoSShYSBiESRtkh9ga0jtJch4zCuNFwcTWsy82kmwCTFIbATU05WRIwlJicqipDychRZA4d5tJBbt-AHsnskCNj+COSJA9QXwdhccVB8CigMRfYcRExgwR8cQBd8TmjNjOw2RzBthOQIQi9uxZdOxYx1hgwtSkwmZi4td8BFFdd9dIjDS5S5BNBHdV4tZhDRReR25XYhRhRGcsQ5Bi4o8fdPs48cxA92TNFzAJBc4BYE0Ths8HcldbAuRZodES8y9-BN9lDZSFZlgMhUUJU2NMYC8wsFIuQzh40ztjBW5VIr938b8oAZ9m9SzVMjTPw0QMRJcxQ0N2Y+88ETB1AMRc54R3d6S2hr9OBb9Y578wJH8cwkywpKp-ZGdDQXxbBEZIRA5tiIQhRBQ4CnAgA */
id: 'Modeling', id: 'Modeling',
tsTypes: {} as import('./modelingMachine.typegen').Typegen0, tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
@ -166,12 +166,6 @@ export const modelingMachine = createMachine(
states: { states: {
idle: { idle: {
on: { on: {
'Set selection': {
target: 'idle',
internal: true,
actions: 'Set selection',
},
'Enter sketch': [ 'Enter sketch': [
{ {
target: 'animating to existing sketch', target: 'animating to existing sketch',
@ -202,12 +196,6 @@ export const modelingMachine = createMachine(
states: { states: {
SketchIdle: { SketchIdle: {
on: { on: {
'Set selection': {
target: 'SketchIdle',
internal: true,
actions: 'Set selection',
},
'Make segment vertical': { 'Make segment vertical': {
cond: 'Can make selection vertical', cond: 'Can make selection vertical',
target: 'SketchIdle', target: 'SketchIdle',
@ -411,12 +399,6 @@ export const modelingMachine = createMachine(
exit: [], exit: [],
on: { on: {
'Set selection': {
target: 'Line tool',
description: `This is just here to stop one of the higher level "Set selections" firing when we are just trying to set the IDE code without triggering a full engine-execute`,
internal: true,
},
'Equip tangential arc to': { 'Equip tangential arc to': {
target: 'Tangential arc to', target: 'Tangential arc to',
cond: 'is editing existing sketch', cond: 'is editing existing sketch',
@ -435,14 +417,7 @@ export const modelingMachine = createMachine(
], ],
}, },
normal: { normal: {},
on: {
'Set selection': {
target: 'normal',
internal: true,
},
},
},
'No Points': { 'No Points': {
entry: 'setup noPoints onClick listener', entry: 'setup noPoints onClick listener',
@ -475,11 +450,6 @@ export const modelingMachine = createMachine(
entry: 'set up draft arc', entry: 'set up draft arc',
on: { on: {
'Set selection': {
target: 'Tangential arc to',
internal: true,
},
'Equip Line tool': 'Line tool', 'Equip Line tool': 'Line tool',
}, },
}, },
@ -519,11 +489,6 @@ export const modelingMachine = createMachine(
target: 'animating to plane', target: 'animating to plane',
actions: ['reset sketch metadata'], actions: ['reset sketch metadata'],
}, },
'Set selection': {
target: 'Sketch no face',
internal: true,
},
}, },
}, },
@ -537,13 +502,6 @@ export const modelingMachine = createMachine(
}, },
}, },
on: {
'Set selection': {
target: 'animating to plane',
internal: true,
},
},
entry: 'clientToEngine cam sync direction', entry: 'clientToEngine cam sync direction',
}, },
@ -836,6 +794,7 @@ export const modelingMachine = createMachine(
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) { if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
await sceneEntitiesManager.tearDownSketch({ removeAxis: false }) await sceneEntitiesManager.tearDownSketch({ removeAxis: false })
} }
sceneInfra.resetMouseListeners()
await sceneEntitiesManager.setupSketch({ await sceneEntitiesManager.setupSketch({
sketchPathToNode: sketchDetails?.sketchPathToNode || [], sketchPathToNode: sketchDetails?.sketchPathToNode || [],
forward: sketchDetails.zAxis, forward: sketchDetails.zAxis,
@ -843,9 +802,13 @@ export const modelingMachine = createMachine(
position: sketchDetails.origin, position: sketchDetails.origin,
maybeModdedAst: kclManager.ast, maybeModdedAst: kclManager.ast,
}) })
sceneEntitiesManager.setupSketchIdleCallbacks( sceneInfra.resetMouseListeners()
sketchDetails?.sketchPathToNode || [] sceneEntitiesManager.setupSketchIdleCallbacks({
) pathToNode: sketchDetails?.sketchPathToNode || [],
forward: sketchDetails.zAxis,
up: sketchDetails.yAxis,
position: sketchDetails.origin,
})
})() })()
}, },
'animate after sketch': () => { 'animate after sketch': () => {

View File

@ -21,7 +21,8 @@ export default function Export() {
<section className="flex-1"> <section className="flex-1">
<h2 className="text-2xl font-bold">Export</h2> <h2 className="text-2xl font-bold">Export</h2>
<p className="my-4"> <p className="my-4">
Try opening the project menu and clicking "Export Part". Try opening the project menu and clicking the "Export Part" at the
bottom of the pane.
</p> </p>
<p className="my-4"> <p className="my-4">
{APP_NAME} uses{' '} {APP_NAME} uses{' '}

View File

@ -2,6 +2,7 @@ import { OnboardingButtons, kbdClasses, useDismiss, useNextClick } from '.'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { useStore } from '../../useStore' import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight' import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
import { bracketWidthConstantLine } from 'lib/exampleKcl'
export default function InteractiveNumbers() { export default function InteractiveNumbers() {
const { buttonDownInStream } = useStore((s) => ({ const { buttonDownInStream } = useStore((s) => ({
@ -26,11 +27,25 @@ export default function InteractiveNumbers() {
<h2 className="text-3xl font-bold">Hybrid editing</h2> <h2 className="text-3xl font-bold">Hybrid editing</h2>
<p className="my-4"> <p className="my-4">
Try changing the value of <code>width</code> on line 2 by holding We believe editing in Modeling App should feel fluid between code
the <kbd className={kbdClasses}>Alt</kbd> (or{' '} and point-and-click, so that you can work in the way that feels most
<kbd className={kbdClasses}>Option</kbd>) key and dragging the natural to you. Let's try something out that demonstrates this
number left and right. You can hold down different modifier keys to principle, by editing numbers without typing.
change the value by different increments: </p>
<ol className="pl-6 my-4 list-decimal">
<li className="list-decimal">
Press and hold the <kbd className={kbdClasses}>Alt</kbd> (or{' '}
<kbd className={kbdClasses}>Option</kbd>) key
</li>
<li>
Hover over the number assigned to <code>width</code> on line{' '}
{bracketWidthConstantLine}
</li>
<li>Drag the number left and right to change its value</li>
</ol>
<p className="my-4">
You can hold down different modifier keys to change the value by
different increments:
</p> </p>
<ul className="flex flex-col text-sm my-4 mx-12 divide-y divide-chalkboard-20 dark:divide-chalkboard-70"> <ul className="flex flex-col text-sm my-4 mx-12 divide-y divide-chalkboard-20 dark:divide-chalkboard-70">
<li className="flex justify-between m-0 px-0 py-2"> <li className="flex justify-between m-0 px-0 py-2">
@ -70,10 +85,8 @@ export default function InteractiveNumbers() {
lets you interact with numbers in your code by dragging them around. lets you interact with numbers in your code by dragging them around.
</p> </p>
<p className="my-4"> <p className="my-4">
We believe editing in Modeling App should feel fluid between code We're going to keep extending the text editor, and we'd love to hear
and point-and-click, so that you can work in the way that feels most your ideas for how to make it better.
natural to you. We're going to keep extending the text editor, and
we'd love to hear your ideas for how to make it better.
</p> </p>
</section> </section>
<OnboardingButtons <OnboardingButtons

View File

@ -141,10 +141,11 @@ export default function Introduction() {
<section className="my-12"> <section className="my-12">
<p className="my-4"> <p className="my-4">
Welcome to {APP_NAME}! This is a hardware design tool that lets you Welcome to {APP_NAME}! This is a hardware design tool that lets you
edit visually, with code, or both. It's powered by the first API edit visually, with code, or both. It's powered by the KittyCAD
created for anyone to build hardware design tools. The 3D view is Design API, the first API created for anyone to build hardware
not running on your computer, but is instead being streamed to you design tools. The 3D view is not running on your computer, but is
from a remote GPU as video. instead being streamed to you from an instance of our Geometry
Engine on a remote GPU as video.
</p> </p>
<p className="my-4"> <p className="my-4">
This is an alpha release, so you will encounter bugs and missing This is an alpha release, so you will encounter bugs and missing
@ -173,7 +174,7 @@ export default function Introduction() {
className="mt-6" className="mt-6"
dismiss={dismiss} dismiss={dismiss}
next={next} next={next}
nextText="Camera" nextText="Mouse Controls"
/> />
</div> </div>
</div> </div>

View File

@ -4,6 +4,7 @@ import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight' import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
import { Themes, getSystemTheme } from 'lib/theme' import { Themes, getSystemTheme } from 'lib/theme'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { bracketThicknessCalculationLine } from 'lib/exampleKcl'
export default function ParametricModeling() { export default function ParametricModeling() {
const { buttonDownInStream } = useStore((s) => ({ const { buttonDownInStream } = useStore((s) => ({
@ -61,7 +62,10 @@ export default function ParametricModeling() {
<p className="my-4"> <p className="my-4">
We are able to easily calculate the thickness of the material based We are able to easily calculate the thickness of the material based
on the width of the bracket to meet a set safety factor on{' '} on the width of the bracket to meet a set safety factor on{' '}
<em className="text-energy-60 dark:text-energy-20">line 14</em>. <em className="text-energy-60 dark:text-energy-20">
line {bracketThicknessCalculationLine}
</em>
.
</p> </p>
</section> </section>
<OnboardingButtons <OnboardingButtons

View File

@ -30,9 +30,15 @@ export default function Sketching() {
<h1 className="text-2xl font-bold">Sketching</h1> <h1 className="text-2xl font-bold">Sketching</h1>
<p className="my-4"> <p className="my-4">
Our 3D modeling tools are still very much a work in progress, but we Our 3D modeling tools are still very much a work in progress, but we
want to show you some early features. Try creating a sketch by want to show you some early features. Try sketching by clicking Start
clicking Create Sketch in the top toolbar, then clicking the Line Sketch in the top toolbar and selecting a plane to draw on. Now you
tool, and clicking in the 3D view. can start clicking to draw lines and shapes.
</p>
<p className="my-4">
The Line tool will be equipped by default, but you can switch it to as
you go by clicking another tool in the toolbar, or unequip it by
clicking the Line tool button. With no tool selected, you can move
points and add constraints to your sketch.
</p> </p>
<p className="my-4"> <p className="my-4">
Watch the code pane as you click. Point-and-click interactions are Watch the code pane as you click. Point-and-click interactions are

View File

@ -14,12 +14,12 @@ slow-timeout = { period = "30s", terminate-after = 5 }
[[profile.default.overrides]] [[profile.default.overrides]]
filter = "test(serial_test_)" filter = "test(serial_test_)"
test-group = "serial-integration" test-group = "serial-integration"
threads-required = 4 threads-required = 2
[[profile.ci.overrides]] [[profile.ci.overrides]]
filter = "test(serial_test_)" filter = "test(serial_test_)"
test-group = "serial-integration" test-group = "serial-integration"
threads-required = 4 threads-required = 2
[[profile.default.overrides]] [[profile.default.overrides]]
filter = "test(parser::parser_impl::snapshot_tests)" filter = "test(parser::parser_impl::snapshot_tests)"

View File

@ -565,9 +565,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.3" version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -589,9 +589,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.3" version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@ -944,6 +944,23 @@ dependencies = [
"syn 2.0.55", "syn 2.0.55",
] ]
[[package]]
name = "derive-docs"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138b94245509a9dd516008788b585c34847829cf37b40a758b4aa581cf94f147"
dependencies = [
"Inflector",
"convert_case",
"once_cell",
"proc-macro2",
"quote",
"regex",
"serde",
"serde_tokenstream",
"syn 2.0.55",
]
[[package]] [[package]]
name = "diesel_derives" name = "diesel_derives"
version = "2.1.3" version = "2.1.3"
@ -1842,7 +1859,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.1.46" version = "0.1.47"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx 0.5.1", "approx 0.5.1",
@ -1856,7 +1873,7 @@ dependencies = [
"criterion", "criterion",
"dashmap", "dashmap",
"databake", "databake",
"derive-docs", "derive-docs 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"expectorate", "expectorate",
"futures", "futures",
"gltf-json", "gltf-json",
@ -1943,8 +1960,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-execution-plan" name = "kittycad-execution-plan"
version = "0.1.1" version = "0.1.3"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#a3b8df282c684b3f7003ec96f5cea85284e0f1f9" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e913f8e5f3ef7928cddca2e7b53c6582d7be6a8f900d18ce6c31c04083056270"
dependencies = [ dependencies = [
"bytes", "bytes",
"gltf-json", "gltf-json",
@ -1966,7 +1984,8 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-execution-plan-macros" name = "kittycad-execution-plan-macros"
version = "0.1.9" version = "0.1.9"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#a3b8df282c684b3f7003ec96f5cea85284e0f1f9" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0611fc9b9786175da21d895ffa0f65039e19c9111e94a41b7af999e3b95f045f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1976,7 +1995,8 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-execution-plan-traits" name = "kittycad-execution-plan-traits"
version = "0.1.15" version = "0.1.15"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#a3b8df282c684b3f7003ec96f5cea85284e0f1f9" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123cb47e2780ea8ef3aa67b4db237a27b388d3d3b96db457e274aa4565723151"
dependencies = [ dependencies = [
"serde", "serde",
"thiserror", "thiserror",
@ -1985,8 +2005,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds" name = "kittycad-modeling-cmds"
version = "0.2.7" version = "0.2.10"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#a3b8df282c684b3f7003ec96f5cea85284e0f1f9" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41beb7d9b776df93fd449604de5c447e33c7bd3326fd590002dc18cf5f08166"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -2014,7 +2035,8 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds-macros" name = "kittycad-modeling-cmds-macros"
version = "0.1.5" version = "0.1.5"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#a3b8df282c684b3f7003ec96f5cea85284e0f1f9" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "385775cc9d5bf25579f3029824ca1a6e7ab1b7c338e972ec8e8fcefff801f353"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2023,8 +2045,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-session" name = "kittycad-modeling-session"
version = "0.1.1" version = "0.1.2"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#a3b8df282c684b3f7003ec96f5cea85284e0f1f9" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ee3a24232a086ec12ae4cfee443485c22e6c6959936d861006fa13bebef0904"
dependencies = [ dependencies = [
"futures", "futures",
"kittycad", "kittycad",

View File

@ -60,11 +60,11 @@ members = [
[workspace.dependencies] [workspace.dependencies]
kittycad = { version = "0.2.63", default-features = false, features = ["js", "requests"] } kittycad = { version = "0.2.63", default-features = false, features = ["js", "requests"] }
kittycad-execution-plan = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" } kittycad-execution-plan = "0.1.3"
kittycad-execution-plan-macros = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" } kittycad-execution-plan-macros = "0.1.9"
kittycad-execution-plan-traits = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" } kittycad-execution-plan-traits = "0.1.14"
kittycad-modeling-cmds = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" } kittycad-modeling-cmds = "0.2.10"
kittycad-modeling-session = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" } kittycad-modeling-session = "0.1.2"
[[test]] [[test]]
name = "executor" name = "executor"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-lib" name = "kcl-lib"
description = "KittyCAD Language implementation and tools" description = "KittyCAD Language implementation and tools"
version = "0.1.46" version = "0.1.47"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"
@ -15,11 +15,11 @@ anyhow = { version = "1.0.81", features = ["backtrace"] }
async-recursion = "1.1.0" async-recursion = "1.1.0"
async-trait = "0.1.79" async-trait = "0.1.79"
chrono = "0.4.37" chrono = "0.4.37"
clap = { version = "4.5.3", features = ["cargo", "derive", "env", "unicode"], optional = true } clap = { version = "4.5.4", features = ["cargo", "derive", "env", "unicode"], optional = true }
dashmap = "5.5.3" dashmap = "5.5.3"
databake = { version = "0.1.7", features = ["derive"] } databake = { version = "0.1.7", features = ["derive"] }
#derive-docs = { version = "0.1.12" } derive-docs = { version = "0.1.12" }
derive-docs = { path = "../derive-docs" } #derive-docs = { path = "../derive-docs" }
futures = { version = "0.3.30" } futures = { version = "0.3.30" }
gltf-json = "1.4.0" gltf-json = "1.4.0"
kittycad = { workspace = true } kittycad = { workspace = true }

View File

@ -2649,8 +2649,8 @@ async fn execute_pipe_body(
source_range: SourceRange, source_range: SourceRange,
ctx: &ExecutorContext, ctx: &ExecutorContext,
) -> Result<MemoryItem, KclError> { ) -> Result<MemoryItem, KclError> {
let mut body_iter = body.iter(); let mut body = body.iter();
let first = body_iter.next().ok_or_else(|| { let first = body.next().ok_or_else(|| {
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
message: "Pipe expressions cannot be empty".to_owned(), message: "Pipe expressions cannot be empty".to_owned(),
source_ranges: vec![source_range], source_ranges: vec![source_range],

View File

@ -93,6 +93,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
} else { } else {
batched_requests batched_requests
}; };
// println!("Running batch: {final_req:#?}");
// Create the map of original command IDs to source range. // Create the map of original command IDs to source range.
// This is for the wasm side, kurt needs it for selections. // This is for the wasm side, kurt needs it for selections.

View File

@ -994,9 +994,8 @@ impl ExecutorContext {
} }
} }
/// Execute a AST's program. /// Execute an AST's program.
#[async_recursion(?Send)] pub async fn execute_outer(
pub async fn execute(
program: crate::ast::types::Program, program: crate::ast::types::Program,
memory: &mut ProgramMemory, memory: &mut ProgramMemory,
_options: BodyType, _options: BodyType,
@ -1013,7 +1012,17 @@ pub async fn execute(
}, },
) )
.await?; .await?;
execute(program, memory, _options, ctx).await
}
/// Execute an AST's program.
#[async_recursion(?Send)]
pub(crate) async fn execute(
program: crate::ast::types::Program,
memory: &mut ProgramMemory,
_options: BodyType,
ctx: &ExecutorContext,
) -> Result<ProgramMemory, KclError> {
let pipe_info = PipeInfo::default(); let pipe_info = PipeInfo::default();
// Iterate over the body of the program. // Iterate over the body of the program.
@ -1299,7 +1308,7 @@ mod tests {
units: kittycad::types::UnitLength::Mm, units: kittycad::types::UnitLength::Mm,
is_mock: false, is_mock: false,
}; };
let memory = execute(program, &mut mem, BodyType::Root, &ctx).await?; let memory = execute_outer(program, &mut mem, BodyType::Root, &ctx).await?;
Ok(memory) Ok(memory)
} }

View File

@ -576,6 +576,8 @@ pub struct TangentialArcInfoOutput {
pub end_angle: f64, pub end_angle: f64,
/// If the arc is counter-clockwise. /// If the arc is counter-clockwise.
pub ccw: i32, pub ccw: i32,
/// The length of the arc.
pub arc_length: f64,
} }
// tanPreviousPoint and arcStartPoint make up a straight segment leading into the arc (of which the arc should be tangential). The arc should start at arcStartPoint and end at, arcEndPoint // tanPreviousPoint and arcStartPoint make up a straight segment leading into the arc (of which the arc should be tangential). The arc should start at arcStartPoint and end at, arcEndPoint
@ -626,6 +628,17 @@ pub fn get_tangential_arc_to_info(input: TangentialArcInfoInput) -> TangentialAr
let end_angle = (input.arc_end_point[1] - center[1]).atan2(input.arc_end_point[0] - center[0]); let end_angle = (input.arc_end_point[1] - center[1]).atan2(input.arc_end_point[0] - center[0]);
let ccw = is_points_ccw(&[input.arc_start_point, arc_mid_point, input.arc_end_point]); let ccw = is_points_ccw(&[input.arc_start_point, arc_mid_point, input.arc_end_point]);
let arc_mid_angle = (arc_mid_point[1] - center[1]).atan2(arc_mid_point[0] - center[0]);
let start_to_mid_arc_length = radius
* delta(Angle::from_radians(start_angle), Angle::from_radians(arc_mid_angle))
.radians()
.abs();
let mid_to_end_arc_length = radius
* delta(Angle::from_radians(arc_mid_angle), Angle::from_radians(end_angle))
.radians()
.abs();
let arc_length = start_to_mid_arc_length + mid_to_end_arc_length;
TangentialArcInfoOutput { TangentialArcInfoOutput {
center, center,
radius, radius,
@ -633,6 +646,7 @@ pub fn get_tangential_arc_to_info(input: TangentialArcInfoInput) -> TangentialAr
start_angle, start_angle,
end_angle, end_angle,
ccw, ccw,
arc_length,
} }
} }
@ -758,6 +772,58 @@ mod get_tangential_arc_to_info_tests {
assert_relative_eq!(result.end_angle, -PI / 2.0); assert_relative_eq!(result.end_angle, -PI / 2.0);
assert_eq!(result.ccw, 1); assert_eq!(result.ccw, 1);
} }
#[test]
fn test_arc_length_obtuse_cw() {
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
tan_previous_point: [-1.0, -1.0],
arc_start_point: [-1.0, 0.0],
arc_end_point: [0.0, -1.0],
obtuse: true,
});
let circumference = 2.0 * PI * result.radius;
let expected_length = circumference * 3.0 / 4.0; // 3 quarters of a circle circle
assert_relative_eq!(result.arc_length, expected_length);
}
#[test]
fn test_arc_length_acute_cw() {
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
tan_previous_point: [-1.0, -1.0],
arc_start_point: [-1.0, 0.0],
arc_end_point: [0.0, 1.0],
obtuse: true,
});
let circumference = 2.0 * PI * result.radius;
let expected_length = circumference / 4.0; // 1 quarters of a circle circle
assert_relative_eq!(result.arc_length, expected_length);
}
#[test]
fn test_arc_length_obtuse_ccw() {
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
tan_previous_point: [1.0, -1.0],
arc_start_point: [1.0, 0.0],
arc_end_point: [0.0, -1.0],
obtuse: true,
});
let circumference = 2.0 * PI * result.radius;
let expected_length = circumference * 3.0 / 4.0; // 1 quarters of a circle circle
assert_relative_eq!(result.arc_length, expected_length);
}
#[test]
fn test_arc_length_acute_ccw() {
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
tan_previous_point: [1.0, -1.0],
arc_start_point: [1.0, 0.0],
arc_end_point: [0.0, 1.0],
obtuse: true,
});
let circumference = 2.0 * PI * result.radius;
let expected_length = circumference / 4.0; // 1 quarters of a circle circle
assert_relative_eq!(result.arc_length, expected_length);
}
} }
pub fn get_tangent_point_from_previous_arc( pub fn get_tangent_point_from_previous_arc(

View File

@ -41,7 +41,7 @@ pub async fn execute_wasm(
is_mock, is_mock,
}; };
let memory = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &ctx) let memory = kcl_lib::executor::execute_outer(program, &mut mem, kcl_lib::executor::BodyType::Root, &ctx)
.await .await
.map_err(String::from)?; .map_err(String::from)?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
@ -333,6 +333,8 @@ pub struct TangentialArcInfoOutputWasm {
pub end_angle: f64, pub end_angle: f64,
/// Flag to determine if the arc is counter clockwise. /// Flag to determine if the arc is counter clockwise.
pub ccw: i32, pub ccw: i32,
/// The length of the arc.
pub arc_length: f64,
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -362,6 +364,7 @@ pub fn get_tangential_arc_to_info(
start_angle: result.start_angle, start_angle: result.start_angle,
end_angle: result.end_angle, end_angle: result.end_angle,
ccw: result.ccw, ccw: result.ccw,
arc_length: result.arc_length,
} }
} }

View File

@ -0,0 +1,42 @@
fn make_circle = (face, tag, pos, radius) => {
const sg0 = startSketchOn(face, tag)
const sg1 = startProfileAt([pos[0] + radius, pos[1]], sg0)
const sg2 = arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, sg1, 'arc-' + tag)
return close(sg2)
}
fn pentagon = (len) => {
const sg3 = startSketchOn('XY')
const sg4 = startProfileAt([-len / 2, -len / 2], sg3)
const sg5 = angledLine({ angle: 0, length: len }, sg4, 'a')
const sg6 = angledLine({
angle: segAng('a', sg5) + 180 - 108,
length: len
},sg5, 'b')
const sg7 = angledLine({
angle: segAng('b', sg6) + 180 - 108,
length: len
}, sg6, 'c')
const sg8 = angledLine({
angle: segAng('c', sg7) + 180 - 108,
length: len
}, sg7, 'd')
return angledLine({
angle: segAng('d', sg8) + 180 - 108,
length: len
}, sg8)
}
const p = pentagon(48)
const pe = extrude(30, p)
const plumbus0 = make_circle(pe, 'a', [0, 0], 9)
const plumbus1 = extrude(18, plumbus0)
const plumbus2 = fillet({
radius: 0.5,
tags: ['arc-a', getOppositeEdge('arc-a', plumbus1)]
}, plumbus1)

View File

@ -0,0 +1,46 @@
fn make_circle = (face, tag, pos, radius) => {
const sg = startSketchOn(face, tag)
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %, 'arc-' + tag)
|> close(%)
return sg
}
fn pentagon = (len) => {
const sg = startSketchOn('XY')
|> startProfileAt([-len / 2, -len / 2], %)
|> angledLine({ angle: 0, length: len }, %, 'a')
|> angledLine({
angle: segAng('a', %) + 180 - 108,
length: len
}, %, 'b')
|> angledLine({
angle: segAng('b', %) + 180 - 108,
length: len
}, %, 'c')
|> angledLine({
angle: segAng('c', %) + 180 - 108,
length: len
}, %, 'd')
|> angledLine({
angle: segAng('d', %) + 180 - 108,
length: len
}, %)
return sg
}
const p = pentagon(48)
|> extrude(30, %)
const plumbus0 = make_circle(p, 'a', [0, 0], 9)
|> extrude(18, %)
|> fillet({
radius: 0.5,
tags: ['arc-a', getOppositeEdge('arc-a', %)]
}, %)

View File

@ -0,0 +1,17 @@
const ANSWER = 41803
fn m = (s) => {
return (ANSWER * s + 12345) % 214748
}
let xs = 205804
let ys = 71816
let ox = 35 - (m(xs) % 70)
let oy = 35 - (m(ys) % 70)
const r = startSketchOn('XZ')
|> startProfileAt([ox, oy], %)
|> line([1, 0], %)
|> line([0, -1], %)
|> line([-1, 0], %)
|> close(%)
|> extrude(1, %)

View File

@ -41,7 +41,7 @@ async fn execute_and_snapshot(code: &str, units: kittycad::types::UnitLength) ->
let mut mem: kcl_lib::executor::ProgramMemory = Default::default(); let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
let ctx = kcl_lib::executor::ExecutorContext::new(ws, units.clone()).await?; let ctx = kcl_lib::executor::ExecutorContext::new(ws, units.clone()).await?;
let _ = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &ctx).await?; let _ = kcl_lib::executor::execute_outer(program, &mut mem, kcl_lib::executor::BodyType::Root, &ctx).await?;
let (x, y) = kcl_lib::std::utils::get_camera_zoom_magnitude_per_unit_length(units); let (x, y) = kcl_lib::std::utils::get_camera_zoom_magnitude_per_unit_length(units);
@ -112,6 +112,33 @@ const part002 = startSketchOn(part001, "here")
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face.png", &result, 0.999); twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face.png", &result, 0.999);
} }
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_riddle_small() {
let code = include_str!("inputs/riddle_small.kcl");
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/riddle_small.png", &result, 0.999);
}
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_pentagon_fillet_desugar() {
let code = include_str!("inputs/pentagon_fillet_desugar.kcl");
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Cm)
.await
.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/pentagon_fillet_desugar.png", &result, 0.999);
}
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_pentagon_fillet_sugar() {
let code = include_str!("inputs/pentagon_fillet_sugar.kcl");
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Cm)
.await
.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/pentagon_fillet_sugar.png", &result, 0.999);
}
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn serial_test_sketch_on_face_start() { async fn serial_test_sketch_on_face_start() {
let code = r#"fn cube = (pos, scale) => { let code = r#"fn cube = (pos, scale) => {
@ -1839,3 +1866,66 @@ const part002 = startSketchOn(part001, 'end')
.unwrap(); .unwrap();
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_sketch_on_edge.png", &result, 1.0); twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_sketch_on_edge.png", &result, 1.0);
} }
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_plumbus_fillets() {
let code = r#"fn make_circle = (face, tag, pos, radius) => {
const sg = startSketchOn(face, tag)
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %, 'arc-' + tag)
|> close(%)
return sg
}
fn pentagon = (len) => {
const sg = startSketchOn('XY')
|> startProfileAt([-len / 2, -len / 2], %)
|> angledLine({ angle: 0, length: len }, %, 'a')
|> angledLine({
angle: segAng('a', %) + 180 - 108,
length: len
}, %, 'b')
|> angledLine({
angle: segAng('b', %) + 180 - 108,
length: len
}, %, 'c')
|> angledLine({
angle: segAng('c', %) + 180 - 108,
length: len
}, %, 'd')
|> angledLine({
angle: segAng('d', %) + 180 - 108,
length: len
}, %)
return sg
}
const p = pentagon(8)
|> extrude(5, %)
const plumbus0 = make_circle(p, 'a', [0, 0], 1.5)
|> extrude(3, %)
|> fillet({
radius: 0.5,
tags: ['arc-a', getOppositeEdge('arc-a', %)]
}, %)
// const plumbus1 = make_circle(p, 'b', [0, 0], 1.5)
// |> extrude(3, %)
// |> fillet({
// radius: 0.5,
// tags: ['arc-b', getOppositeEdge('arc-b', %)]
// }, %)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
.await
.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/plumbus_fillets.png", &result, 1.0);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 112 KiB

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