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 expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
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)
const pointToDragFirst = [691, 237]
const pointToDragFirst = [787, 565]
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
await page.mouse.down()
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'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> 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], %)
|> close(%)`)
@ -1509,7 +1515,9 @@ test('Sketch on face', async ({ page, context }) => {
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> 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], %)
|> close(%)
|> 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",
"version": "0.17.0",
"version": "0.17.1",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.15.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

View File

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

View File

@ -28,12 +28,15 @@ export function createGridHelper({
gridHelper.rotation.x = Math.PI / 2
return gridHelper
}
const fudgeFactor = 72.66985970437086
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) =>
(group.position.distanceTo(cam.position) * cam.fov) / 4000
(group.position.distanceTo(cam.position) * cam.fov * fudgeFactor) /
4000 /
window.innerHeight
export function isQuaternionVertical(q: Quaternion) {
const v = new Vector3(0, 0, 1).applyQuaternion(q)

View File

@ -12,6 +12,7 @@ import {
OrthographicCamera,
PerspectiveCamera,
PlaneGeometry,
Points,
Quaternion,
Scene,
Shape,
@ -87,14 +88,17 @@ import { EngineCommandManager } from 'lang/std/engineConnection'
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_BODY = 'straight-segment-body'
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 =
'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.
// That mostly mean sketch elements.
@ -111,8 +115,12 @@ export class SceneEntities {
this.engineCommandManager = engineCommandManager
this.scene = sceneInfra?.scene
sceneInfra?.camControls.subscribeToCamChange(this.onCamChange)
window.addEventListener('resize', this.onWindowResize)
}
onWindowResize = () => {
this.onCamChange()
}
onCamChange = () => {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -282,7 +290,6 @@ export class SceneEntities {
sketchGroup: SketchGroup
variableDeclarationName: string
}> {
sceneInfra.resetMouseListeners()
this.createIntersectionPlane()
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
@ -295,7 +302,7 @@ export class SceneEntities {
})
const sketchGroup = sketchGroupFromPathToNode({
pathToNode: sketchPathToNode,
ast: kclManager.ast,
ast: maybeModdedAst,
programMemory,
})
if (!Array.isArray(sketchGroup?.value))
@ -383,6 +390,7 @@ export class SceneEntities {
pathToNode: segPathToNode,
isDraftSegment,
scale: factor,
texture: sceneInfra.extraSegmentTexture,
})
} else {
seg = straightSegment({
@ -393,6 +401,7 @@ export class SceneEntities {
isDraftSegment,
scale: factor,
callExpName,
texture: sceneInfra.extraSegmentTexture,
})
}
seg.layers.set(SKETCH_LAYER)
@ -435,6 +444,7 @@ export class SceneEntities {
) => {
await kclManager.updateAst(modifiedAst, false)
await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners()
await this.setupSketch({
sketchPathToNode,
forward,
@ -442,7 +452,12 @@ export class SceneEntities {
position: origin,
maybeModdedAst: kclManager.ast,
})
this.setupSketchIdleCallbacks(sketchPathToNode)
this.setupSketchIdleCallbacks({
forward,
up,
position: origin,
pathToNode: sketchPathToNode,
})
}
setUpDraftSegment = async (
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`
let modifiedAst = addNewSketchLn({
node: kclManager.ast,
const mod = addNewSketchLn({
node: _ast,
programMemory: kclManager.programMemory,
to: [lastSeg.to[0], lastSeg.to[1]],
from: [lastSeg.to[0], lastSeg.to[1]],
fnName: segmentName,
pathToNode: sketchPathToNode,
}).modifiedAst
modifiedAst = parse(recast(modifiedAst))
})
const modifiedAst = parse(recast(mod.modifiedAst))
const draftExpressionsIndices = { start: index, end: index }
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners()
const { truncatedAst, programMemoryOverride, sketchGroup } =
await this.setupSketch({
sketchPathToNode,
@ -549,10 +565,101 @@ export class SceneEntities {
...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({
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
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({
object: selected,
intersection2d: intersectionPoint.twoD,
@ -755,8 +862,7 @@ export class SceneEntities {
group.userData.to = to
group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
arrowGroup.position.set(to[0], to[1], 0)
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
const previousPoint =
prevSegment?.type === 'TangentialArcTo'
@ -774,13 +880,40 @@ export class SceneEntities {
obtuse: true,
})
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)
const pxLength = arcInfo.arcLength / scale
const shouldHide = pxLength < MIN_SEGMENT_LENGTH
if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0)
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(
(child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY
@ -827,10 +960,17 @@ export class SceneEntities {
group.userData.from = from
group.userData.to = to
const shape = new Shape()
shape.moveTo(0, -0.08 * scale)
shape.lineTo(0, 0.08 * scale) // The width of the line
shape.moveTo(0, -1.2 * scale) // The width of the line in px (2.4px in this case)
shape.lineTo(0, 1.2 * scale)
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) {
arrowGroup.position.set(to[0], to[1], 0)
@ -842,6 +982,21 @@ export class SceneEntities {
.normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
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(
@ -1160,7 +1315,7 @@ function colorSegment(object: any, color: number) {
])
if (straightSegmentBody) {
straightSegmentBody.traverse((child) => {
if (child instanceof Mesh) {
if (child instanceof Mesh && !child.userData.ignoreColorChange) {
child.material.color.set(color)
}
})
@ -1264,7 +1419,7 @@ function massageFormats(a: any): Vector3 {
function mouseEnterLeaveCallbacks() {
return {
onMouseEnter: ({ selected }: OnMouseEnterLeaveArgs) => {
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial
@ -1286,6 +1441,14 @@ function mouseEnterLeaveCallbacks() {
sceneInfra.highlightCallback([node.start, node.end])
const yellow = 0xffff00
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
}
sceneInfra.highlightCallback([0, 0])
@ -1302,6 +1465,14 @@ function mouseEnterLeaveCallbacks() {
selected,
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)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial

View File

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

View File

@ -12,14 +12,20 @@ import {
Mesh,
MeshBasicMaterial,
NormalBufferAttributes,
Points,
PointsMaterial,
Shape,
SphereGeometry,
Texture,
Vector2,
Vector3,
} from 'three'
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
import {
EXTRA_SEGMENT_HANDLE,
EXTRA_SEGMENT_OFFSET_PX,
MIN_SEGMENT_LENGTH,
PROFILE_START,
STRAIGHT_SEGMENT,
STRAIGHT_SEGMENT_BODY,
@ -44,7 +50,7 @@ export function profileStart({
}) {
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 mesh = new Mesh(geometry, body)
@ -71,6 +77,7 @@ export function straightSegment({
isDraftSegment,
scale = 1,
callExpName,
texture,
}: {
from: Coords2d
to: Coords2d
@ -79,12 +86,13 @@ export function straightSegment({
isDraftSegment?: boolean
scale?: number
callExpName: string
texture: Texture
}): Group {
const group = new Group()
const shape = new Shape()
shape.moveTo(0, -0.08 * scale)
shape.lineTo(0, 0.08 * scale) // The width of the line
shape.moveTo(0, -1.2 * scale)
shape.lineTo(0, 1.2 * scale)
let geometry
if (isDraftSegment) {
@ -122,24 +130,44 @@ export function straightSegment({
}
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)
arrowGroup.position.set(to[0], to[1], 0)
const dir = new Vector3()
.subVectors(new Vector3(to[0], to[1], 0), new Vector3(from[0], from[1], 0))
.normalize()
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)
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
}
function createArrowhead(scale = 1): Group {
const arrowMaterial = new MeshBasicMaterial({ color: 0xffffff })
const arrowheadMesh = new Mesh(new ConeGeometry(0.31, 1.5, 12), arrowMaterial)
arrowheadMesh.position.set(0, -0.6, 0)
const sphereMesh = new Mesh(new SphereGeometry(0.27, 12, 12), arrowMaterial)
// specify the size of the geometry in pixels (i.e. cone height = 20px, cone radius = 4.5px)
// we'll scale the group to the correct size later to match these sizes in screen space
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()
arrowGroup.userData.type = ARROWHEAD
@ -150,6 +178,36 @@ function createArrowhead(scale = 1): Group {
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({
prevSegment,
from,
@ -158,6 +216,7 @@ export function tangentialArcToSegment({
pathToNode,
isDraftSegment,
scale = 1,
texture,
}: {
prevSegment: SketchGroup['value'][number]
from: Coords2d
@ -166,6 +225,7 @@ export function tangentialArcToSegment({
pathToNode: PathToNode
isDraftSegment?: boolean
scale?: number
texture: Texture
}): Group {
const group = new Group()
@ -178,12 +238,13 @@ export function tangentialArcToSegment({
)
: prevSegment.from
const { center, radius, startAngle, endAngle, ccw } = getTangentialArcToInfo({
arcStartPoint: from,
arcEndPoint: to,
tanPreviousPoint: previousPoint,
obtuse: true,
})
const { center, radius, startAngle, endAngle, ccw, arcLength } =
getTangentialArcToInfo({
arcStartPoint: from,
arcEndPoint: to,
tanPreviousPoint: previousPoint,
obtuse: true,
})
const geometry = createArcGeometry({
center,
@ -219,8 +280,28 @@ export function tangentialArcToSegment({
new Vector3(0, 1, 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
}
@ -242,8 +323,8 @@ export function createArcGeometry({
isDashed?: boolean
scale?: number
}): BufferGeometry {
const dashSize = 1.2 * scale
const gapSize = 1.2 * scale
const dashSizePx = 18 * scale
const gapSizePx = 18 * scale
const arcStart = new EllipseCurve(
center[0],
center[1],
@ -265,8 +346,8 @@ export function createArcGeometry({
0
)
const shape = new Shape()
shape.moveTo(0, -0.08 * scale)
shape.lineTo(0, 0.08 * scale) // The width of the line
shape.moveTo(0, -1.2 * scale)
shape.lineTo(0, 1.2 * scale) // The width of the line
if (!isDashed) {
const points = arcStart.getPoints(50)
@ -281,7 +362,7 @@ export function createArcGeometry({
}
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 dashGeometries = []
@ -289,8 +370,8 @@ export function createArcGeometry({
// Function to create a dash at a specific t value (0 to 1 along the curve)
const createDashAt = (t: number, curve: EllipseCurve) => {
const startVec = curve.getPoint(t)
const endVec = curve.getPoint(Math.min(0.5, t + dashSize / length))
const midVec = curve.getPoint(Math.min(0.5, t + dashSize / length / 2))
const endVec = curve.getPoint(Math.min(0.5, t + dashSizePx / length))
const midVec = curve.getPoint(Math.min(0.5, t + dashSizePx / length / 2))
const dashCurve = new CurvePath<Vector3>()
dashCurve.add(
new CatmullRomCurve3([
@ -314,7 +395,8 @@ export function createArcGeometry({
}
// fill in the remaining arc
const remainingArcLength = length - dashesAtEachEnd * 2 * (dashSize + gapSize)
const remainingArcLength =
length - dashesAtEachEnd * 2 * (dashSizePx + gapSizePx)
if (remainingArcLength > 0) {
const remainingArcStartT = dashesAtEachEnd / totalDashes
const remainingArcEndT = 1 - remainingArcStartT
@ -359,8 +441,8 @@ export function dashedStraight(
shape: Shape,
scale = 1
): BufferGeometry<NormalBufferAttributes> {
const dashSize = 1.2 * scale
const gapSize = 1.2 * scale // todo: gabSize is not respected
const dashSize = 18 * scale
const gapSize = 18 * scale // todo: gabSize is not respected
const dashLine = new LineCurve3(
new Vector3(from[0], from[1], 0),
new Vector3(to[0], to[1], 0)

View File

@ -162,6 +162,7 @@ export const line: SketchLineHelper = {
replaceExisting,
referencedSegment,
createCallback,
spliceBetween,
}) => {
const _node = { ...node }
const { node: pipe } = getNodeFromPath<PipeExpression | CallExpression>(
@ -178,6 +179,30 @@ export const line: SketchLineHelper = {
const newXVal = createLiteral(roundOff(to[0] - from[0], 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') {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback(
@ -1023,15 +1048,6 @@ export function changeSketchArguments(
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(
vec1: [number, number],
vec2: [number, number],
@ -1056,6 +1072,16 @@ export function compareVec2Epsilon2(
return distance < compareEpsilon
}
interface CreateLineFnCallArgs {
node: Program
programMemory: ProgramMemory
to: [number, number]
from: [number, number]
fnName: ToolTip
pathToNode: PathToNode
spliceBetween?: boolean
}
export function addNewSketchLn({
node: _node,
programMemory: previousProgramMemory,
@ -1063,6 +1089,7 @@ export function addNewSketchLn({
fnName,
pathToNode,
from,
spliceBetween = false,
}: CreateLineFnCallArgs): {
modifiedAst: Program
pathToNode: PathToNode
@ -1083,6 +1110,7 @@ export function addNewSketchLn({
to,
from,
replaceExisting: false,
spliceBetween,
})
}

View File

@ -35,6 +35,8 @@ interface addCall extends ModifyAstBase {
referencedSegment?: Path
replaceExisting?: boolean
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 {

View File

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

View File

@ -34,5 +34,28 @@ const bracket = startSketchOn('XY')
|> fillet({
radius: filletR + thickness,
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(
{
/** @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',
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
@ -166,12 +166,6 @@ export const modelingMachine = createMachine(
states: {
idle: {
on: {
'Set selection': {
target: 'idle',
internal: true,
actions: 'Set selection',
},
'Enter sketch': [
{
target: 'animating to existing sketch',
@ -202,12 +196,6 @@ export const modelingMachine = createMachine(
states: {
SketchIdle: {
on: {
'Set selection': {
target: 'SketchIdle',
internal: true,
actions: 'Set selection',
},
'Make segment vertical': {
cond: 'Can make selection vertical',
target: 'SketchIdle',
@ -411,12 +399,6 @@ export const modelingMachine = createMachine(
exit: [],
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': {
target: 'Tangential arc to',
cond: 'is editing existing sketch',
@ -435,14 +417,7 @@ export const modelingMachine = createMachine(
],
},
normal: {
on: {
'Set selection': {
target: 'normal',
internal: true,
},
},
},
normal: {},
'No Points': {
entry: 'setup noPoints onClick listener',
@ -475,11 +450,6 @@ export const modelingMachine = createMachine(
entry: 'set up draft arc',
on: {
'Set selection': {
target: 'Tangential arc to',
internal: true,
},
'Equip Line tool': 'Line tool',
},
},
@ -519,11 +489,6 @@ export const modelingMachine = createMachine(
target: 'animating to plane',
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',
},
@ -836,6 +794,7 @@ export const modelingMachine = createMachine(
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
await sceneEntitiesManager.tearDownSketch({ removeAxis: false })
}
sceneInfra.resetMouseListeners()
await sceneEntitiesManager.setupSketch({
sketchPathToNode: sketchDetails?.sketchPathToNode || [],
forward: sketchDetails.zAxis,
@ -843,9 +802,13 @@ export const modelingMachine = createMachine(
position: sketchDetails.origin,
maybeModdedAst: kclManager.ast,
})
sceneEntitiesManager.setupSketchIdleCallbacks(
sketchDetails?.sketchPathToNode || []
)
sceneInfra.resetMouseListeners()
sceneEntitiesManager.setupSketchIdleCallbacks({
pathToNode: sketchDetails?.sketchPathToNode || [],
forward: sketchDetails.zAxis,
up: sketchDetails.yAxis,
position: sketchDetails.origin,
})
})()
},
'animate after sketch': () => {

View File

@ -21,7 +21,8 @@ export default function Export() {
<section className="flex-1">
<h2 className="text-2xl font-bold">Export</h2>
<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 className="my-4">
{APP_NAME} uses{' '}

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
import { Themes, getSystemTheme } from 'lib/theme'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { bracketThicknessCalculationLine } from 'lib/exampleKcl'
export default function ParametricModeling() {
const { buttonDownInStream } = useStore((s) => ({
@ -61,7 +62,10 @@ export default function ParametricModeling() {
<p className="my-4">
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{' '}
<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>
</section>
<OnboardingButtons

View File

@ -30,9 +30,15 @@ export default function Sketching() {
<h1 className="text-2xl font-bold">Sketching</h1>
<p className="my-4">
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
clicking Create Sketch in the top toolbar, then clicking the Line
tool, and clicking in the 3D view.
want to show you some early features. Try sketching by clicking Start
Sketch in the top toolbar and selecting a plane to draw on. Now you
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 className="my-4">
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]]
filter = "test(serial_test_)"
test-group = "serial-integration"
threads-required = 4
threads-required = 2
[[profile.ci.overrides]]
filter = "test(serial_test_)"
test-group = "serial-integration"
threads-required = 4
threads-required = 2
[[profile.default.overrides]]
filter = "test(parser::parser_impl::snapshot_tests)"

View File

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

View File

@ -60,11 +60,11 @@ members = [
[workspace.dependencies]
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-macros = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
kittycad-execution-plan-traits = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
kittycad-modeling-cmds = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
kittycad-modeling-session = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
kittycad-execution-plan = "0.1.3"
kittycad-execution-plan-macros = "0.1.9"
kittycad-execution-plan-traits = "0.1.14"
kittycad-modeling-cmds = "0.2.10"
kittycad-modeling-session = "0.1.2"
[[test]]
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]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.1.46"
version = "0.1.47"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -15,11 +15,11 @@ anyhow = { version = "1.0.81", features = ["backtrace"] }
async-recursion = "1.1.0"
async-trait = "0.1.79"
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"
databake = { version = "0.1.7", features = ["derive"] }
#derive-docs = { version = "0.1.12" }
derive-docs = { path = "../derive-docs" }
derive-docs = { version = "0.1.12" }
#derive-docs = { path = "../derive-docs" }
futures = { version = "0.3.30" }
gltf-json = "1.4.0"
kittycad = { workspace = true }

View File

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

View File

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

View File

@ -994,9 +994,8 @@ impl ExecutorContext {
}
}
/// Execute a AST's program.
#[async_recursion(?Send)]
pub async fn execute(
/// Execute an AST's program.
pub async fn execute_outer(
program: crate::ast::types::Program,
memory: &mut ProgramMemory,
_options: BodyType,
@ -1013,7 +1012,17 @@ pub async fn execute(
},
)
.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();
// Iterate over the body of the program.
@ -1299,7 +1308,7 @@ mod tests {
units: kittycad::types::UnitLength::Mm,
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)
}

View File

@ -576,6 +576,8 @@ pub struct TangentialArcInfoOutput {
pub end_angle: f64,
/// If the arc is counter-clockwise.
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
@ -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 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 {
center,
radius,
@ -633,6 +646,7 @@ pub fn get_tangential_arc_to_info(input: TangentialArcInfoInput) -> TangentialAr
start_angle,
end_angle,
ccw,
arc_length,
}
}
@ -758,6 +772,58 @@ mod get_tangential_arc_to_info_tests {
assert_relative_eq!(result.end_angle, -PI / 2.0);
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(

View File

@ -41,7 +41,7 @@ pub async fn execute_wasm(
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
.map_err(String::from)?;
// 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,
/// Flag to determine if the arc is counter clockwise.
pub ccw: i32,
/// The length of the arc.
pub arc_length: f64,
}
#[wasm_bindgen]
@ -362,6 +364,7 @@ pub fn get_tangential_arc_to_info(
start_angle: result.start_angle,
end_angle: result.end_angle,
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 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);
@ -112,6 +112,33 @@ const part002 = startSketchOn(part001, "here")
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")]
async fn serial_test_sketch_on_face_start() {
let code = r#"fn cube = (pos, scale) => {
@ -1839,3 +1866,66 @@ const part002 = startSketchOn(part001, 'end')
.unwrap();
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