2024-02-11 12:59:00 +11:00
|
|
|
import {
|
|
|
|
BoxGeometry,
|
|
|
|
DoubleSide,
|
|
|
|
Group,
|
2024-03-04 08:14:37 +11:00
|
|
|
Intersection,
|
2024-02-11 12:59:00 +11:00
|
|
|
Mesh,
|
|
|
|
MeshBasicMaterial,
|
2024-03-04 08:14:37 +11:00
|
|
|
Object3D,
|
|
|
|
Object3DEventMap,
|
2024-02-11 12:59:00 +11:00
|
|
|
OrthographicCamera,
|
|
|
|
PerspectiveCamera,
|
|
|
|
PlaneGeometry,
|
2024-04-03 13:22:56 +11:00
|
|
|
Points,
|
2024-02-11 12:59:00 +11:00
|
|
|
Quaternion,
|
|
|
|
Scene,
|
|
|
|
Vector2,
|
|
|
|
Vector3,
|
|
|
|
} from 'three'
|
|
|
|
import {
|
2024-11-13 09:41:27 -05:00
|
|
|
ANGLE_SNAP_THRESHOLD_DEGREES,
|
2024-02-11 12:59:00 +11:00
|
|
|
ARROWHEAD,
|
|
|
|
AXIS_GROUP,
|
2024-10-31 07:04:38 -07:00
|
|
|
DRAFT_POINT,
|
|
|
|
DRAFT_POINT_GROUP,
|
2024-02-17 07:04:24 +11:00
|
|
|
getSceneScale,
|
2024-02-11 12:59:00 +11:00
|
|
|
INTERSECTION_PLANE_LAYER,
|
2024-07-30 14:16:53 -04:00
|
|
|
OnClickCallbackArgs,
|
2024-03-04 08:14:37 +11:00
|
|
|
OnMouseEnterLeaveArgs,
|
2024-02-11 12:59:00 +11:00
|
|
|
RAYCASTABLE_PLANE,
|
|
|
|
SKETCH_GROUP_SEGMENTS,
|
|
|
|
SKETCH_LAYER,
|
|
|
|
X_AXIS,
|
|
|
|
Y_AXIS,
|
2024-02-14 08:03:20 +11:00
|
|
|
} from './sceneInfra'
|
2024-03-22 10:23:04 +11:00
|
|
|
import { isQuaternionVertical, quaternionFromUpNForward } from './helpers'
|
2024-02-11 12:59:00 +11:00
|
|
|
import {
|
|
|
|
CallExpression,
|
|
|
|
parse,
|
|
|
|
Path,
|
|
|
|
PathToNode,
|
|
|
|
PipeExpression,
|
|
|
|
Program,
|
|
|
|
recast,
|
2024-09-27 15:44:44 -07:00
|
|
|
Sketch,
|
2024-02-11 12:59:00 +11:00
|
|
|
VariableDeclaration,
|
|
|
|
VariableDeclarator,
|
2024-09-27 15:44:44 -07:00
|
|
|
sketchFromKclValue,
|
2024-12-06 13:57:31 +13:00
|
|
|
defaultSourceRange,
|
|
|
|
sourceRangeFromRust,
|
|
|
|
resultIsOk,
|
2024-12-17 15:12:18 -05:00
|
|
|
SourceRange,
|
2025-01-17 14:34:36 -05:00
|
|
|
topLevelRange,
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
CallExpressionKw,
|
2025-02-12 10:22:56 +13:00
|
|
|
VariableMap,
|
2024-02-11 12:59:00 +11:00
|
|
|
} from 'lang/wasm'
|
2024-04-17 20:18:07 -07:00
|
|
|
import {
|
|
|
|
engineCommandManager,
|
|
|
|
kclManager,
|
|
|
|
sceneInfra,
|
|
|
|
codeManager,
|
2024-04-19 14:24:40 -07:00
|
|
|
editorManager,
|
2025-03-15 10:08:39 -07:00
|
|
|
rustContext,
|
2024-04-17 20:18:07 -07:00
|
|
|
} from 'lib/singletons'
|
2025-01-27 14:24:28 +01:00
|
|
|
import { getNodeFromPath } from 'lang/queryAst'
|
|
|
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
2024-10-31 07:04:38 -07:00
|
|
|
import { executeAst, ToolTip } from 'lang/langHelpers'
|
2024-02-11 12:59:00 +11:00
|
|
|
import {
|
2024-09-13 21:14:14 +10:00
|
|
|
createProfileStartHandle,
|
|
|
|
SegmentUtils,
|
|
|
|
segmentUtils,
|
2024-02-11 12:59:00 +11:00
|
|
|
} from './segments'
|
|
|
|
import {
|
2024-05-23 00:53:15 -04:00
|
|
|
addCallExpressionsToPipe,
|
2024-02-11 12:59:00 +11:00
|
|
|
addCloseToPipe,
|
|
|
|
addNewSketchLn,
|
2025-02-28 11:50:14 -06:00
|
|
|
ARG_END_ABSOLUTE,
|
2024-02-11 12:59:00 +11:00
|
|
|
changeSketchArguments,
|
2024-03-02 08:48:30 +11:00
|
|
|
updateStartProfileAtArgs,
|
2024-02-11 12:59:00 +11:00
|
|
|
} from 'lang/std/sketch'
|
2024-09-13 21:14:14 +10:00
|
|
|
import { isArray, isOverlap, roundOff } from 'lib/utils'
|
2024-02-11 12:59:00 +11:00
|
|
|
import {
|
|
|
|
createArrayExpression,
|
|
|
|
createCallExpressionStdLib,
|
2025-02-15 00:57:04 +11:00
|
|
|
createIdentifier,
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
createCallExpressionStdLibKw,
|
|
|
|
createLabeledArg,
|
2024-02-11 12:59:00 +11:00
|
|
|
createLiteral,
|
2025-02-15 00:57:04 +11:00
|
|
|
createNodeFromExprSnippet,
|
2024-09-23 22:42:51 +10:00
|
|
|
createObjectExpression,
|
2024-04-19 11:56:21 -04:00
|
|
|
createPipeExpression,
|
2024-02-11 12:59:00 +11:00
|
|
|
createPipeSubstitution,
|
2025-02-15 00:57:04 +11:00
|
|
|
createVariableDeclaration,
|
2024-04-19 11:56:21 -04:00
|
|
|
findUniqueName,
|
2025-02-15 00:57:04 +11:00
|
|
|
getInsertIndex,
|
|
|
|
insertNewStartProfileAt,
|
|
|
|
updateSketchNodePathsWithInsertIndex,
|
2024-02-11 12:59:00 +11:00
|
|
|
} from 'lang/modifyAst'
|
2024-08-03 18:08:51 +10:00
|
|
|
import { Selections, getEventForSegmentSelection } from 'lib/selections'
|
2024-02-11 12:59:00 +11:00
|
|
|
import { createGridHelper, orthoScale, perspScale } from './helpers'
|
2024-03-22 10:23:04 +11:00
|
|
|
import { Models } from '@kittycad/lib'
|
2024-04-03 19:38:16 +11:00
|
|
|
import { uuidv4 } from 'lib/utils'
|
2025-02-15 00:57:04 +11:00
|
|
|
import {
|
|
|
|
SegmentOverlayPayload,
|
|
|
|
SketchDetails,
|
|
|
|
SketchDetailsUpdate,
|
|
|
|
SketchTool,
|
|
|
|
} from 'machines/modelingMachine'
|
2024-07-25 19:03:56 +10:00
|
|
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
2024-04-19 11:56:21 -04:00
|
|
|
import {
|
|
|
|
getRectangleCallExpressions,
|
|
|
|
updateRectangleSketch,
|
2024-11-18 10:04:09 -05:00
|
|
|
updateCenterRectangleSketch,
|
2024-04-19 11:56:21 -04:00
|
|
|
} from 'lib/rectangleTool'
|
2024-09-10 13:30:39 -04:00
|
|
|
import { getThemeColorForThreeJs, Themes } from 'lib/theme'
|
2025-02-15 00:57:04 +11:00
|
|
|
import { err, reportRejection, trap } from 'lib/trap'
|
2024-07-08 16:41:00 -04:00
|
|
|
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
2025-03-01 13:59:01 -08:00
|
|
|
import { Point3d } from '@rust/kcl-lib/bindings/Point3d'
|
2024-09-23 22:42:51 +10:00
|
|
|
import { SegmentInputs } from 'lang/std/stdTypes'
|
2025-03-01 13:59:01 -08:00
|
|
|
import { Node } from '@rust/kcl-lib/bindings/Node'
|
2024-11-13 09:41:27 -05:00
|
|
|
import { radToDeg } from 'three/src/math/MathUtils'
|
2025-02-15 00:57:04 +11:00
|
|
|
import toast from 'react-hot-toast'
|
2024-12-17 15:12:18 -05:00
|
|
|
import { getArtifactFromRange, codeRefFromRange } from 'lang/std/artifactGraph'
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
type DraftSegment = 'line' | 'tangentialArcTo'
|
|
|
|
|
2024-04-03 13:22:56 +11:00
|
|
|
export const EXTRA_SEGMENT_HANDLE = 'extraSegmentHandle'
|
|
|
|
export const EXTRA_SEGMENT_OFFSET_PX = 8
|
|
|
|
export const PROFILE_START = 'profile-start'
|
2024-02-11 12:59:00 +11:00
|
|
|
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_DASH =
|
|
|
|
'tangential-arc-to-segment-body-dashed'
|
2024-04-03 13:22:56 +11:00
|
|
|
export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
|
|
|
|
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
|
2025-02-15 00:57:04 +11:00
|
|
|
export const CIRCLE_THREE_POINT_SEGMENT = 'circle-three-point-segment'
|
|
|
|
export const CIRCLE_THREE_POINT_SEGMENT_BODY = 'circle-segment-body'
|
|
|
|
export const CIRCLE_THREE_POINT_SEGMENT_DASH =
|
|
|
|
'circle-three-point-segment-body-dashed'
|
|
|
|
export const CIRCLE_THREE_POINT_HANDLE1 = 'circle-three-point-handle1'
|
|
|
|
export const CIRCLE_THREE_POINT_HANDLE2 = 'circle-three-point-handle2'
|
|
|
|
export const CIRCLE_THREE_POINT_HANDLE3 = 'circle-three-point-handle3'
|
2024-09-23 22:42:51 +10:00
|
|
|
export const CIRCLE_SEGMENT = 'circle-segment'
|
|
|
|
export const CIRCLE_SEGMENT_BODY = 'circle-segment-body'
|
|
|
|
export const CIRCLE_SEGMENT_DASH = 'circle-segment-body-dashed'
|
|
|
|
export const CIRCLE_CENTER_HANDLE = 'circle-center-handle'
|
2024-04-03 13:22:56 +11:00
|
|
|
export const SEGMENT_WIDTH_PX = 1.6
|
2024-04-04 11:07:51 +11:00
|
|
|
export const HIDE_SEGMENT_LENGTH = 75 // in pixels
|
|
|
|
export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels
|
2024-09-23 22:42:51 +10:00
|
|
|
export const SEGMENT_BODIES = [
|
|
|
|
STRAIGHT_SEGMENT,
|
|
|
|
TANGENTIAL_ARC_TO_SEGMENT,
|
|
|
|
CIRCLE_SEGMENT,
|
2025-02-15 00:57:04 +11:00
|
|
|
CIRCLE_THREE_POINT_SEGMENT,
|
2024-09-23 22:42:51 +10:00
|
|
|
]
|
2024-09-13 21:14:14 +10:00
|
|
|
export const SEGMENT_BODIES_PLUS_PROFILE_START = [
|
|
|
|
...SEGMENT_BODIES,
|
|
|
|
PROFILE_START,
|
|
|
|
]
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-08-22 16:08:49 -04:00
|
|
|
type Vec3Array = [number, number, number]
|
|
|
|
|
2024-02-14 08:03:20 +11:00
|
|
|
// This singleton Class is responsible for all of the things the user sees and interacts with.
|
|
|
|
// That mostly mean sketch elements.
|
|
|
|
// Cameras, controls, raycasters, etc are handled by sceneInfra
|
2024-03-22 16:55:30 +11:00
|
|
|
export class SceneEntities {
|
|
|
|
engineCommandManager: EngineCommandManager
|
2024-02-11 12:59:00 +11:00
|
|
|
scene: Scene
|
|
|
|
activeSegments: { [key: string]: Group } = {}
|
|
|
|
intersectionPlane: Mesh | null = null
|
|
|
|
axisGroup: Group | null = null
|
2024-11-22 11:05:04 -05:00
|
|
|
draftPointGroups: Group[] = []
|
2024-02-11 12:59:00 +11:00
|
|
|
currentSketchQuaternion: Quaternion | null = null
|
2024-03-22 16:55:30 +11:00
|
|
|
constructor(engineCommandManager: EngineCommandManager) {
|
|
|
|
this.engineCommandManager = engineCommandManager
|
2024-02-14 08:03:20 +11:00
|
|
|
this.scene = sceneInfra?.scene
|
2024-02-26 19:53:44 +11:00
|
|
|
sceneInfra?.camControls.subscribeToCamChange(this.onCamChange)
|
2024-04-03 13:22:56 +11:00
|
|
|
window.addEventListener('resize', this.onWindowResize)
|
2024-11-22 11:05:04 -05:00
|
|
|
this.createIntersectionPlane()
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
2024-04-03 13:22:56 +11:00
|
|
|
onWindowResize = () => {
|
|
|
|
this.onCamChange()
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
onCamChange = () => {
|
2024-02-26 19:53:44 +11:00
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
2024-05-24 20:54:42 +10:00
|
|
|
const callbacks: (() => SegmentOverlayPayload | null)[] = []
|
|
|
|
Object.values(this.activeSegments).forEach((segment, index) => {
|
2024-02-11 12:59:00 +11:00
|
|
|
const factor =
|
2024-03-01 06:55:49 +11:00
|
|
|
(sceneInfra.camControls.camera instanceof OrthographicCamera
|
2024-02-11 12:59:00 +11:00
|
|
|
? orthoFactor
|
2024-03-01 06:55:49 +11:00
|
|
|
: perspScale(sceneInfra.camControls.camera, segment)) /
|
|
|
|
sceneInfra._baseUnitMultiplier
|
2024-09-23 22:42:51 +10:00
|
|
|
let input: SegmentInputs = {
|
2024-09-13 21:14:14 +10:00
|
|
|
type: 'straight-segment',
|
|
|
|
from: segment.userData.from,
|
|
|
|
to: segment.userData.to,
|
2024-09-23 22:42:51 +10:00
|
|
|
}
|
2024-09-13 21:14:14 +10:00
|
|
|
let update: SegmentUtils['update'] | null = null
|
2024-02-11 12:59:00 +11:00
|
|
|
if (
|
|
|
|
segment.userData.from &&
|
|
|
|
segment.userData.to &&
|
|
|
|
segment.userData.type === STRAIGHT_SEGMENT
|
|
|
|
) {
|
2024-09-13 21:14:14 +10:00
|
|
|
update = segmentUtils.straight.update
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
if (
|
|
|
|
segment.userData.from &&
|
|
|
|
segment.userData.to &&
|
|
|
|
segment.userData.prevSegment &&
|
|
|
|
segment.userData.type === TANGENTIAL_ARC_TO_SEGMENT
|
|
|
|
) {
|
2024-09-13 21:14:14 +10:00
|
|
|
update = segmentUtils.tangentialArcTo.update
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
if (
|
|
|
|
segment.userData.from &&
|
|
|
|
segment.userData.center &&
|
|
|
|
segment.userData.radius &&
|
|
|
|
segment.userData.type === CIRCLE_SEGMENT
|
|
|
|
) {
|
|
|
|
update = segmentUtils.circle.update
|
|
|
|
input = {
|
|
|
|
type: 'arc-segment',
|
|
|
|
from: segment.userData.from,
|
|
|
|
center: segment.userData.center,
|
|
|
|
radius: segment.userData.radius,
|
|
|
|
}
|
|
|
|
}
|
2025-02-15 00:57:04 +11:00
|
|
|
if (
|
|
|
|
segment.userData.p1 &&
|
|
|
|
segment.userData.p2 &&
|
|
|
|
segment.userData.p3 &&
|
|
|
|
segment.userData.type === CIRCLE_THREE_POINT_SEGMENT
|
|
|
|
) {
|
|
|
|
update = segmentUtils.circleThreePoint.update
|
|
|
|
input = {
|
|
|
|
type: 'circle-three-point-segment',
|
|
|
|
p1: segment.userData.p1,
|
|
|
|
p2: segment.userData.p2,
|
|
|
|
p3: segment.userData.p3,
|
|
|
|
}
|
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
|
2024-09-13 21:14:14 +10:00
|
|
|
const callBack = update?.({
|
|
|
|
prevSegment: segment.userData.prevSegment,
|
|
|
|
input,
|
|
|
|
group: segment,
|
|
|
|
scale: factor,
|
|
|
|
sceneInfra,
|
|
|
|
})
|
|
|
|
callBack && !err(callBack) && callbacks.push(callBack)
|
2024-03-02 08:48:30 +11:00
|
|
|
if (segment.name === PROFILE_START) {
|
|
|
|
segment.scale.set(factor, factor, factor)
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
|
|
|
if (this.axisGroup) {
|
|
|
|
const factor =
|
2024-02-26 19:53:44 +11:00
|
|
|
sceneInfra.camControls.camera instanceof OrthographicCamera
|
2024-02-11 12:59:00 +11:00
|
|
|
? orthoFactor
|
2024-02-26 19:53:44 +11:00
|
|
|
: perspScale(sceneInfra.camControls.camera, this.axisGroup)
|
2024-02-11 12:59:00 +11:00
|
|
|
const x = this.axisGroup.getObjectByName(X_AXIS)
|
2024-03-01 06:55:49 +11:00
|
|
|
x?.scale.set(1, factor / sceneInfra._baseUnitMultiplier, 1)
|
2024-02-11 12:59:00 +11:00
|
|
|
const y = this.axisGroup.getObjectByName(Y_AXIS)
|
2024-03-01 06:55:49 +11:00
|
|
|
y?.scale.set(factor / sceneInfra._baseUnitMultiplier, 1, 1)
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-05-24 20:54:42 +10:00
|
|
|
sceneInfra.overlayCallbacks(callbacks)
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
createIntersectionPlane() {
|
2024-02-26 19:53:44 +11:00
|
|
|
if (sceneInfra.scene.getObjectByName(RAYCASTABLE_PLANE)) {
|
|
|
|
console.warn('createIntersectionPlane called when it already exists')
|
|
|
|
return
|
|
|
|
}
|
2024-03-22 10:23:04 +11:00
|
|
|
const hundredM = 100_0000
|
2024-02-26 19:53:44 +11:00
|
|
|
const planeGeometry = new PlaneGeometry(hundredM, hundredM)
|
2024-02-11 12:59:00 +11:00
|
|
|
const planeMaterial = new MeshBasicMaterial({
|
|
|
|
color: 0xff0000,
|
|
|
|
side: DoubleSide,
|
|
|
|
transparent: true,
|
|
|
|
opacity: 0.5,
|
|
|
|
})
|
|
|
|
this.intersectionPlane = new Mesh(planeGeometry, planeMaterial)
|
|
|
|
this.intersectionPlane.userData = { type: RAYCASTABLE_PLANE }
|
|
|
|
this.intersectionPlane.name = RAYCASTABLE_PLANE
|
|
|
|
this.intersectionPlane.layers.set(INTERSECTION_PLANE_LAYER)
|
|
|
|
this.scene.add(this.intersectionPlane)
|
|
|
|
}
|
2024-03-22 10:23:04 +11:00
|
|
|
createSketchAxis(
|
|
|
|
sketchPathToNode: PathToNode,
|
|
|
|
forward: [number, number, number],
|
|
|
|
up: [number, number, number],
|
|
|
|
sketchPosition?: [number, number, number]
|
|
|
|
) {
|
2024-03-01 06:55:49 +11:00
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
2024-02-11 12:59:00 +11:00
|
|
|
const baseXColor = 0x000055
|
|
|
|
const baseYColor = 0x550000
|
2024-04-22 20:14:06 +10:00
|
|
|
const axisPixelWidth = 1.6
|
|
|
|
const xAxisGeometry = new BoxGeometry(100000, axisPixelWidth, 0.01)
|
|
|
|
const yAxisGeometry = new BoxGeometry(axisPixelWidth, 100000, 0.01)
|
2024-02-11 12:59:00 +11:00
|
|
|
const xAxisMaterial = new MeshBasicMaterial({
|
|
|
|
color: baseXColor,
|
|
|
|
depthTest: false,
|
|
|
|
})
|
|
|
|
const yAxisMaterial = new MeshBasicMaterial({
|
|
|
|
color: baseYColor,
|
|
|
|
depthTest: false,
|
|
|
|
})
|
|
|
|
const xAxisMesh = new Mesh(xAxisGeometry, xAxisMaterial)
|
|
|
|
const yAxisMesh = new Mesh(yAxisGeometry, yAxisMaterial)
|
|
|
|
xAxisMesh.renderOrder = -2
|
|
|
|
yAxisMesh.renderOrder = -1
|
|
|
|
xAxisMesh.userData = {
|
|
|
|
type: X_AXIS,
|
|
|
|
baseColor: baseXColor,
|
|
|
|
isSelected: false,
|
|
|
|
}
|
|
|
|
yAxisMesh.userData = {
|
|
|
|
type: Y_AXIS,
|
|
|
|
baseColor: baseYColor,
|
|
|
|
isSelected: false,
|
|
|
|
}
|
|
|
|
xAxisMesh.name = X_AXIS
|
|
|
|
yAxisMesh.name = Y_AXIS
|
|
|
|
|
|
|
|
this.axisGroup = new Group()
|
|
|
|
const gridHelper = createGridHelper({ size: 100, divisions: 10 })
|
2024-02-26 19:53:44 +11:00
|
|
|
gridHelper.position.z = -0.01
|
2024-02-11 12:59:00 +11:00
|
|
|
gridHelper.renderOrder = -3 // is this working?
|
2024-02-17 07:04:24 +11:00
|
|
|
gridHelper.name = 'gridHelper'
|
|
|
|
const sceneScale = getSceneScale(
|
2024-02-26 19:53:44 +11:00
|
|
|
sceneInfra.camControls.camera,
|
|
|
|
sceneInfra.camControls.target
|
2024-02-17 07:04:24 +11:00
|
|
|
)
|
|
|
|
gridHelper.scale.set(sceneScale, sceneScale, sceneScale)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
|
|
|
const factor =
|
|
|
|
sceneInfra.camControls.camera instanceof OrthographicCamera
|
|
|
|
? orthoFactor
|
|
|
|
: perspScale(sceneInfra.camControls.camera, this.axisGroup)
|
|
|
|
xAxisMesh?.scale.set(1, factor / sceneInfra._baseUnitMultiplier, 1)
|
|
|
|
yAxisMesh?.scale.set(factor / sceneInfra._baseUnitMultiplier, 1, 1)
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
this.axisGroup.add(xAxisMesh, yAxisMesh, gridHelper)
|
|
|
|
this.currentSketchQuaternion &&
|
|
|
|
this.axisGroup.setRotationFromQuaternion(this.currentSketchQuaternion)
|
|
|
|
|
|
|
|
this.axisGroup.userData = { type: AXIS_GROUP }
|
2024-02-17 07:04:24 +11:00
|
|
|
this.axisGroup.name = AXIS_GROUP
|
2024-02-11 12:59:00 +11:00
|
|
|
this.axisGroup.layers.set(SKETCH_LAYER)
|
|
|
|
this.axisGroup.traverse((child) => {
|
|
|
|
child.layers.set(SKETCH_LAYER)
|
|
|
|
})
|
|
|
|
|
2024-03-22 10:23:04 +11:00
|
|
|
const quat = quaternionFromUpNForward(
|
|
|
|
new Vector3(...up),
|
|
|
|
new Vector3(...forward)
|
2024-02-11 12:59:00 +11:00
|
|
|
)
|
|
|
|
this.axisGroup.setRotationFromQuaternion(quat)
|
2024-03-22 10:23:04 +11:00
|
|
|
sketchPosition && this.axisGroup.position.set(...sketchPosition)
|
2024-02-11 12:59:00 +11:00
|
|
|
this.scene.add(this.axisGroup)
|
|
|
|
}
|
2024-10-31 07:04:38 -07:00
|
|
|
getDraftPoint() {
|
|
|
|
return this.scene.getObjectByName(DRAFT_POINT)
|
|
|
|
}
|
|
|
|
createDraftPoint({ point, group }: { point: Vector2; group: Group }) {
|
|
|
|
const dummy = new Mesh()
|
|
|
|
dummy.position.set(0, 0, 0)
|
|
|
|
const scale = sceneInfra.getClientSceneScaleFactor(dummy)
|
|
|
|
|
|
|
|
const draftPoint = createProfileStartHandle({
|
|
|
|
isDraft: true,
|
|
|
|
from: [point.x, point.y],
|
|
|
|
scale,
|
|
|
|
theme: sceneInfra._theme,
|
2025-02-15 00:57:04 +11:00
|
|
|
// default is 12, this makes the draft point pop a bit more,
|
|
|
|
// especially when snapping to the startProfileAt handle as it's it was the exact same size
|
|
|
|
size: 16,
|
2024-10-31 07:04:38 -07:00
|
|
|
})
|
|
|
|
draftPoint.layers.set(SKETCH_LAYER)
|
|
|
|
group.add(draftPoint)
|
|
|
|
}
|
2024-11-22 11:05:04 -05:00
|
|
|
|
2024-10-31 07:04:38 -07:00
|
|
|
removeDraftPoint() {
|
|
|
|
const draftPoint = this.getDraftPoint()
|
|
|
|
if (draftPoint) draftPoint.removeFromParent()
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-07-30 14:16:53 -04:00
|
|
|
setupNoPointsListener({
|
|
|
|
sketchDetails,
|
|
|
|
afterClick,
|
2025-02-15 00:57:04 +11:00
|
|
|
currentTool,
|
2024-07-30 14:16:53 -04:00
|
|
|
}: {
|
|
|
|
sketchDetails: SketchDetails
|
2025-02-15 00:57:04 +11:00
|
|
|
currentTool: SketchTool
|
|
|
|
afterClick: (
|
|
|
|
args: OnClickCallbackArgs,
|
|
|
|
updatedPaths: {
|
|
|
|
sketchNodePaths: PathToNode[]
|
|
|
|
sketchEntryNodePath: PathToNode
|
|
|
|
}
|
|
|
|
) => void
|
2024-07-30 14:16:53 -04:00
|
|
|
}) {
|
2024-10-31 07:04:38 -07:00
|
|
|
// TODO: Consolidate shared logic between this and setupSketch
|
|
|
|
// Which should just fire when the sketch mode is entered,
|
|
|
|
// instead of in these two separate XState states.
|
2024-11-22 11:05:04 -05:00
|
|
|
|
2024-10-31 07:04:38 -07:00
|
|
|
const draftPointGroup = new Group()
|
2024-11-22 11:05:04 -05:00
|
|
|
this.draftPointGroups.push(draftPointGroup)
|
2024-10-31 07:04:38 -07:00
|
|
|
draftPointGroup.name = DRAFT_POINT_GROUP
|
|
|
|
sketchDetails.origin &&
|
|
|
|
draftPointGroup.position.set(...sketchDetails.origin)
|
|
|
|
if (!(sketchDetails.yAxis && sketchDetails)) {
|
|
|
|
console.error('No sketch quaternion or sketch details found')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.currentSketchQuaternion = quaternionFromUpNForward(
|
|
|
|
new Vector3(...sketchDetails.yAxis),
|
|
|
|
new Vector3(...sketchDetails.zAxis)
|
|
|
|
)
|
|
|
|
draftPointGroup.setRotationFromQuaternion(this.currentSketchQuaternion)
|
|
|
|
this.scene.add(draftPointGroup)
|
|
|
|
|
2024-07-30 14:16:53 -04:00
|
|
|
const quaternion = quaternionFromUpNForward(
|
|
|
|
new Vector3(...sketchDetails.yAxis),
|
|
|
|
new Vector3(...sketchDetails.zAxis)
|
|
|
|
)
|
|
|
|
|
|
|
|
// Position the click raycast plane
|
2024-10-31 07:04:38 -07:00
|
|
|
this.intersectionPlane!.setRotationFromQuaternion(quaternion)
|
|
|
|
this.intersectionPlane!.position.copy(
|
|
|
|
new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
|
|
|
|
)
|
2024-07-30 14:16:53 -04:00
|
|
|
sceneInfra.setCallbacks({
|
2024-10-31 07:04:38 -07:00
|
|
|
onMove: (args) => {
|
|
|
|
if (!args.intersects.length) return
|
|
|
|
const axisIntersection = args.intersects.find(
|
|
|
|
(sceneObject) =>
|
|
|
|
sceneObject.object.name === X_AXIS ||
|
|
|
|
sceneObject.object.name === Y_AXIS
|
|
|
|
)
|
2025-02-15 00:57:04 +11:00
|
|
|
|
|
|
|
const arrowHead = getParentGroup(args.intersects[0].object, [ARROWHEAD])
|
|
|
|
const parent = getParentGroup(
|
|
|
|
args.intersects[0].object,
|
|
|
|
SEGMENT_BODIES_PLUS_PROFILE_START
|
|
|
|
)
|
|
|
|
if (
|
|
|
|
!axisIntersection &&
|
|
|
|
!(
|
|
|
|
parent?.userData?.isLastInProfile &&
|
|
|
|
(arrowHead || parent?.name === PROFILE_START)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return
|
2024-10-31 07:04:38 -07:00
|
|
|
const { intersectionPoint } = args
|
|
|
|
// We're hovering over an axis, so we should show a draft point
|
|
|
|
const snappedPoint = intersectionPoint.twoD.clone()
|
2025-02-15 00:57:04 +11:00
|
|
|
if (axisIntersection?.object.name === X_AXIS) {
|
2024-10-31 07:04:38 -07:00
|
|
|
snappedPoint.setComponent(1, 0)
|
2025-02-15 00:57:04 +11:00
|
|
|
} else if (axisIntersection?.object.name === X_AXIS) {
|
2024-10-31 07:04:38 -07:00
|
|
|
snappedPoint.setComponent(0, 0)
|
2025-02-15 00:57:04 +11:00
|
|
|
} else if (arrowHead) {
|
|
|
|
snappedPoint.set(arrowHead.position.x, arrowHead.position.y)
|
|
|
|
} else if (parent?.name === PROFILE_START) {
|
|
|
|
snappedPoint.set(parent.position.x, parent.position.y)
|
2024-10-31 07:04:38 -07:00
|
|
|
}
|
|
|
|
// Either create a new one or update the existing one
|
|
|
|
const draftPoint = this.getDraftPoint()
|
|
|
|
|
|
|
|
if (!draftPoint) {
|
|
|
|
this.createDraftPoint({
|
|
|
|
point: snappedPoint,
|
|
|
|
group: draftPointGroup,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// Ignore if there are huge jumps in the mouse position,
|
|
|
|
// that is likely a strange behavior
|
|
|
|
if (
|
|
|
|
draftPoint.position.distanceTo(
|
|
|
|
new Vector3(snappedPoint.x, snappedPoint.y, 0)
|
|
|
|
) > 100
|
|
|
|
) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
draftPoint.position.set(snappedPoint.x, snappedPoint.y, 0)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onMouseLeave: () => {
|
|
|
|
this.removeDraftPoint()
|
|
|
|
},
|
2024-07-30 14:16:53 -04:00
|
|
|
onClick: async (args) => {
|
2024-10-31 07:04:38 -07:00
|
|
|
this.removeDraftPoint()
|
2024-07-30 14:16:53 -04:00
|
|
|
if (!args) return
|
2024-10-24 06:26:33 -07:00
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
|
|
|
)
|
|
|
|
if (interaction !== 'none') return
|
2024-07-30 14:16:53 -04:00
|
|
|
if (args.mouseEvent.which !== 1) return
|
|
|
|
const { intersectionPoint } = args
|
2025-02-15 00:57:04 +11:00
|
|
|
if (!intersectionPoint?.twoD || !sketchDetails?.sketchEntryNodePath)
|
|
|
|
return
|
|
|
|
|
|
|
|
const parent = getParentGroup(
|
|
|
|
args?.intersects?.[0]?.object,
|
|
|
|
SEGMENT_BODIES_PLUS_PROFILE_START
|
|
|
|
)
|
|
|
|
if (parent?.userData?.isLastInProfile) {
|
|
|
|
afterClick(args, {
|
|
|
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
|
|
|
sketchEntryNodePath: parent.userData.pathToNode,
|
|
|
|
})
|
|
|
|
return
|
|
|
|
} else if (currentTool === 'tangentialArc') {
|
|
|
|
toast.error(
|
|
|
|
'Tangential Arc must continue an existing profile, please click on the last segment of the profile'
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
2024-10-31 07:04:38 -07:00
|
|
|
|
|
|
|
// Snap to either or both axes
|
|
|
|
// if the click intersects their meshes
|
|
|
|
const yAxisIntersection = args.intersects.find(
|
|
|
|
(sceneObject) => sceneObject.object.name === Y_AXIS
|
|
|
|
)
|
|
|
|
const xAxisIntersection = args.intersects.find(
|
|
|
|
(sceneObject) => sceneObject.object.name === X_AXIS
|
|
|
|
)
|
|
|
|
|
|
|
|
const snappedClickPoint = {
|
|
|
|
x: yAxisIntersection ? 0 : intersectionPoint.twoD.x,
|
|
|
|
y: xAxisIntersection ? 0 : intersectionPoint.twoD.y,
|
|
|
|
}
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const inserted = insertNewStartProfileAt(
|
2024-07-30 14:16:53 -04:00
|
|
|
kclManager.ast,
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchDetails.sketchEntryNodePath,
|
|
|
|
sketchDetails.sketchNodePaths,
|
|
|
|
sketchDetails.planeNodePath,
|
|
|
|
[snappedClickPoint.x, snappedClickPoint.y],
|
|
|
|
'end'
|
2024-07-30 14:16:53 -04:00
|
|
|
)
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
if (trap(inserted)) return
|
|
|
|
const { modifiedAst } = inserted
|
2024-07-30 14:16:53 -04:00
|
|
|
|
|
|
|
await kclManager.updateAst(modifiedAst, false)
|
2024-11-16 16:49:44 -05:00
|
|
|
|
2024-10-31 07:04:38 -07:00
|
|
|
this.scene.remove(draftPointGroup)
|
2024-07-30 14:16:53 -04:00
|
|
|
|
|
|
|
// Now perform the caller-specified action
|
2025-02-15 00:57:04 +11:00
|
|
|
afterClick(args, {
|
|
|
|
sketchNodePaths: inserted.updatedSketchNodePaths,
|
|
|
|
sketchEntryNodePath: inserted.updatedEntryNodePath,
|
|
|
|
})
|
2024-07-30 14:16:53 -04:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
async setupSketch({
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
2024-03-22 10:23:04 +11:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position,
|
2024-03-25 15:20:43 +11:00
|
|
|
maybeModdedAst,
|
|
|
|
draftExpressionsIndices,
|
2024-06-22 04:49:31 -04:00
|
|
|
selectionRanges,
|
2024-02-11 12:59:00 +11:00
|
|
|
}: {
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: PathToNode
|
|
|
|
sketchNodePaths: PathToNode[]
|
2024-10-30 16:52:17 -04:00
|
|
|
maybeModdedAst: Node<Program>
|
2024-03-25 15:20:43 +11:00
|
|
|
draftExpressionsIndices?: { start: number; end: number }
|
2024-03-22 10:23:04 +11:00
|
|
|
forward: [number, number, number]
|
|
|
|
up: [number, number, number]
|
|
|
|
position?: [number, number, number]
|
2024-06-22 04:49:31 -04:00
|
|
|
selectionRanges?: Selections
|
2024-03-25 15:20:43 +11:00
|
|
|
}): Promise<{
|
2024-10-30 16:52:17 -04:00
|
|
|
truncatedAst: Node<Program>
|
2024-03-25 15:20:43 +11:00
|
|
|
variableDeclarationName: string
|
|
|
|
}> {
|
2025-02-15 00:57:04 +11:00
|
|
|
this.createIntersectionPlane()
|
|
|
|
|
|
|
|
const prepared = this.prepareTruncatedAst(sketchNodePaths, maybeModdedAst)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (err(prepared)) return Promise.reject(prepared)
|
2025-02-12 10:22:56 +13:00
|
|
|
const { truncatedAst, variableDeclarationName } = prepared
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-10-09 19:38:40 -04:00
|
|
|
const { execState } = await executeAst({
|
2024-02-11 12:59:00 +11:00
|
|
|
ast: truncatedAst,
|
2024-03-22 16:55:30 +11:00
|
|
|
engineCommandManager: this.engineCommandManager,
|
2025-03-15 10:08:39 -07:00
|
|
|
rustContext,
|
2025-02-12 10:22:56 +13:00
|
|
|
isMock: true,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
2025-02-15 00:57:04 +11:00
|
|
|
const sketchesInfo = getSketchesInfo({
|
|
|
|
sketchNodePaths,
|
2024-04-03 13:22:56 +11:00
|
|
|
ast: maybeModdedAst,
|
2025-02-12 10:22:56 +13:00
|
|
|
variables: execState.variables,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
const group = new Group()
|
2024-03-22 10:23:04 +11:00
|
|
|
position && group.position.set(...position)
|
2024-02-11 12:59:00 +11:00
|
|
|
group.userData = {
|
|
|
|
type: SKETCH_GROUP_SEGMENTS,
|
2025-02-15 00:57:04 +11:00
|
|
|
pathToNode: sketchEntryNodePath,
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
const dummy = new Mesh()
|
2024-06-21 19:54:18 -07:00
|
|
|
// TODO: When we actually have sketch positions and rotations we can use them here.
|
|
|
|
dummy.position.set(0, 0, 0)
|
2024-10-31 07:04:38 -07:00
|
|
|
const scale = sceneInfra.getClientSceneScaleFactor(dummy)
|
2024-03-02 08:48:30 +11:00
|
|
|
|
2024-05-24 20:54:42 +10:00
|
|
|
const callbacks: (() => SegmentOverlayPayload | null)[] = []
|
2025-02-15 00:57:04 +11:00
|
|
|
|
|
|
|
for (const sketchInfo of sketchesInfo) {
|
|
|
|
const { sketch } = sketchInfo
|
|
|
|
const segPathToNode = getNodePathFromSourceRange(
|
2024-04-19 11:56:21 -04:00
|
|
|
maybeModdedAst,
|
2025-02-15 00:57:04 +11:00
|
|
|
sourceRangeFromRust(sketch.start.__geoMeta.sourceRange)
|
2024-02-11 12:59:00 +11:00
|
|
|
)
|
2024-12-16 10:34:11 -05:00
|
|
|
if (
|
2025-02-15 00:57:04 +11:00
|
|
|
['Circle', 'CircleThreePoint'].includes(sketch?.paths?.[0]?.type) ===
|
|
|
|
false
|
2024-12-16 10:34:11 -05:00
|
|
|
) {
|
2025-02-15 00:57:04 +11:00
|
|
|
const _profileStart = createProfileStartHandle({
|
|
|
|
from: sketch.start.from,
|
|
|
|
id: sketch.start.__geoMeta.id,
|
|
|
|
pathToNode: segPathToNode,
|
|
|
|
scale,
|
|
|
|
theme: sceneInfra._theme,
|
|
|
|
isDraft: false,
|
|
|
|
})
|
|
|
|
_profileStart.layers.set(SKETCH_LAYER)
|
|
|
|
_profileStart.traverse((child) => {
|
|
|
|
child.layers.set(SKETCH_LAYER)
|
|
|
|
})
|
|
|
|
if (!sketch.paths.length) {
|
|
|
|
_profileStart.userData.isLastInProfile = true
|
|
|
|
}
|
|
|
|
group.add(_profileStart)
|
|
|
|
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
|
|
|
|
}
|
|
|
|
sketch.paths.forEach((segment, index) => {
|
|
|
|
const isLastInProfile =
|
|
|
|
index === sketch.paths.length - 1 && segment.type !== 'Circle'
|
|
|
|
let segPathToNode = getNodePathFromSourceRange(
|
2024-04-19 11:56:21 -04:00
|
|
|
maybeModdedAst,
|
2024-12-16 10:34:11 -05:00
|
|
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
2024-12-06 13:57:31 +13:00
|
|
|
)
|
2025-02-15 00:57:04 +11:00
|
|
|
if (
|
|
|
|
draftExpressionsIndices &&
|
|
|
|
(sketch.paths[index - 1] || sketch.start)
|
|
|
|
) {
|
|
|
|
const previousSegment = sketch.paths[index - 1] || sketch.start
|
|
|
|
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
|
|
|
maybeModdedAst,
|
|
|
|
sourceRangeFromRust(previousSegment.__geoMeta.sourceRange)
|
|
|
|
)
|
|
|
|
const bodyIndex = previousSegmentPathToNode[1][0]
|
|
|
|
segPathToNode = getNodePathFromSourceRange(
|
|
|
|
truncatedAst,
|
|
|
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
|
|
|
)
|
|
|
|
segPathToNode[1][0] = bodyIndex
|
|
|
|
}
|
|
|
|
const isDraftSegment =
|
|
|
|
draftExpressionsIndices &&
|
|
|
|
index <= draftExpressionsIndices.end &&
|
|
|
|
index >= draftExpressionsIndices.start &&
|
|
|
|
// the following line is not robust to sketches defined within a function
|
|
|
|
sketchInfo.pathToNode[1][0] === sketchEntryNodePath[1][0]
|
|
|
|
const isSelected = selectionRanges?.graphSelections.some((selection) =>
|
|
|
|
isOverlap(
|
|
|
|
selection?.codeRef?.range,
|
|
|
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
|
|
|
)
|
2024-12-14 09:57:33 +11:00
|
|
|
)
|
2024-12-17 15:12:18 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
let seg: Group
|
|
|
|
const _node1 = getNodeFromPath<Node<CallExpression | CallExpressionKw>>(
|
|
|
|
maybeModdedAst,
|
|
|
|
segPathToNode,
|
|
|
|
['CallExpression', 'CallExpressionKw']
|
|
|
|
)
|
|
|
|
if (err(_node1)) return
|
|
|
|
const callExpName = _node1.node?.callee?.name
|
|
|
|
|
|
|
|
const initSegment =
|
|
|
|
segment.type === 'TangentialArcTo'
|
|
|
|
? segmentUtils.tangentialArcTo.init
|
|
|
|
: segment.type === 'Circle'
|
|
|
|
? segmentUtils.circle.init
|
|
|
|
: segment.type === 'CircleThreePoint'
|
|
|
|
? segmentUtils.circleThreePoint.init
|
|
|
|
: segmentUtils.straight.init
|
|
|
|
const input: SegmentInputs =
|
|
|
|
segment.type === 'Circle'
|
|
|
|
? {
|
|
|
|
type: 'arc-segment',
|
|
|
|
from: segment.from,
|
|
|
|
center: segment.center,
|
|
|
|
radius: segment.radius,
|
|
|
|
}
|
|
|
|
: segment.type === 'CircleThreePoint'
|
|
|
|
? {
|
|
|
|
type: 'circle-three-point-segment',
|
|
|
|
p1: segment.p1,
|
|
|
|
p2: segment.p2,
|
|
|
|
p3: segment.p3,
|
|
|
|
}
|
|
|
|
: {
|
|
|
|
type: 'straight-segment',
|
|
|
|
from: segment.from,
|
|
|
|
to: segment.to,
|
|
|
|
}
|
|
|
|
const startRange = _node1.node.start
|
|
|
|
const endRange = _node1.node.end
|
|
|
|
const sourceRange: SourceRange = [startRange, endRange, 0]
|
|
|
|
const selection: Selections = computeSelectionFromSourceRangeAndAST(
|
|
|
|
sourceRange,
|
|
|
|
maybeModdedAst
|
|
|
|
)
|
|
|
|
const result = initSegment({
|
|
|
|
prevSegment: sketch.paths[index - 1],
|
|
|
|
callExpName,
|
|
|
|
input,
|
|
|
|
id: segment.__geoMeta.id,
|
|
|
|
pathToNode: segPathToNode,
|
|
|
|
isDraftSegment,
|
|
|
|
scale,
|
|
|
|
texture: sceneInfra.extraSegmentTexture,
|
|
|
|
theme: sceneInfra._theme,
|
|
|
|
isSelected,
|
|
|
|
sceneInfra,
|
|
|
|
selection,
|
|
|
|
})
|
|
|
|
if (err(result)) return
|
|
|
|
const { group: _group, updateOverlaysCallback } = result
|
|
|
|
seg = _group
|
|
|
|
if (isLastInProfile) {
|
|
|
|
seg.userData.isLastInProfile = true
|
|
|
|
}
|
|
|
|
callbacks.push(updateOverlaysCallback)
|
|
|
|
seg.layers.set(SKETCH_LAYER)
|
|
|
|
seg.traverse((child) => {
|
|
|
|
child.layers.set(SKETCH_LAYER)
|
|
|
|
})
|
2024-12-17 15:12:18 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
group.add(seg)
|
|
|
|
this.activeSegments[JSON.stringify(segPathToNode)] = seg
|
2024-12-16 10:34:11 -05:00
|
|
|
})
|
2025-02-15 00:57:04 +11:00
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-03-22 10:23:04 +11:00
|
|
|
this.currentSketchQuaternion = quaternionFromUpNForward(
|
|
|
|
new Vector3(...up),
|
|
|
|
new Vector3(...forward)
|
|
|
|
)
|
2024-02-11 12:59:00 +11:00
|
|
|
group.setRotationFromQuaternion(this.currentSketchQuaternion)
|
|
|
|
this.intersectionPlane &&
|
|
|
|
this.intersectionPlane.setRotationFromQuaternion(
|
|
|
|
this.currentSketchQuaternion
|
|
|
|
)
|
2024-03-22 10:23:04 +11:00
|
|
|
this.intersectionPlane &&
|
|
|
|
position &&
|
|
|
|
this.intersectionPlane.position.set(...position)
|
2024-02-11 12:59:00 +11:00
|
|
|
this.scene.add(group)
|
2024-03-25 15:20:43 +11:00
|
|
|
sceneInfra.camControls.enableRotate = false
|
2024-05-24 20:54:42 +10:00
|
|
|
sceneInfra.overlayCallbacks(callbacks)
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-03-25 15:20:43 +11:00
|
|
|
return {
|
|
|
|
truncatedAst,
|
|
|
|
variableDeclarationName,
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
updateAstAndRejigSketch = async (
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: PathToNode,
|
|
|
|
sketchNodePaths: PathToNode[],
|
|
|
|
planeNodePath: PathToNode,
|
|
|
|
modifiedAst: Node<Program> | Error,
|
2024-03-22 10:23:04 +11:00
|
|
|
forward: [number, number, number],
|
|
|
|
up: [number, number, number],
|
2024-05-02 22:26:29 +10:00
|
|
|
origin: [number, number, number]
|
2024-02-11 12:59:00 +11:00
|
|
|
) => {
|
2025-02-15 00:57:04 +11:00
|
|
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
2024-06-24 11:45:40 -04:00
|
|
|
const nextAst = await kclManager.updateAst(modifiedAst, false)
|
2025-02-15 00:57:04 +11:00
|
|
|
this.tearDownSketch({ removeAxis: false })
|
2024-04-03 13:22:56 +11:00
|
|
|
sceneInfra.resetMouseListeners()
|
2024-03-25 15:20:43 +11:00
|
|
|
await this.setupSketch({
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
2024-03-25 15:20:43 +11:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: origin,
|
2024-06-24 11:45:40 -04:00
|
|
|
maybeModdedAst: nextAst.newAst,
|
2024-03-25 15:20:43 +11:00
|
|
|
})
|
2024-04-03 13:22:56 +11:00
|
|
|
this.setupSketchIdleCallbacks({
|
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: origin,
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
|
|
|
planeNodePath,
|
2024-04-03 13:22:56 +11:00
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
return nextAst
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-11-16 16:49:44 -05:00
|
|
|
setupDraftSegment = async (
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: PathToNode,
|
|
|
|
sketchNodePaths: PathToNode[],
|
|
|
|
planeNodePath: PathToNode,
|
2024-03-22 10:23:04 +11:00
|
|
|
forward: [number, number, number],
|
2024-03-25 15:20:43 +11:00
|
|
|
up: [number, number, number],
|
|
|
|
origin: [number, number, number],
|
|
|
|
segmentName: 'line' | 'tangentialArcTo' = 'line',
|
|
|
|
shouldTearDown = true
|
2024-03-22 10:23:04 +11:00
|
|
|
) => {
|
2024-07-25 20:11:46 -04:00
|
|
|
const _ast = structuredClone(kclManager.ast)
|
2024-03-25 15:20:43 +11:00
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node1 = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath || [],
|
2024-06-24 11:45:40 -04:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (trap(_node1)) return Promise.reject(_node1)
|
2024-12-07 07:16:04 +13:00
|
|
|
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
const sg = sketchFromKclValue(
|
2025-02-12 10:22:56 +13:00
|
|
|
kclManager.variables[variableDeclarationName],
|
2024-03-25 15:20:43 +11:00
|
|
|
variableDeclarationName
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
)
|
2024-09-13 21:14:14 +10:00
|
|
|
if (err(sg)) return Promise.reject(sg)
|
2024-10-23 12:42:54 -05:00
|
|
|
const lastSeg = sg?.paths?.slice(-1)[0] || sg.start
|
2024-03-25 15:20:43 +11:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const index = sg.paths.length // because we've added a new segment that's not in the memory yet, no need for `.length -1`
|
2024-04-03 13:22:56 +11:00
|
|
|
const mod = addNewSketchLn({
|
|
|
|
node: _ast,
|
2025-02-12 10:22:56 +13:00
|
|
|
variables: kclManager.variables,
|
2024-09-13 21:14:14 +10:00
|
|
|
input: {
|
|
|
|
type: 'straight-segment',
|
|
|
|
to: lastSeg.to,
|
|
|
|
from: lastSeg.to,
|
|
|
|
},
|
2024-03-25 15:20:43 +11:00
|
|
|
fnName: segmentName,
|
2025-02-15 00:57:04 +11:00
|
|
|
pathToNode: sketchEntryNodePath,
|
2024-04-03 13:22:56 +11:00
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(mod)) return Promise.reject(mod)
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(mod.modifiedAst))
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
|
|
|
const modifiedAst = pResult.program
|
2024-03-25 15:20:43 +11:00
|
|
|
|
|
|
|
const draftExpressionsIndices = { start: index, end: index }
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
if (shouldTearDown) this.tearDownSketch({ removeAxis: false })
|
2024-04-03 13:22:56 +11:00
|
|
|
sceneInfra.resetMouseListeners()
|
2024-06-29 18:10:07 -07:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const { truncatedAst } = await this.setupSketch({
|
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
2025-02-12 10:22:56 +13:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: origin,
|
|
|
|
maybeModdedAst: modifiedAst,
|
|
|
|
draftExpressionsIndices,
|
|
|
|
})
|
2024-03-25 15:20:43 +11:00
|
|
|
sceneInfra.setCallbacks({
|
|
|
|
onClick: async (args) => {
|
|
|
|
if (!args) return
|
2024-10-24 06:26:33 -07:00
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
|
|
|
)
|
|
|
|
if (interaction !== 'none') return
|
2024-03-25 15:20:43 +11:00
|
|
|
if (args.mouseEvent.which !== 1) return
|
2024-10-24 06:26:33 -07:00
|
|
|
|
2024-03-25 15:20:43 +11:00
|
|
|
const { intersectionPoint } = args
|
|
|
|
let intersection2d = intersectionPoint?.twoD
|
2024-10-31 07:04:38 -07:00
|
|
|
const intersectsProfileStart = args.intersects
|
2024-03-25 15:20:43 +11:00
|
|
|
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
|
2025-02-15 00:57:04 +11:00
|
|
|
.find(isGroupStartProfileForCurrentProfile(sketchEntryNodePath))
|
2024-03-25 15:20:43 +11:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
let modifiedAst: Program | Error = structuredClone(kclManager.ast)
|
|
|
|
|
|
|
|
const sketch = sketchFromPathToNode({
|
|
|
|
pathToNode: sketchEntryNodePath,
|
|
|
|
ast: kclManager.ast,
|
|
|
|
variables: kclManager.variables,
|
|
|
|
})
|
|
|
|
if (err(sketch)) return Promise.reject(sketch)
|
|
|
|
if (!sketch) return Promise.reject(new Error('No sketch found'))
|
2024-10-31 07:04:38 -07:00
|
|
|
|
|
|
|
// Snapping logic for the profile start handle
|
|
|
|
if (intersectsProfileStart) {
|
2024-10-23 12:42:54 -05:00
|
|
|
const lastSegment = sketch.paths.slice(-1)[0]
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
const originCoords = createArrayExpression([
|
|
|
|
createCallExpressionStdLib('profileStartX', [
|
|
|
|
createPipeSubstitution(),
|
|
|
|
]),
|
|
|
|
createCallExpressionStdLib('profileStartY', [
|
|
|
|
createPipeSubstitution(),
|
|
|
|
]),
|
|
|
|
])
|
2024-05-23 00:53:15 -04:00
|
|
|
modifiedAst = addCallExpressionsToPipe({
|
2024-03-25 15:20:43 +11:00
|
|
|
node: kclManager.ast,
|
2025-02-12 10:22:56 +13:00
|
|
|
variables: kclManager.variables,
|
2025-02-15 00:57:04 +11:00
|
|
|
pathToNode: sketchEntryNodePath,
|
2024-05-23 00:53:15 -04:00
|
|
|
expressions: [
|
2025-02-15 00:57:04 +11:00
|
|
|
segmentName === 'tangentialArcTo'
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
? createCallExpressionStdLib('tangentialArcTo', [
|
|
|
|
originCoords,
|
|
|
|
createPipeSubstitution(),
|
|
|
|
])
|
|
|
|
: createCallExpressionStdLibKw('line', null, [
|
2025-02-28 11:50:14 -06:00
|
|
|
createLabeledArg(ARG_END_ABSOLUTE, originCoords),
|
2024-05-23 00:53:15 -04:00
|
|
|
]),
|
|
|
|
],
|
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
2024-05-23 00:53:15 -04:00
|
|
|
modifiedAst = addCloseToPipe({
|
|
|
|
node: modifiedAst,
|
2025-02-12 10:22:56 +13:00
|
|
|
variables: kclManager.variables,
|
2025-02-15 00:57:04 +11:00
|
|
|
pathToNode: sketchEntryNodePath,
|
2024-03-25 15:20:43 +11:00
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
2024-03-25 15:20:43 +11:00
|
|
|
} else if (intersection2d) {
|
2024-10-31 07:04:38 -07:00
|
|
|
const intersectsYAxis = args.intersects.find(
|
|
|
|
(sceneObject) => sceneObject.object.name === Y_AXIS
|
|
|
|
)
|
|
|
|
const intersectsXAxis = args.intersects.find(
|
|
|
|
(sceneObject) => sceneObject.object.name === X_AXIS
|
|
|
|
)
|
|
|
|
|
2024-11-13 09:41:27 -05:00
|
|
|
const lastSegment = sketch.paths.slice(-1)[0] || sketch.start
|
2024-10-31 07:04:38 -07:00
|
|
|
const snappedPoint = {
|
|
|
|
x: intersectsYAxis ? 0 : intersection2d.x,
|
|
|
|
y: intersectsXAxis ? 0 : intersection2d.y,
|
|
|
|
}
|
2024-11-13 09:41:27 -05:00
|
|
|
// Get the angle between the previous segment (or sketch start)'s end and this one's
|
|
|
|
const angle = Math.atan2(
|
|
|
|
snappedPoint.y - lastSegment.to[1],
|
|
|
|
snappedPoint.x - lastSegment.to[0]
|
|
|
|
)
|
|
|
|
|
|
|
|
const isHorizontal =
|
|
|
|
radToDeg(Math.abs(angle)) < ANGLE_SNAP_THRESHOLD_DEGREES ||
|
|
|
|
Math.abs(radToDeg(Math.abs(angle) - Math.PI)) <
|
|
|
|
ANGLE_SNAP_THRESHOLD_DEGREES
|
|
|
|
const isVertical =
|
|
|
|
Math.abs(radToDeg(Math.abs(angle) - Math.PI / 2)) <
|
|
|
|
ANGLE_SNAP_THRESHOLD_DEGREES
|
2024-10-31 07:04:38 -07:00
|
|
|
|
|
|
|
let resolvedFunctionName: ToolTip = 'line'
|
|
|
|
|
|
|
|
// This might need to become its own function if we want more
|
|
|
|
// case-based logic for different segment types
|
2025-02-15 00:57:04 +11:00
|
|
|
if (
|
|
|
|
(lastSegment.type === 'TangentialArcTo' &&
|
|
|
|
segmentName !== 'line') ||
|
|
|
|
segmentName === 'tangentialArcTo'
|
|
|
|
) {
|
2024-10-31 07:04:38 -07:00
|
|
|
resolvedFunctionName = 'tangentialArcTo'
|
2024-11-13 09:41:27 -05:00
|
|
|
} else if (isHorizontal) {
|
|
|
|
// If the angle between is 0 or 180 degrees (+/- the snapping angle), make the line an xLine
|
|
|
|
resolvedFunctionName = 'xLine'
|
|
|
|
} else if (isVertical) {
|
|
|
|
// If the angle between is 90 or 270 degrees (+/- the snapping angle), make the line a yLine
|
|
|
|
resolvedFunctionName = 'yLine'
|
2024-10-31 07:04:38 -07:00
|
|
|
} else if (snappedPoint.x === 0 || snappedPoint.y === 0) {
|
|
|
|
// We consider a point placed on axes or origin to be absolute
|
|
|
|
resolvedFunctionName = 'lineTo'
|
|
|
|
}
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const tmp = addNewSketchLn({
|
2024-03-25 15:20:43 +11:00
|
|
|
node: kclManager.ast,
|
2025-02-12 10:22:56 +13:00
|
|
|
variables: kclManager.variables,
|
2024-09-13 21:14:14 +10:00
|
|
|
input: {
|
|
|
|
type: 'straight-segment',
|
|
|
|
from: [lastSegment.to[0], lastSegment.to[1]],
|
2024-10-31 07:04:38 -07:00
|
|
|
to: [snappedPoint.x, snappedPoint.y],
|
2024-09-13 21:14:14 +10:00
|
|
|
},
|
2024-10-31 07:04:38 -07:00
|
|
|
fnName: resolvedFunctionName,
|
2025-02-15 00:57:04 +11:00
|
|
|
pathToNode: sketchEntryNodePath,
|
2024-06-24 11:45:40 -04:00
|
|
|
})
|
|
|
|
if (trap(tmp)) return Promise.reject(tmp)
|
|
|
|
modifiedAst = tmp.modifiedAst
|
|
|
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
2024-03-25 15:20:43 +11:00
|
|
|
} else {
|
|
|
|
// return early as we didn't modify the ast
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-17 20:18:07 -07:00
|
|
|
await kclManager.executeAstMock(modifiedAst)
|
2024-11-16 16:49:44 -05:00
|
|
|
|
2024-10-31 07:04:38 -07:00
|
|
|
if (intersectsProfileStart) {
|
2025-02-15 00:57:04 +11:00
|
|
|
sceneInfra.modelingSend({ type: 'Close sketch' })
|
2024-05-23 00:53:15 -04:00
|
|
|
} else {
|
2024-11-16 16:49:44 -05:00
|
|
|
await this.setupDraftSegment(
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
|
|
|
planeNodePath,
|
2024-05-23 00:53:15 -04:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
origin,
|
|
|
|
segmentName
|
|
|
|
)
|
|
|
|
}
|
2024-11-16 16:49:44 -05:00
|
|
|
|
|
|
|
await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst)
|
2024-03-25 15:20:43 +11:00
|
|
|
},
|
|
|
|
onMove: (args) => {
|
2025-02-15 00:57:04 +11:00
|
|
|
const expressionIndex = Number(sketchEntryNodePath[1][0])
|
|
|
|
const activeSegmentsInCorrectExpression = Object.values(
|
|
|
|
this.activeSegments
|
|
|
|
).filter((seg) => {
|
|
|
|
return seg.userData.pathToNode[1][0] === expressionIndex
|
|
|
|
})
|
|
|
|
const object =
|
|
|
|
activeSegmentsInCorrectExpression[
|
|
|
|
activeSegmentsInCorrectExpression.length - 1
|
|
|
|
]
|
2024-03-25 15:20:43 +11:00
|
|
|
this.onDragSegment({
|
|
|
|
intersection2d: args.intersectionPoint.twoD,
|
2025-02-15 00:57:04 +11:00
|
|
|
object,
|
2024-03-25 15:20:43 +11:00
|
|
|
intersects: args.intersects,
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchNodePaths,
|
|
|
|
sketchEntryNodePath,
|
|
|
|
planeNodePath,
|
2024-03-25 15:20:43 +11:00
|
|
|
draftInfo: {
|
|
|
|
truncatedAst,
|
|
|
|
variableDeclarationName,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-04-19 11:56:21 -04:00
|
|
|
setupDraftRectangle = async (
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: PathToNode,
|
|
|
|
sketchNodePaths: PathToNode[],
|
|
|
|
planeNodePath: PathToNode,
|
2024-04-19 11:56:21 -04:00
|
|
|
forward: [number, number, number],
|
|
|
|
up: [number, number, number],
|
|
|
|
sketchOrigin: [number, number, number],
|
|
|
|
rectangleOrigin: [x: number, y: number]
|
2025-02-15 00:57:04 +11:00
|
|
|
): Promise<SketchDetailsUpdate | Error> => {
|
2024-07-25 20:11:46 -04:00
|
|
|
let _ast = structuredClone(kclManager.ast)
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
2024-06-24 11:45:40 -04:00
|
|
|
_ast,
|
2025-02-15 00:57:04 +11:00
|
|
|
planeNodePath,
|
|
|
|
'VariableDeclarator'
|
2024-12-14 09:57:33 +11:00
|
|
|
)
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
if (err(varDec)) return varDec
|
|
|
|
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
|
2024-12-14 09:57:33 +11:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const varName = findUniqueName(_ast, 'profile')
|
|
|
|
|
|
|
|
// first create just the variable declaration, as that's
|
|
|
|
// all we want the user to see in the editor
|
|
|
|
const tag = findUniqueName(_ast, 'rectangleSegmentA')
|
|
|
|
const newDeclaration = createVariableDeclaration(
|
|
|
|
varName,
|
|
|
|
createCallExpressionStdLib('startProfileAt', [
|
|
|
|
createArrayExpression([
|
|
|
|
createLiteral(roundOff(rectangleOrigin[0])),
|
|
|
|
createLiteral(roundOff(rectangleOrigin[1])),
|
|
|
|
]),
|
|
|
|
createIdentifier(varDec.node.id.name),
|
|
|
|
])
|
|
|
|
)
|
|
|
|
|
|
|
|
const insertIndex = getInsertIndex(sketchNodePaths, planeNodePath, 'end')
|
|
|
|
|
|
|
|
_ast.body.splice(insertIndex, 0, newDeclaration)
|
|
|
|
const { updatedEntryNodePath, updatedSketchNodePaths } =
|
|
|
|
updateSketchNodePathsWithInsertIndex({
|
|
|
|
insertIndex,
|
|
|
|
insertType: 'end',
|
|
|
|
sketchNodePaths,
|
|
|
|
})
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(_ast))
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
// do a quick mock execution to get the program memory up-to-date
|
|
|
|
await kclManager.executeAstMock(_ast)
|
|
|
|
|
|
|
|
const justCreatedNode = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
|
|
|
updatedEntryNodePath,
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
|
|
|
|
if (trap(justCreatedNode)) return Promise.reject(justCreatedNode)
|
|
|
|
const startProfileAt = justCreatedNode.node?.declaration
|
|
|
|
// than add the rest of the profile so we can "animate" it
|
|
|
|
// as draft segments
|
|
|
|
startProfileAt.init = createPipeExpression([
|
|
|
|
startProfileAt?.init,
|
|
|
|
...getRectangleCallExpressions(rectangleOrigin, tag),
|
|
|
|
])
|
|
|
|
|
|
|
|
const code = recast(_ast)
|
|
|
|
const _recastAst = parse(code)
|
|
|
|
if (trap(_recastAst) || !resultIsOk(_recastAst))
|
|
|
|
return Promise.reject(_recastAst)
|
|
|
|
_ast = _recastAst.program
|
|
|
|
|
2025-02-12 10:22:56 +13:00
|
|
|
const { truncatedAst } = await this.setupSketch({
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: updatedEntryNodePath,
|
|
|
|
sketchNodePaths: updatedSketchNodePaths,
|
2024-04-19 11:56:21 -04:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: sketchOrigin,
|
|
|
|
maybeModdedAst: _ast,
|
|
|
|
draftExpressionsIndices: { start: 0, end: 3 },
|
|
|
|
})
|
|
|
|
|
|
|
|
sceneInfra.setCallbacks({
|
|
|
|
onMove: async (args) => {
|
|
|
|
// Update the width and height of the draft rectangle
|
2025-02-15 00:57:04 +11:00
|
|
|
|
|
|
|
const nodePathWithCorrectedIndexForTruncatedAst =
|
|
|
|
structuredClone(updatedEntryNodePath)
|
|
|
|
nodePathWithCorrectedIndexForTruncatedAst[1][0] =
|
|
|
|
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) -
|
|
|
|
Number(planeNodePath[1][0]) -
|
|
|
|
1
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
2024-04-19 11:56:21 -04:00
|
|
|
truncatedAst,
|
2025-02-15 00:57:04 +11:00
|
|
|
nodePathWithCorrectedIndexForTruncatedAst,
|
2024-04-19 11:56:21 -04:00
|
|
|
'VariableDeclaration'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (trap(_node)) return Promise.reject(_node)
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node?.declaration.init
|
2024-04-19 11:56:21 -04:00
|
|
|
|
|
|
|
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
|
|
|
|
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
|
|
|
|
|
|
|
if (sketchInit.type === 'PipeExpression') {
|
2025-02-15 00:57:04 +11:00
|
|
|
updateRectangleSketch(sketchInit, x, y, tag)
|
2024-04-19 11:56:21 -04:00
|
|
|
}
|
|
|
|
|
2024-10-09 19:38:40 -04:00
|
|
|
const { execState } = await executeAst({
|
2024-04-19 11:56:21 -04:00
|
|
|
ast: truncatedAst,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
2025-03-15 10:08:39 -07:00
|
|
|
rustContext,
|
2025-02-12 10:22:56 +13:00
|
|
|
isMock: true,
|
2024-04-19 11:56:21 -04:00
|
|
|
})
|
2025-02-15 00:57:04 +11:00
|
|
|
const sketch = sketchFromKclValue(execState.variables[varName], varName)
|
2024-09-27 15:44:44 -07:00
|
|
|
if (err(sketch)) return Promise.reject(sketch)
|
2024-10-23 12:42:54 -05:00
|
|
|
const sgPaths = sketch.paths
|
2024-04-19 11:56:21 -04:00
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const varDecIndex = Number(updatedEntryNodePath[1][0])
|
|
|
|
|
|
|
|
this.updateSegment(
|
|
|
|
sketch.start,
|
|
|
|
0,
|
|
|
|
varDecIndex,
|
|
|
|
_ast,
|
|
|
|
orthoFactor,
|
|
|
|
sketch
|
|
|
|
)
|
2024-04-19 11:56:21 -04:00
|
|
|
sgPaths.forEach((seg, index) =>
|
2025-02-15 00:57:04 +11:00
|
|
|
this.updateSegment(seg, index, varDecIndex, _ast, orthoFactor, sketch)
|
2024-04-19 11:56:21 -04:00
|
|
|
)
|
|
|
|
},
|
|
|
|
onClick: async (args) => {
|
2024-10-24 06:26:33 -07:00
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
|
|
|
)
|
|
|
|
if (interaction !== 'none') return
|
2024-04-19 11:56:21 -04:00
|
|
|
// Commit the rectangle to the full AST/code and return to sketch.idle
|
|
|
|
const cornerPoint = args.intersectionPoint?.twoD
|
|
|
|
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
|
|
|
|
|
|
|
const x = roundOff((cornerPoint.x || 0) - rectangleOrigin[0])
|
|
|
|
const y = roundOff((cornerPoint.y || 0) - rectangleOrigin[1])
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
2024-04-19 11:56:21 -04:00
|
|
|
_ast,
|
2025-02-15 00:57:04 +11:00
|
|
|
updatedEntryNodePath,
|
2024-04-19 11:56:21 -04:00
|
|
|
'VariableDeclaration'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
2024-09-13 21:14:14 +10:00
|
|
|
if (trap(_node)) return
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node?.declaration.init
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-11-16 16:49:44 -05:00
|
|
|
if (sketchInit.type !== 'PipeExpression') {
|
|
|
|
return
|
|
|
|
}
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
updateRectangleSketch(sketchInit, x, y, tag)
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-11-16 16:49:44 -05:00
|
|
|
const newCode = recast(_ast)
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(newCode)
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult))
|
|
|
|
return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-11-16 16:49:44 -05:00
|
|
|
// Update the primary AST and unequip the rectangle tool
|
|
|
|
await kclManager.executeAstMock(_ast)
|
2024-12-16 10:34:11 -05:00
|
|
|
sceneInfra.modelingSend({ type: 'Finish rectangle' })
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-11-16 16:49:44 -05:00
|
|
|
// lee: I had this at the bottom of the function, but it's
|
|
|
|
// possible sketchFromKclValue "fails" when sketching on a face,
|
|
|
|
// and this couldn't wouldn't run.
|
|
|
|
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
|
2024-04-19 11:56:21 -04:00
|
|
|
},
|
|
|
|
})
|
2025-02-15 00:57:04 +11:00
|
|
|
return {
|
|
|
|
updatedEntryNodePath,
|
|
|
|
updatedSketchNodePaths,
|
|
|
|
expressionIndexToDelete: insertIndex,
|
|
|
|
}
|
2024-04-19 11:56:21 -04:00
|
|
|
}
|
2024-11-18 10:04:09 -05:00
|
|
|
setupDraftCenterRectangle = async (
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: PathToNode,
|
|
|
|
sketchNodePaths: PathToNode[],
|
|
|
|
planeNodePath: PathToNode,
|
2024-11-18 10:04:09 -05:00
|
|
|
forward: [number, number, number],
|
|
|
|
up: [number, number, number],
|
|
|
|
sketchOrigin: [number, number, number],
|
|
|
|
rectangleOrigin: [x: number, y: number]
|
2025-02-15 00:57:04 +11:00
|
|
|
): Promise<SketchDetailsUpdate | Error> => {
|
2024-11-18 10:04:09 -05:00
|
|
|
let _ast = structuredClone(kclManager.ast)
|
2025-02-15 00:57:04 +11:00
|
|
|
|
|
|
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
2024-11-18 10:04:09 -05:00
|
|
|
_ast,
|
2025-02-15 00:57:04 +11:00
|
|
|
planeNodePath,
|
|
|
|
'VariableDeclarator'
|
2024-12-14 09:57:33 +11:00
|
|
|
)
|
2024-11-18 10:04:09 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
if (err(varDec)) return varDec
|
|
|
|
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
|
2024-12-14 09:57:33 +11:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const varName = findUniqueName(_ast, 'profile')
|
|
|
|
// first create just the variable declaration, as that's
|
|
|
|
// all we want the user to see in the editor
|
|
|
|
const tag = findUniqueName(_ast, 'rectangleSegmentA')
|
|
|
|
const newDeclaration = createVariableDeclaration(
|
|
|
|
varName,
|
|
|
|
createCallExpressionStdLib('startProfileAt', [
|
|
|
|
createArrayExpression([
|
|
|
|
createLiteral(roundOff(rectangleOrigin[0])),
|
|
|
|
createLiteral(roundOff(rectangleOrigin[1])),
|
|
|
|
]),
|
|
|
|
createIdentifier(varDec.node.id.name),
|
|
|
|
])
|
|
|
|
)
|
|
|
|
const insertIndex = getInsertIndex(sketchNodePaths, planeNodePath, 'end')
|
|
|
|
|
|
|
|
_ast.body.splice(insertIndex, 0, newDeclaration)
|
|
|
|
const { updatedEntryNodePath, updatedSketchNodePaths } =
|
|
|
|
updateSketchNodePathsWithInsertIndex({
|
|
|
|
insertIndex,
|
|
|
|
insertType: 'end',
|
|
|
|
sketchNodePaths,
|
|
|
|
})
|
2024-12-14 09:57:33 +11:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
let __recastAst = parse(recast(_ast))
|
|
|
|
if (trap(__recastAst) || !resultIsOk(__recastAst))
|
|
|
|
return Promise.reject(__recastAst)
|
|
|
|
_ast = __recastAst.program
|
2024-12-16 10:34:11 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
// do a quick mock execution to get the program memory up-to-date
|
|
|
|
await kclManager.executeAstMock(_ast)
|
|
|
|
|
|
|
|
const justCreatedNode = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
|
|
|
updatedEntryNodePath,
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
|
|
|
|
if (trap(justCreatedNode)) return Promise.reject(justCreatedNode)
|
|
|
|
const startProfileAt = justCreatedNode.node?.declaration
|
|
|
|
// than add the rest of the profile so we can "animate" it
|
|
|
|
// as draft segments
|
|
|
|
startProfileAt.init = createPipeExpression([
|
|
|
|
startProfileAt?.init,
|
|
|
|
...getRectangleCallExpressions(rectangleOrigin, tag),
|
|
|
|
])
|
|
|
|
const code = recast(_ast)
|
|
|
|
__recastAst = parse(code)
|
|
|
|
if (trap(__recastAst) || !resultIsOk(__recastAst))
|
|
|
|
return Promise.reject(__recastAst)
|
|
|
|
_ast = __recastAst.program
|
2024-11-18 10:04:09 -05:00
|
|
|
|
2025-02-12 10:22:56 +13:00
|
|
|
const { truncatedAst } = await this.setupSketch({
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: updatedEntryNodePath,
|
|
|
|
sketchNodePaths: updatedSketchNodePaths,
|
2024-11-18 10:04:09 -05:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: sketchOrigin,
|
|
|
|
maybeModdedAst: _ast,
|
|
|
|
draftExpressionsIndices: { start: 0, end: 3 },
|
|
|
|
})
|
|
|
|
|
|
|
|
sceneInfra.setCallbacks({
|
|
|
|
onMove: async (args) => {
|
|
|
|
// Update the width and height of the draft rectangle
|
2025-02-15 00:57:04 +11:00
|
|
|
|
|
|
|
const nodePathWithCorrectedIndexForTruncatedAst =
|
|
|
|
structuredClone(updatedEntryNodePath)
|
|
|
|
nodePathWithCorrectedIndexForTruncatedAst[1][0] =
|
|
|
|
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) -
|
|
|
|
Number(planeNodePath[1][0]) -
|
|
|
|
1
|
2024-11-18 10:04:09 -05:00
|
|
|
|
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
truncatedAst,
|
2025-02-15 00:57:04 +11:00
|
|
|
nodePathWithCorrectedIndexForTruncatedAst,
|
2024-11-18 10:04:09 -05:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (trap(_node)) return Promise.reject(_node)
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node?.declaration.init
|
2024-11-18 10:04:09 -05:00
|
|
|
|
|
|
|
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
|
|
|
|
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
|
|
|
|
|
|
|
if (sketchInit.type === 'PipeExpression') {
|
|
|
|
updateCenterRectangleSketch(
|
|
|
|
sketchInit,
|
|
|
|
x,
|
|
|
|
y,
|
2025-02-15 00:57:04 +11:00
|
|
|
tag,
|
2024-11-18 10:04:09 -05:00
|
|
|
rectangleOrigin[0],
|
|
|
|
rectangleOrigin[1]
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const { execState } = await executeAst({
|
|
|
|
ast: truncatedAst,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
2025-03-15 10:08:39 -07:00
|
|
|
rustContext,
|
2025-02-12 10:22:56 +13:00
|
|
|
isMock: true,
|
2024-11-18 10:04:09 -05:00
|
|
|
})
|
2025-02-15 00:57:04 +11:00
|
|
|
const sketch = sketchFromKclValue(execState.variables[varName], varName)
|
2024-11-18 10:04:09 -05:00
|
|
|
if (err(sketch)) return Promise.reject(sketch)
|
|
|
|
const sgPaths = sketch.paths
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const varDecIndex = Number(updatedEntryNodePath[1][0])
|
|
|
|
|
|
|
|
this.updateSegment(
|
|
|
|
sketch.start,
|
|
|
|
0,
|
|
|
|
varDecIndex,
|
|
|
|
_ast,
|
|
|
|
orthoFactor,
|
|
|
|
sketch
|
|
|
|
)
|
2024-11-18 10:04:09 -05:00
|
|
|
sgPaths.forEach((seg, index) =>
|
2025-02-15 00:57:04 +11:00
|
|
|
this.updateSegment(seg, index, varDecIndex, _ast, orthoFactor, sketch)
|
2024-11-18 10:04:09 -05:00
|
|
|
)
|
|
|
|
},
|
|
|
|
onClick: async (args) => {
|
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
|
|
|
)
|
|
|
|
if (interaction !== 'none') return
|
|
|
|
// Commit the rectangle to the full AST/code and return to sketch.idle
|
|
|
|
const cornerPoint = args.intersectionPoint?.twoD
|
|
|
|
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
|
|
|
|
|
|
|
const x = roundOff((cornerPoint.x || 0) - rectangleOrigin[0])
|
|
|
|
const y = roundOff((cornerPoint.y || 0) - rectangleOrigin[1])
|
|
|
|
|
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
2025-02-15 00:57:04 +11:00
|
|
|
updatedEntryNodePath,
|
2024-11-18 10:04:09 -05:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (trap(_node)) return
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node?.declaration.init
|
2024-11-18 10:04:09 -05:00
|
|
|
|
|
|
|
if (sketchInit.type === 'PipeExpression') {
|
|
|
|
updateCenterRectangleSketch(
|
|
|
|
sketchInit,
|
|
|
|
x,
|
|
|
|
y,
|
2025-02-15 00:57:04 +11:00
|
|
|
tag,
|
2024-11-18 10:04:09 -05:00
|
|
|
rectangleOrigin[0],
|
|
|
|
rectangleOrigin[1]
|
|
|
|
)
|
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(_ast))
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult))
|
|
|
|
return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-11-18 10:04:09 -05:00
|
|
|
|
|
|
|
// Update the primary AST and unequip the rectangle tool
|
|
|
|
await kclManager.executeAstMock(_ast)
|
2024-12-16 10:34:11 -05:00
|
|
|
sceneInfra.modelingSend({ type: 'Finish center rectangle' })
|
2024-11-18 10:04:09 -05:00
|
|
|
|
2024-11-19 11:10:08 -05:00
|
|
|
// lee: I had this at the bottom of the function, but it's
|
|
|
|
// possible sketchFromKclValue "fails" when sketching on a face,
|
|
|
|
// and this couldn't wouldn't run.
|
|
|
|
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
|
2024-11-18 10:04:09 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
2025-02-15 00:57:04 +11:00
|
|
|
return {
|
|
|
|
updatedEntryNodePath,
|
|
|
|
updatedSketchNodePaths,
|
|
|
|
expressionIndexToDelete: insertIndex,
|
|
|
|
}
|
2024-11-18 10:04:09 -05:00
|
|
|
}
|
2025-02-15 00:57:04 +11:00
|
|
|
setupDraftCircleThreePoint = async (
|
|
|
|
sketchEntryNodePath: PathToNode,
|
|
|
|
sketchNodePaths: PathToNode[],
|
|
|
|
planeNodePath: PathToNode,
|
|
|
|
forward: [number, number, number],
|
|
|
|
up: [number, number, number],
|
|
|
|
sketchOrigin: [number, number, number],
|
|
|
|
point1: [x: number, y: number],
|
|
|
|
point2: [x: number, y: number]
|
|
|
|
): Promise<SketchDetailsUpdate | Error> => {
|
|
|
|
let _ast = structuredClone(kclManager.ast)
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
|
|
|
_ast,
|
|
|
|
planeNodePath,
|
|
|
|
'VariableDeclarator'
|
|
|
|
)
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
if (err(varDec)) return varDec
|
|
|
|
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
|
|
|
|
|
|
|
|
const varName = findUniqueName(_ast, 'profile')
|
|
|
|
|
|
|
|
const thirdPointCloseToWhereUserLastClicked = `[${roundOff(
|
|
|
|
point2[0] + 0.1,
|
|
|
|
2
|
|
|
|
)}, ${roundOff(point2[1] + 0.1, 2)}]`
|
|
|
|
const newExpression = createNodeFromExprSnippet`${varName} = circleThreePoint(
|
|
|
|
${varDec.node.id.name},
|
|
|
|
p1 = [${roundOff(point1[0], 2)}, ${roundOff(point1[1], 2)}],
|
|
|
|
p2 = [${roundOff(point2[0], 2)}, ${roundOff(point2[1], 2)}],
|
|
|
|
p3 = ${thirdPointCloseToWhereUserLastClicked},
|
|
|
|
)`
|
|
|
|
if (err(newExpression)) return newExpression
|
|
|
|
const insertIndex = getInsertIndex(sketchNodePaths, planeNodePath, 'end')
|
|
|
|
|
|
|
|
_ast.body.splice(insertIndex, 0, newExpression)
|
|
|
|
const { updatedEntryNodePath, updatedSketchNodePaths } =
|
|
|
|
updateSketchNodePathsWithInsertIndex({
|
|
|
|
insertIndex,
|
|
|
|
insertType: 'end',
|
|
|
|
sketchNodePaths,
|
2025-01-16 11:10:36 -05:00
|
|
|
})
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const pResult = parse(recast(_ast))
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
// do a quick mock execution to get the program memory up-to-date
|
|
|
|
await kclManager.executeAstMock(_ast)
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const { truncatedAst } = await this.setupSketch({
|
|
|
|
sketchEntryNodePath: updatedEntryNodePath,
|
|
|
|
sketchNodePaths: updatedSketchNodePaths,
|
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: sketchOrigin,
|
|
|
|
maybeModdedAst: _ast,
|
|
|
|
draftExpressionsIndices: { start: 0, end: 0 },
|
|
|
|
})
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
sceneInfra.setCallbacks({
|
|
|
|
onMove: async (args) => {
|
|
|
|
const firstProfileIndex = Number(updatedSketchNodePaths[0][1][0])
|
|
|
|
const nodePathWithCorrectedIndexForTruncatedAst =
|
|
|
|
structuredClone(updatedEntryNodePath)
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
nodePathWithCorrectedIndexForTruncatedAst[1][0] =
|
|
|
|
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) -
|
|
|
|
firstProfileIndex
|
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
truncatedAst,
|
|
|
|
nodePathWithCorrectedIndexForTruncatedAst,
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
let modded = structuredClone(truncatedAst)
|
|
|
|
if (trap(_node)) return
|
|
|
|
const sketchInit = _node.node.declaration.init
|
2025-01-16 11:10:36 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
if (sketchInit.type === 'CallExpressionKw') {
|
|
|
|
const moddedResult = changeSketchArguments(
|
|
|
|
modded,
|
|
|
|
kclManager.variables,
|
|
|
|
{
|
|
|
|
type: 'path',
|
|
|
|
pathToNode: nodePathWithCorrectedIndexForTruncatedAst,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'circle-three-point-segment',
|
|
|
|
p1: [point1[0], point1[1]],
|
|
|
|
p2: [point2[0], point2[1]],
|
|
|
|
p3: [
|
|
|
|
args.intersectionPoint.twoD.x,
|
|
|
|
args.intersectionPoint.twoD.y,
|
|
|
|
],
|
|
|
|
}
|
|
|
|
)
|
|
|
|
if (err(moddedResult)) return
|
|
|
|
modded = moddedResult.modifiedAst
|
2025-01-16 11:10:36 -05:00
|
|
|
}
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const { execState } = await executeAst({
|
|
|
|
ast: modded,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
2025-03-15 10:08:39 -07:00
|
|
|
rustContext,
|
2025-02-15 00:57:04 +11:00
|
|
|
isMock: true,
|
|
|
|
})
|
|
|
|
const sketch = sketchFromKclValue(execState.variables[varName], varName)
|
|
|
|
if (err(sketch)) return
|
|
|
|
const sgPaths = sketch.paths
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
2025-01-06 14:08:18 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const varDecIndex = Number(updatedEntryNodePath[1][0])
|
2025-01-16 11:10:36 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
this.updateSegment(
|
|
|
|
sketch.start,
|
|
|
|
0,
|
|
|
|
varDecIndex,
|
|
|
|
_ast,
|
|
|
|
orthoFactor,
|
|
|
|
sketch
|
2025-01-16 11:10:36 -05:00
|
|
|
)
|
2025-02-15 00:57:04 +11:00
|
|
|
sgPaths.forEach((seg, index) =>
|
|
|
|
this.updateSegment(seg, index, varDecIndex, _ast, orthoFactor, sketch)
|
2024-12-20 14:30:37 -05:00
|
|
|
)
|
2025-02-15 00:57:04 +11:00
|
|
|
},
|
|
|
|
onClick: async (args) => {
|
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
2025-01-16 11:10:36 -05:00
|
|
|
)
|
2025-02-15 00:57:04 +11:00
|
|
|
if (interaction !== 'none') return
|
|
|
|
// Commit the rectangle to the full AST/code and return to sketch.idle
|
|
|
|
const cornerPoint = args.intersectionPoint?.twoD
|
|
|
|
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
2025-01-16 11:10:36 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
|
|
|
updatedEntryNodePath || [],
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (trap(_node)) return
|
|
|
|
const sketchInit = _node.node?.declaration.init
|
2025-01-16 11:10:36 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
let modded = structuredClone(_ast)
|
|
|
|
if (sketchInit.type === 'CallExpressionKw') {
|
|
|
|
const moddedResult = changeSketchArguments(
|
|
|
|
modded,
|
|
|
|
kclManager.variables,
|
|
|
|
{
|
|
|
|
type: 'path',
|
|
|
|
pathToNode: updatedEntryNodePath,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'circle-three-point-segment',
|
|
|
|
p1: [point1[0], point1[1]],
|
|
|
|
p2: [point2[0], point2[1]],
|
|
|
|
p3: [cornerPoint.x || 0, cornerPoint.y || 0],
|
|
|
|
}
|
2025-01-16 11:10:36 -05:00
|
|
|
)
|
2025-02-15 00:57:04 +11:00
|
|
|
if (err(moddedResult)) return
|
|
|
|
modded = moddedResult.modifiedAst
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const newCode = recast(modded)
|
|
|
|
if (err(newCode)) return
|
|
|
|
const pResult = parse(newCode)
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult))
|
|
|
|
return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
// Update the primary AST and unequip the rectangle tool
|
|
|
|
await kclManager.executeAstMock(_ast)
|
|
|
|
sceneInfra.modelingSend({ type: 'Finish circle three point' })
|
|
|
|
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
|
|
|
|
}
|
2024-12-20 14:30:37 -05:00
|
|
|
},
|
|
|
|
})
|
2025-02-15 00:57:04 +11:00
|
|
|
return {
|
|
|
|
updatedEntryNodePath,
|
|
|
|
updatedSketchNodePaths,
|
|
|
|
expressionIndexToDelete: insertIndex,
|
|
|
|
}
|
2024-12-20 14:30:37 -05:00
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
setupDraftCircle = async (
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: PathToNode,
|
|
|
|
sketchNodePaths: PathToNode[],
|
|
|
|
planeNodePath: PathToNode,
|
2024-09-23 22:42:51 +10:00
|
|
|
forward: [number, number, number],
|
|
|
|
up: [number, number, number],
|
|
|
|
sketchOrigin: [number, number, number],
|
|
|
|
circleCenter: [x: number, y: number]
|
2025-02-15 00:57:04 +11:00
|
|
|
): Promise<SketchDetailsUpdate | Error> => {
|
2024-09-23 22:42:51 +10:00
|
|
|
let _ast = structuredClone(kclManager.ast)
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
2024-09-23 22:42:51 +10:00
|
|
|
_ast,
|
2025-02-15 00:57:04 +11:00
|
|
|
planeNodePath,
|
|
|
|
'VariableDeclarator'
|
2024-09-23 22:42:51 +10:00
|
|
|
)
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
if (err(varDec)) return varDec
|
|
|
|
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
|
|
|
|
|
|
|
|
const varName = findUniqueName(_ast, 'profile')
|
|
|
|
const newExpression = createVariableDeclaration(
|
|
|
|
varName,
|
2025-02-28 17:40:01 -08:00
|
|
|
createCallExpressionStdLibKw('circle', varDec.node.id, [
|
|
|
|
createLabeledArg(
|
|
|
|
'center',
|
|
|
|
createArrayExpression([
|
2024-09-23 22:42:51 +10:00
|
|
|
createLiteral(roundOff(circleCenter[0])),
|
|
|
|
createLiteral(roundOff(circleCenter[1])),
|
2025-02-28 17:40:01 -08:00
|
|
|
])
|
|
|
|
),
|
|
|
|
createLabeledArg('radius', createLiteral(1)),
|
2025-02-15 00:57:04 +11:00
|
|
|
])
|
|
|
|
)
|
|
|
|
|
|
|
|
const insertIndex = getInsertIndex(sketchNodePaths, planeNodePath, 'end')
|
|
|
|
|
|
|
|
_ast.body.splice(insertIndex, 0, newExpression)
|
|
|
|
const { updatedEntryNodePath, updatedSketchNodePaths } =
|
|
|
|
updateSketchNodePathsWithInsertIndex({
|
|
|
|
insertIndex,
|
|
|
|
insertType: 'end',
|
|
|
|
sketchNodePaths,
|
|
|
|
})
|
2024-09-23 22:42:51 +10:00
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(_ast))
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-09-23 22:42:51 +10:00
|
|
|
|
|
|
|
// do a quick mock execution to get the program memory up-to-date
|
|
|
|
await kclManager.executeAstMock(_ast)
|
|
|
|
|
2025-02-12 10:22:56 +13:00
|
|
|
const { truncatedAst } = await this.setupSketch({
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: updatedEntryNodePath,
|
|
|
|
sketchNodePaths: updatedSketchNodePaths,
|
2024-09-23 22:42:51 +10:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: sketchOrigin,
|
|
|
|
maybeModdedAst: _ast,
|
|
|
|
draftExpressionsIndices: { start: 0, end: 0 },
|
|
|
|
})
|
|
|
|
|
|
|
|
sceneInfra.setCallbacks({
|
|
|
|
onMove: async (args) => {
|
2025-02-15 00:57:04 +11:00
|
|
|
const nodePathWithCorrectedIndexForTruncatedAst =
|
|
|
|
structuredClone(updatedEntryNodePath)
|
|
|
|
nodePathWithCorrectedIndexForTruncatedAst[1][0] =
|
|
|
|
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) -
|
|
|
|
Number(planeNodePath[1][0]) -
|
|
|
|
1
|
2024-09-23 22:42:51 +10:00
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
truncatedAst,
|
2025-02-15 00:57:04 +11:00
|
|
|
nodePathWithCorrectedIndexForTruncatedAst,
|
2024-09-23 22:42:51 +10:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
let modded = structuredClone(truncatedAst)
|
|
|
|
if (trap(_node)) return
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node.declaration.init
|
2024-09-23 22:42:51 +10:00
|
|
|
|
|
|
|
const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0]
|
|
|
|
const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
|
|
|
|
|
2025-02-28 17:40:01 -08:00
|
|
|
if (sketchInit.type === 'CallExpressionKw') {
|
2024-09-23 22:42:51 +10:00
|
|
|
const moddedResult = changeSketchArguments(
|
|
|
|
modded,
|
2025-02-12 10:22:56 +13:00
|
|
|
kclManager.variables,
|
2024-09-23 22:42:51 +10:00
|
|
|
{
|
|
|
|
type: 'path',
|
2025-02-15 00:57:04 +11:00
|
|
|
pathToNode: nodePathWithCorrectedIndexForTruncatedAst,
|
2024-09-23 22:42:51 +10:00
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'arc-segment',
|
|
|
|
center: circleCenter,
|
|
|
|
radius: Math.sqrt(x ** 2 + y ** 2),
|
|
|
|
from: circleCenter,
|
|
|
|
}
|
|
|
|
)
|
2025-03-07 22:07:16 -06:00
|
|
|
if (err(moddedResult)) {
|
|
|
|
return
|
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
modded = moddedResult.modifiedAst
|
|
|
|
}
|
|
|
|
|
2024-10-09 19:38:40 -04:00
|
|
|
const { execState } = await executeAst({
|
2024-09-23 22:42:51 +10:00
|
|
|
ast: modded,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
2025-03-15 10:08:39 -07:00
|
|
|
rustContext,
|
2025-02-12 10:22:56 +13:00
|
|
|
isMock: true,
|
2024-09-23 22:42:51 +10:00
|
|
|
})
|
2025-02-15 00:57:04 +11:00
|
|
|
const sketch = sketchFromKclValue(execState.variables[varName], varName)
|
2024-09-27 15:44:44 -07:00
|
|
|
if (err(sketch)) return
|
2024-10-23 12:42:54 -05:00
|
|
|
const sgPaths = sketch.paths
|
2024-09-23 22:42:51 +10:00
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const varDecIndex = Number(updatedEntryNodePath[1][0])
|
|
|
|
|
|
|
|
this.updateSegment(
|
|
|
|
sketch.start,
|
|
|
|
0,
|
|
|
|
varDecIndex,
|
|
|
|
_ast,
|
|
|
|
orthoFactor,
|
|
|
|
sketch
|
|
|
|
)
|
2024-09-23 22:42:51 +10:00
|
|
|
sgPaths.forEach((seg, index) =>
|
2025-02-15 00:57:04 +11:00
|
|
|
this.updateSegment(seg, index, varDecIndex, _ast, orthoFactor, sketch)
|
2024-09-23 22:42:51 +10:00
|
|
|
)
|
|
|
|
},
|
|
|
|
onClick: async (args) => {
|
2024-10-24 06:26:33 -07:00
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
|
|
|
)
|
|
|
|
if (interaction !== 'none') return
|
2024-09-23 22:42:51 +10:00
|
|
|
// Commit the rectangle to the full AST/code and return to sketch.idle
|
|
|
|
const cornerPoint = args.intersectionPoint?.twoD
|
|
|
|
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
|
|
|
|
|
|
|
const x = roundOff((cornerPoint.x || 0) - circleCenter[0])
|
|
|
|
const y = roundOff((cornerPoint.y || 0) - circleCenter[1])
|
|
|
|
|
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
2025-02-15 00:57:04 +11:00
|
|
|
updatedEntryNodePath || [],
|
2024-09-23 22:42:51 +10:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (trap(_node)) return
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node?.declaration.init
|
2024-09-23 22:42:51 +10:00
|
|
|
|
|
|
|
let modded = structuredClone(_ast)
|
2025-02-28 17:40:01 -08:00
|
|
|
if (sketchInit.type === 'CallExpressionKw') {
|
2024-09-23 22:42:51 +10:00
|
|
|
const moddedResult = changeSketchArguments(
|
|
|
|
modded,
|
2025-02-12 10:22:56 +13:00
|
|
|
kclManager.variables,
|
2024-09-23 22:42:51 +10:00
|
|
|
{
|
|
|
|
type: 'path',
|
2025-02-15 00:57:04 +11:00
|
|
|
pathToNode: updatedEntryNodePath,
|
2024-09-23 22:42:51 +10:00
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'arc-segment',
|
|
|
|
center: circleCenter,
|
|
|
|
radius: Math.sqrt(x ** 2 + y ** 2),
|
|
|
|
from: circleCenter,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
if (err(moddedResult)) return
|
|
|
|
modded = moddedResult.modifiedAst
|
|
|
|
|
2024-11-16 16:49:44 -05:00
|
|
|
const newCode = recast(modded)
|
|
|
|
if (err(newCode)) return
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(newCode)
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult))
|
|
|
|
return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-09-23 22:42:51 +10:00
|
|
|
|
|
|
|
// Update the primary AST and unequip the rectangle tool
|
|
|
|
await kclManager.executeAstMock(_ast)
|
2024-12-14 09:57:33 +11:00
|
|
|
sceneInfra.modelingSend({ type: 'Finish circle' })
|
2024-12-16 10:34:11 -05:00
|
|
|
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
|
2024-09-23 22:42:51 +10:00
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
2025-02-15 00:57:04 +11:00
|
|
|
return {
|
|
|
|
updatedEntryNodePath,
|
|
|
|
updatedSketchNodePaths,
|
|
|
|
expressionIndexToDelete: insertIndex,
|
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
}
|
2024-04-03 13:22:56 +11:00
|
|
|
setupSketchIdleCallbacks = ({
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
|
|
|
planeNodePath,
|
2024-04-03 13:22:56 +11:00
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
}: {
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: PathToNode
|
|
|
|
sketchNodePaths: PathToNode[]
|
|
|
|
planeNodePath: PathToNode
|
2024-04-03 13:22:56 +11:00
|
|
|
forward: [number, number, number]
|
|
|
|
up: [number, number, number]
|
|
|
|
position?: [number, number, number]
|
|
|
|
}) => {
|
|
|
|
let addingNewSegmentStatus: 'nothing' | 'pending' | 'added' = 'nothing'
|
2024-03-25 15:48:51 +11:00
|
|
|
sceneInfra.setCallbacks({
|
2024-04-03 13:22:56 +11:00
|
|
|
onDragEnd: async () => {
|
|
|
|
if (addingNewSegmentStatus !== 'nothing') {
|
2025-02-15 00:57:04 +11:00
|
|
|
this.tearDownSketch({ removeAxis: false })
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-04-03 13:22:56 +11:00
|
|
|
this.setupSketch({
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
2024-04-03 13:22:56 +11:00
|
|
|
maybeModdedAst: kclManager.ast,
|
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
})
|
|
|
|
// setting up the callbacks again resets value in closures
|
|
|
|
this.setupSketchIdleCallbacks({
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
|
|
|
planeNodePath,
|
2024-04-03 13:22:56 +11:00
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
})
|
2024-11-16 16:49:44 -05:00
|
|
|
await codeManager.writeToFile()
|
2024-04-03 13:22:56 +11:00
|
|
|
}
|
|
|
|
},
|
|
|
|
onDrag: async ({
|
|
|
|
selected,
|
|
|
|
intersectionPoint,
|
|
|
|
mouseEvent,
|
|
|
|
intersects,
|
|
|
|
}) => {
|
2024-03-25 15:48:51 +11:00
|
|
|
if (mouseEvent.which !== 1) return
|
2024-04-03 13:22:56 +11:00
|
|
|
|
|
|
|
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'
|
|
|
|
)
|
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
const sketch = sketchFromPathToNode({
|
2024-04-03 13:22:56 +11:00
|
|
|
pathToNode,
|
|
|
|
ast: kclManager.ast,
|
2025-02-12 10:22:56 +13:00
|
|
|
variables: kclManager.variables,
|
2024-04-03 13:22:56 +11:00
|
|
|
})
|
2024-09-27 15:44:44 -07:00
|
|
|
if (trap(sketch)) return
|
|
|
|
if (!sketch) {
|
|
|
|
trap(new Error('sketch not found'))
|
2024-08-07 15:15:22 -04:00
|
|
|
return
|
|
|
|
}
|
2024-04-03 13:22:56 +11:00
|
|
|
|
|
|
|
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
|
|
|
|
if (addingNewSegmentStatus === 'nothing') {
|
2024-10-23 12:42:54 -05:00
|
|
|
const prevSegment = sketch.paths[pipeIndex - 2]
|
2024-04-03 13:22:56 +11:00
|
|
|
const mod = addNewSketchLn({
|
|
|
|
node: kclManager.ast,
|
2025-02-12 10:22:56 +13:00
|
|
|
variables: kclManager.variables,
|
2024-09-13 21:14:14 +10:00
|
|
|
input: {
|
|
|
|
type: 'straight-segment',
|
|
|
|
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
|
|
|
|
from: prevSegment.from,
|
|
|
|
},
|
2024-04-03 13:22:56 +11:00
|
|
|
// 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'
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(mod)) return
|
|
|
|
|
2024-04-17 20:18:07 -07:00
|
|
|
await kclManager.executeAstMock(mod.modifiedAst)
|
2025-02-15 00:57:04 +11:00
|
|
|
this.tearDownSketch({ removeAxis: false })
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-04-03 13:22:56 +11:00
|
|
|
this.setupSketch({
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: pathToNode,
|
|
|
|
sketchNodePaths,
|
2024-04-03 13:22:56 +11:00
|
|
|
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({
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchNodePaths,
|
|
|
|
sketchEntryNodePath: pathToNodeForNewSegment,
|
|
|
|
planeNodePath,
|
2024-04-03 13:22:56 +11:00
|
|
|
object: selected,
|
|
|
|
intersection2d: intersectionPoint.twoD,
|
|
|
|
intersects,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-03-25 15:48:51 +11:00
|
|
|
this.onDragSegment({
|
|
|
|
object: selected,
|
|
|
|
intersection2d: intersectionPoint.twoD,
|
2025-02-15 00:57:04 +11:00
|
|
|
planeNodePath,
|
2024-03-25 15:48:51 +11:00
|
|
|
intersects,
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchNodePaths,
|
|
|
|
sketchEntryNodePath,
|
2024-03-25 15:48:51 +11:00
|
|
|
})
|
|
|
|
},
|
|
|
|
onMove: () => {},
|
|
|
|
onClick: (args) => {
|
2024-10-24 06:26:33 -07:00
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
|
|
|
)
|
|
|
|
if (interaction !== 'none') return
|
2024-03-25 15:48:51 +11:00
|
|
|
if (args?.mouseEvent.which !== 1) return
|
|
|
|
if (!args || !args.selected) {
|
|
|
|
sceneInfra.modelingSend({
|
|
|
|
type: 'Set selection',
|
|
|
|
data: {
|
|
|
|
selectionType: 'singleCodeCursor',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const { selected } = args
|
|
|
|
const event = getEventForSegmentSelection(selected)
|
|
|
|
if (!event) return
|
|
|
|
sceneInfra.modelingSend(event)
|
|
|
|
},
|
2024-04-04 11:07:51 +11:00
|
|
|
...this.mouseEnterLeaveCallbacks(),
|
2024-03-25 15:48:51 +11:00
|
|
|
})
|
|
|
|
}
|
2025-02-12 10:22:56 +13:00
|
|
|
prepareTruncatedAst = (
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchNodePaths: PathToNode[],
|
2024-10-30 16:52:17 -04:00
|
|
|
ast?: Node<Program>,
|
2024-02-11 12:59:00 +11:00
|
|
|
draftSegment?: DraftSegment
|
|
|
|
) =>
|
2025-02-12 10:22:56 +13:00
|
|
|
prepareTruncatedAst(
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchNodePaths,
|
2024-02-11 12:59:00 +11:00
|
|
|
ast || kclManager.ast,
|
2025-02-12 10:22:56 +13:00
|
|
|
kclManager.lastSuccessfulVariables,
|
2024-02-11 12:59:00 +11:00
|
|
|
draftSegment
|
|
|
|
)
|
|
|
|
onDragSegment({
|
|
|
|
object,
|
2024-03-04 08:14:37 +11:00
|
|
|
intersection2d: _intersection2d,
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
|
|
|
planeNodePath,
|
2024-02-11 12:59:00 +11:00
|
|
|
draftInfo,
|
2024-03-04 08:14:37 +11:00
|
|
|
intersects,
|
2024-02-11 12:59:00 +11:00
|
|
|
}: {
|
|
|
|
object: any
|
|
|
|
intersection2d: Vector2
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchEntryNodePath: PathToNode
|
|
|
|
sketchNodePaths: PathToNode[]
|
|
|
|
planeNodePath: PathToNode
|
2024-03-04 08:14:37 +11:00
|
|
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
2024-02-11 12:59:00 +11:00
|
|
|
draftInfo?: {
|
2024-10-30 16:52:17 -04:00
|
|
|
truncatedAst: Node<Program>
|
2024-02-11 12:59:00 +11:00
|
|
|
variableDeclarationName: string
|
|
|
|
}
|
|
|
|
}) {
|
2024-10-31 07:04:38 -07:00
|
|
|
const intersectsProfileStart =
|
2024-03-04 08:14:37 +11:00
|
|
|
draftInfo &&
|
|
|
|
intersects
|
|
|
|
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
|
2025-02-15 00:57:04 +11:00
|
|
|
.find(isGroupStartProfileForCurrentProfile(sketchEntryNodePath))
|
2024-10-31 07:04:38 -07:00
|
|
|
const intersection2d = intersectsProfileStart
|
|
|
|
? new Vector2(
|
|
|
|
intersectsProfileStart.position.x,
|
|
|
|
intersectsProfileStart.position.y
|
|
|
|
)
|
2024-03-04 08:14:37 +11:00
|
|
|
: _intersection2d
|
|
|
|
|
2024-10-31 07:04:38 -07:00
|
|
|
const intersectsYAxis = intersects.find(
|
|
|
|
(sceneObject) => sceneObject.object.name === Y_AXIS
|
|
|
|
)
|
|
|
|
const intersectsXAxis = intersects.find(
|
|
|
|
(sceneObject) => sceneObject.object.name === X_AXIS
|
|
|
|
)
|
|
|
|
|
|
|
|
const snappedPoint = new Vector2(
|
|
|
|
intersectsYAxis ? 0 : intersection2d.x,
|
|
|
|
intersectsXAxis ? 0 : intersection2d.y
|
|
|
|
)
|
|
|
|
|
2024-09-23 22:42:51 +10:00
|
|
|
const group = getParentGroup(object, SEGMENT_BODIES_PLUS_PROFILE_START)
|
2025-02-15 00:57:04 +11:00
|
|
|
const subGroup = getParentGroup(object, [
|
|
|
|
ARROWHEAD,
|
|
|
|
CIRCLE_CENTER_HANDLE,
|
|
|
|
CIRCLE_THREE_POINT_HANDLE1,
|
|
|
|
CIRCLE_THREE_POINT_HANDLE2,
|
|
|
|
CIRCLE_THREE_POINT_HANDLE3,
|
|
|
|
])
|
2024-02-11 12:59:00 +11:00
|
|
|
if (!group) return
|
2024-07-25 20:11:46 -04:00
|
|
|
const pathToNode: PathToNode = structuredClone(group.userData.pathToNode)
|
|
|
|
const varDecIndex = pathToNode[1][0]
|
|
|
|
if (typeof varDecIndex !== 'number') {
|
|
|
|
console.error(
|
|
|
|
`Expected varDecIndex to be a number, but found: ${typeof varDecIndex} ${varDecIndex}`
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
const from: [number, number] = [
|
2025-02-15 00:57:04 +11:00
|
|
|
group.userData?.from?.[0],
|
|
|
|
group.userData?.from?.[1],
|
2024-02-11 12:59:00 +11:00
|
|
|
]
|
2024-10-31 07:04:38 -07:00
|
|
|
const dragTo: [number, number] = [snappedPoint.x, snappedPoint.y]
|
2024-02-11 12:59:00 +11:00
|
|
|
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const nodePathWithCorrectedIndexForTruncatedAst =
|
|
|
|
structuredClone(pathToNode)
|
|
|
|
nodePathWithCorrectedIndexForTruncatedAst[1][0] =
|
|
|
|
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) -
|
|
|
|
Number(sketchNodePaths[0][1][0])
|
|
|
|
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
const _node = getNodeFromPath<Node<CallExpression | CallExpressionKw>>(
|
2024-02-11 12:59:00 +11:00
|
|
|
modifiedAst,
|
2025-02-15 00:57:04 +11:00
|
|
|
draftInfo ? nodePathWithCorrectedIndexForTruncatedAst : pathToNode,
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
['CallExpression', 'CallExpressionKw']
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (trap(_node)) return
|
|
|
|
const node = _node.node
|
|
|
|
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
if (node.type !== 'CallExpression' && node.type !== 'CallExpressionKw')
|
|
|
|
return
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
let modded:
|
|
|
|
| {
|
2024-10-30 16:52:17 -04:00
|
|
|
modifiedAst: Node<Program>
|
2024-06-24 11:45:40 -04:00
|
|
|
pathToNode: PathToNode
|
|
|
|
}
|
|
|
|
| Error
|
2024-09-23 22:42:51 +10:00
|
|
|
|
|
|
|
const getChangeSketchInput = (): SegmentInputs => {
|
|
|
|
if (
|
|
|
|
group.name === CIRCLE_SEGMENT &&
|
|
|
|
// !subGroup treats grabbing the outer circumference of the circle
|
|
|
|
// as a drag of the center handle
|
|
|
|
(!subGroup || subGroup?.name === ARROWHEAD)
|
|
|
|
)
|
|
|
|
return {
|
|
|
|
type: 'arc-segment',
|
|
|
|
from,
|
|
|
|
center: group.userData.center,
|
|
|
|
// distance between the center and the drag point
|
|
|
|
radius: Math.sqrt(
|
|
|
|
(group.userData.center[0] - dragTo[0]) ** 2 +
|
|
|
|
(group.userData.center[1] - dragTo[1]) ** 2
|
|
|
|
),
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
group.name === CIRCLE_SEGMENT &&
|
|
|
|
subGroup?.name === CIRCLE_CENTER_HANDLE
|
|
|
|
)
|
|
|
|
return {
|
|
|
|
type: 'arc-segment',
|
|
|
|
from,
|
|
|
|
center: dragTo,
|
|
|
|
radius: group.userData.radius,
|
|
|
|
}
|
2025-02-15 00:57:04 +11:00
|
|
|
if (
|
|
|
|
subGroup?.name &&
|
|
|
|
[
|
|
|
|
CIRCLE_THREE_POINT_HANDLE1,
|
|
|
|
CIRCLE_THREE_POINT_HANDLE2,
|
|
|
|
CIRCLE_THREE_POINT_HANDLE3,
|
|
|
|
].includes(subGroup?.name)
|
|
|
|
) {
|
|
|
|
const input: SegmentInputs = {
|
|
|
|
type: 'circle-three-point-segment',
|
|
|
|
p1: group.userData.p1,
|
|
|
|
p2: group.userData.p2,
|
|
|
|
p3: group.userData.p3,
|
|
|
|
}
|
|
|
|
if (subGroup?.name === CIRCLE_THREE_POINT_HANDLE1) {
|
|
|
|
input.p1 = dragTo
|
|
|
|
} else if (subGroup?.name === CIRCLE_THREE_POINT_HANDLE2) {
|
|
|
|
input.p2 = dragTo
|
|
|
|
} else if (subGroup?.name === CIRCLE_THREE_POINT_HANDLE3) {
|
|
|
|
input.p3 = dragTo
|
|
|
|
}
|
|
|
|
return input
|
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
|
|
|
|
// straight segment is the default
|
|
|
|
return {
|
|
|
|
type: 'straight-segment',
|
|
|
|
from,
|
|
|
|
to: dragTo,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-02 08:48:30 +11:00
|
|
|
if (group.name === PROFILE_START) {
|
|
|
|
modded = updateStartProfileAtArgs({
|
|
|
|
node: modifiedAst,
|
|
|
|
pathToNode,
|
2024-09-13 21:14:14 +10:00
|
|
|
input: {
|
|
|
|
type: 'straight-segment',
|
|
|
|
to: dragTo,
|
|
|
|
from,
|
|
|
|
},
|
2025-02-12 10:22:56 +13:00
|
|
|
variables: kclManager.variables,
|
2024-03-02 08:48:30 +11:00
|
|
|
})
|
|
|
|
} else {
|
|
|
|
modded = changeSketchArguments(
|
|
|
|
modifiedAst,
|
2025-02-12 10:22:56 +13:00
|
|
|
kclManager.variables,
|
2024-09-13 21:14:14 +10:00
|
|
|
{
|
2024-09-23 22:42:51 +10:00
|
|
|
type: 'sourceRange',
|
2025-01-17 14:34:36 -05:00
|
|
|
sourceRange: topLevelRange(node.start, node.end),
|
2024-09-23 22:42:51 +10:00
|
|
|
},
|
|
|
|
getChangeSketchInput()
|
2024-03-02 08:48:30 +11:00
|
|
|
)
|
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(modded)) return
|
2024-03-02 08:48:30 +11:00
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
modifiedAst = modded.modifiedAst
|
2024-06-24 11:45:40 -04:00
|
|
|
const info = draftInfo
|
|
|
|
? draftInfo
|
2025-02-15 00:57:04 +11:00
|
|
|
: this.prepareTruncatedAst(sketchNodePaths || [], modifiedAst)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(info, { suppress: true })) return
|
2025-02-15 00:57:04 +11:00
|
|
|
const { truncatedAst } = info
|
2024-02-11 12:59:00 +11:00
|
|
|
;(async () => {
|
|
|
|
const code = recast(modifiedAst)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(code)) return
|
2024-02-11 12:59:00 +11:00
|
|
|
if (!draftInfo)
|
2024-04-17 20:18:07 -07:00
|
|
|
// don't want to mod the user's code yet as they have't committed to the change yet
|
2024-02-11 12:59:00 +11:00
|
|
|
// plus this would be the truncated ast being recast, it would be wrong
|
2024-05-06 19:28:30 +10:00
|
|
|
codeManager.updateCodeEditor(code)
|
2024-10-09 19:38:40 -04:00
|
|
|
const { execState } = await executeAst({
|
2024-02-11 12:59:00 +11:00
|
|
|
ast: truncatedAst,
|
2024-03-22 16:55:30 +11:00
|
|
|
engineCommandManager: this.engineCommandManager,
|
2025-03-15 10:08:39 -07:00
|
|
|
rustContext,
|
2025-02-12 10:22:56 +13:00
|
|
|
isMock: true,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
2025-02-12 10:22:56 +13:00
|
|
|
const variables = execState.variables
|
2025-02-15 00:57:04 +11:00
|
|
|
const sketchesInfo = getSketchesInfo({
|
|
|
|
sketchNodePaths,
|
|
|
|
ast: truncatedAst,
|
|
|
|
variables,
|
|
|
|
})
|
|
|
|
const callBacks: (() => SegmentOverlayPayload | null)[] = []
|
|
|
|
for (const sketchInfo of sketchesInfo) {
|
|
|
|
const { sketch, pathToNode: _pathToNode } = sketchInfo
|
|
|
|
const varDecIndex = Number(_pathToNode[1][0])
|
2024-07-08 17:57:37 -04:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
if (!sketch) return
|
2024-05-24 20:54:42 +10:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
const sgPaths = sketch.paths
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
2024-12-16 10:34:11 -05:00
|
|
|
|
2024-04-19 11:56:21 -04:00
|
|
|
this.updateSegment(
|
2025-02-15 00:57:04 +11:00
|
|
|
sketch.start,
|
|
|
|
0,
|
2024-04-19 11:56:21 -04:00
|
|
|
varDecIndex,
|
2024-02-11 12:59:00 +11:00
|
|
|
modifiedAst,
|
2024-04-19 11:56:21 -04:00
|
|
|
orthoFactor,
|
2024-09-27 15:44:44 -07:00
|
|
|
sketch
|
2024-02-11 12:59:00 +11:00
|
|
|
)
|
2025-02-15 00:57:04 +11:00
|
|
|
|
|
|
|
callBacks.push(
|
|
|
|
...sgPaths.map((group, index) =>
|
|
|
|
this.updateSegment(
|
|
|
|
group,
|
|
|
|
index,
|
|
|
|
varDecIndex,
|
|
|
|
modifiedAst,
|
|
|
|
orthoFactor,
|
|
|
|
sketch
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
2024-05-24 20:54:42 +10:00
|
|
|
sceneInfra.overlayCallbacks(callBacks)
|
2024-09-09 18:17:45 -04:00
|
|
|
})().catch(reportRejection)
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
2024-04-19 11:56:21 -04:00
|
|
|
/**
|
|
|
|
* Update the THREEjs sketch entities with new segment data
|
|
|
|
* mapping them back to the AST
|
|
|
|
* @param segment
|
|
|
|
* @param index
|
|
|
|
* @param varDecIndex
|
|
|
|
* @param modifiedAst
|
|
|
|
* @param orthoFactor
|
2024-09-27 15:44:44 -07:00
|
|
|
* @param sketch
|
2024-04-19 11:56:21 -04:00
|
|
|
*/
|
|
|
|
updateSegment = (
|
2024-09-27 15:44:44 -07:00
|
|
|
segment: Path | Sketch['start'],
|
2024-04-19 11:56:21 -04:00
|
|
|
index: number,
|
|
|
|
varDecIndex: number,
|
|
|
|
modifiedAst: Program,
|
|
|
|
orthoFactor: number,
|
2024-09-27 15:44:44 -07:00
|
|
|
sketch: Sketch
|
2024-05-24 20:54:42 +10:00
|
|
|
): (() => SegmentOverlayPayload | null) => {
|
2024-04-19 11:56:21 -04:00
|
|
|
const segPathToNode = getNodePathFromSourceRange(
|
|
|
|
modifiedAst,
|
2024-12-06 13:57:31 +13:00
|
|
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
2024-04-19 11:56:21 -04:00
|
|
|
)
|
2024-10-23 12:42:54 -05:00
|
|
|
const sgPaths = sketch.paths
|
2024-04-19 11:56:21 -04:00
|
|
|
const originalPathToNodeStr = JSON.stringify(segPathToNode)
|
|
|
|
segPathToNode[1][0] = varDecIndex
|
|
|
|
const pathToNodeStr = JSON.stringify(segPathToNode)
|
2024-09-27 15:44:44 -07:00
|
|
|
// more hacks to hopefully be solved by proper pathToNode info in memory/sketch segments
|
2024-04-19 11:56:21 -04:00
|
|
|
const group =
|
|
|
|
this.activeSegments[pathToNodeStr] ||
|
|
|
|
this.activeSegments[originalPathToNodeStr]
|
|
|
|
const type = group?.userData?.type
|
|
|
|
const factor =
|
|
|
|
(sceneInfra.camControls.camera instanceof OrthographicCamera
|
|
|
|
? orthoFactor
|
|
|
|
: perspScale(sceneInfra.camControls.camera, group)) /
|
|
|
|
sceneInfra._baseUnitMultiplier
|
2024-09-23 22:42:51 +10:00
|
|
|
let input: SegmentInputs = {
|
2024-09-13 21:14:14 +10:00
|
|
|
type: 'straight-segment',
|
|
|
|
from: segment.from,
|
|
|
|
to: segment.to,
|
2024-09-23 22:42:51 +10:00
|
|
|
}
|
2024-09-13 21:14:14 +10:00
|
|
|
let update: SegmentUtils['update'] | null = null
|
2024-04-19 11:56:21 -04:00
|
|
|
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
|
2024-09-13 21:14:14 +10:00
|
|
|
update = segmentUtils.tangentialArcTo.update
|
2024-04-19 11:56:21 -04:00
|
|
|
} else if (type === STRAIGHT_SEGMENT) {
|
2024-09-13 21:14:14 +10:00
|
|
|
update = segmentUtils.straight.update
|
2024-09-23 22:42:51 +10:00
|
|
|
} else if (
|
|
|
|
type === CIRCLE_SEGMENT &&
|
|
|
|
'type' in segment &&
|
|
|
|
segment.type === 'Circle'
|
|
|
|
) {
|
|
|
|
update = segmentUtils.circle.update
|
|
|
|
input = {
|
|
|
|
type: 'arc-segment',
|
|
|
|
from: segment.from,
|
|
|
|
center: segment.center,
|
|
|
|
radius: segment.radius,
|
|
|
|
}
|
2025-02-15 00:57:04 +11:00
|
|
|
} else if (
|
|
|
|
type === CIRCLE_THREE_POINT_SEGMENT &&
|
|
|
|
'type' in segment &&
|
|
|
|
segment.type === 'CircleThreePoint'
|
|
|
|
) {
|
|
|
|
update = segmentUtils.circleThreePoint.update
|
|
|
|
input = {
|
|
|
|
type: 'circle-three-point-segment',
|
|
|
|
p1: segment.p1,
|
|
|
|
p2: segment.p2,
|
|
|
|
p3: segment.p3,
|
|
|
|
}
|
2024-09-13 21:14:14 +10:00
|
|
|
}
|
|
|
|
const callBack =
|
|
|
|
update &&
|
|
|
|
!err(update) &&
|
|
|
|
update({
|
|
|
|
input,
|
2024-04-19 11:56:21 -04:00
|
|
|
group,
|
|
|
|
scale: factor,
|
2024-09-13 21:14:14 +10:00
|
|
|
prevSegment: sgPaths[index - 1],
|
|
|
|
sceneInfra,
|
2024-04-19 11:56:21 -04:00
|
|
|
})
|
2024-09-13 21:14:14 +10:00
|
|
|
if (callBack && !err(callBack)) return callBack
|
|
|
|
|
|
|
|
if (type === PROFILE_START) {
|
2024-04-19 11:56:21 -04:00
|
|
|
group.position.set(segment.from[0], segment.from[1], 0)
|
|
|
|
group.scale.set(factor, factor, factor)
|
|
|
|
}
|
2024-05-24 20:54:42 +10:00
|
|
|
return () => null
|
2024-04-19 11:56:21 -04:00
|
|
|
}
|
|
|
|
|
2024-09-10 13:30:39 -04:00
|
|
|
/**
|
|
|
|
* Update the base color of each of the THREEjs meshes
|
|
|
|
* that represent each of the sketch segments, to get the
|
|
|
|
* latest value from `sceneInfra._theme`
|
|
|
|
*/
|
|
|
|
updateSegmentBaseColor(newColor: Themes.Light | Themes.Dark) {
|
|
|
|
const newColorThreeJs = getThemeColorForThreeJs(newColor)
|
|
|
|
Object.values(this.activeSegments).forEach((group) => {
|
|
|
|
group.userData.baseColor = newColorThreeJs
|
|
|
|
group.traverse((child) => {
|
|
|
|
if (
|
|
|
|
child instanceof Mesh &&
|
|
|
|
child.material instanceof MeshBasicMaterial
|
|
|
|
) {
|
|
|
|
child.material.color.set(newColorThreeJs)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
removeSketchGrid() {
|
|
|
|
if (this.axisGroup) this.scene.remove(this.axisGroup)
|
|
|
|
}
|
2025-02-15 00:57:04 +11:00
|
|
|
tearDownSketch({ removeAxis = true }: { removeAxis?: boolean }) {
|
2024-11-22 11:05:04 -05:00
|
|
|
// Remove all draft groups
|
|
|
|
this.draftPointGroups.forEach((draftPointGroup) => {
|
|
|
|
this.scene.remove(draftPointGroup)
|
|
|
|
})
|
2025-02-15 00:57:04 +11:00
|
|
|
|
|
|
|
// Remove all sketch tools
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
if (this.axisGroup && removeAxis) this.scene.remove(this.axisGroup)
|
|
|
|
const sketchSegments = this.scene.children.find(
|
|
|
|
({ userData }) => userData?.type === SKETCH_GROUP_SEGMENTS
|
|
|
|
)
|
|
|
|
if (sketchSegments) {
|
2024-07-08 16:41:00 -04:00
|
|
|
// We have to manually remove the CSS2DObjects
|
|
|
|
// as they don't get removed when the group is removed
|
|
|
|
sketchSegments.traverse((object) => {
|
|
|
|
if (object instanceof CSS2DObject) {
|
|
|
|
object.element.remove()
|
|
|
|
object.remove()
|
|
|
|
}
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
this.scene.remove(sketchSegments)
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
sceneInfra.camControls.enableRotate = true
|
2024-02-11 12:59:00 +11:00
|
|
|
this.activeSegments = {}
|
|
|
|
}
|
2024-04-04 11:07:51 +11:00
|
|
|
mouseEnterLeaveCallbacks() {
|
|
|
|
return {
|
|
|
|
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
|
|
|
|
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
|
|
|
|
const obj = selected as Mesh
|
|
|
|
const mat = obj.material as MeshBasicMaterial
|
|
|
|
mat.color.set(obj.userData.baseColor)
|
|
|
|
mat.color.offsetHSL(0, 0, 0.5)
|
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
const parent = getParentGroup(
|
|
|
|
selected,
|
|
|
|
SEGMENT_BODIES_PLUS_PROFILE_START
|
|
|
|
)
|
2024-04-04 11:07:51 +11:00
|
|
|
if (parent?.userData?.pathToNode) {
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(kclManager.ast))
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult))
|
|
|
|
return Promise.reject(pResult)
|
|
|
|
const updatedAst = pResult.program
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
const _node = getNodeFromPath<
|
|
|
|
Node<CallExpression | CallExpressionKw>
|
|
|
|
>(updatedAst, parent.userData.pathToNode, [
|
|
|
|
'CallExpressionKw',
|
|
|
|
'CallExpression',
|
|
|
|
])
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(_node, { suppress: true })) return
|
|
|
|
const node = _node.node
|
2025-01-17 14:34:36 -05:00
|
|
|
editorManager.setHighlightRange([topLevelRange(node.start, node.end)])
|
2024-04-04 11:07:51 +11:00
|
|
|
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
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
2024-09-23 22:42:51 +10:00
|
|
|
let input: SegmentInputs = {
|
2024-09-13 21:14:14 +10:00
|
|
|
type: 'straight-segment',
|
|
|
|
from: parent.userData.from,
|
|
|
|
to: parent.userData.to,
|
2024-09-23 22:42:51 +10:00
|
|
|
}
|
2024-04-04 11:07:51 +11:00
|
|
|
const factor =
|
|
|
|
(sceneInfra.camControls.camera instanceof OrthographicCamera
|
|
|
|
? orthoFactor
|
|
|
|
: perspScale(sceneInfra.camControls.camera, parent)) /
|
|
|
|
sceneInfra._baseUnitMultiplier
|
2024-09-13 21:14:14 +10:00
|
|
|
let update: SegmentUtils['update'] | null = null
|
2024-04-04 11:07:51 +11:00
|
|
|
if (parent.name === STRAIGHT_SEGMENT) {
|
2024-09-13 21:14:14 +10:00
|
|
|
update = segmentUtils.straight.update
|
2024-04-04 11:07:51 +11:00
|
|
|
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
|
2024-09-13 21:14:14 +10:00
|
|
|
update = segmentUtils.tangentialArcTo.update
|
2024-09-23 22:42:51 +10:00
|
|
|
input = {
|
|
|
|
type: 'arc-segment',
|
|
|
|
from: parent.userData.from,
|
|
|
|
radius: parent.userData.radius,
|
|
|
|
center: parent.userData.center,
|
|
|
|
}
|
2024-09-13 21:14:14 +10:00
|
|
|
}
|
|
|
|
update &&
|
|
|
|
update({
|
2024-04-04 11:07:51 +11:00
|
|
|
prevSegment: parent.userData.prevSegment,
|
2024-09-13 21:14:14 +10:00
|
|
|
input,
|
2024-04-04 11:07:51 +11:00
|
|
|
group: parent,
|
|
|
|
scale: factor,
|
2024-09-13 21:14:14 +10:00
|
|
|
sceneInfra,
|
2024-04-04 11:07:51 +11:00
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
2024-12-06 13:57:31 +13:00
|
|
|
editorManager.setHighlightRange([defaultSourceRange()])
|
2024-04-04 11:07:51 +11:00
|
|
|
},
|
|
|
|
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
|
2024-12-06 13:57:31 +13:00
|
|
|
editorManager.setHighlightRange([defaultSourceRange()])
|
2024-09-23 22:42:51 +10:00
|
|
|
const parent = getParentGroup(
|
|
|
|
selected,
|
|
|
|
SEGMENT_BODIES_PLUS_PROFILE_START
|
|
|
|
)
|
2024-04-04 11:07:51 +11:00
|
|
|
if (parent) {
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
2024-09-23 22:42:51 +10:00
|
|
|
let input: SegmentInputs = {
|
2024-09-13 21:14:14 +10:00
|
|
|
type: 'straight-segment',
|
|
|
|
from: parent.userData.from,
|
|
|
|
to: parent.userData.to,
|
2024-09-23 22:42:51 +10:00
|
|
|
}
|
2024-04-04 11:07:51 +11:00
|
|
|
const factor =
|
|
|
|
(sceneInfra.camControls.camera instanceof OrthographicCamera
|
|
|
|
? orthoFactor
|
|
|
|
: perspScale(sceneInfra.camControls.camera, parent)) /
|
|
|
|
sceneInfra._baseUnitMultiplier
|
2024-09-13 21:14:14 +10:00
|
|
|
let update: SegmentUtils['update'] | null = null
|
2024-04-04 11:07:51 +11:00
|
|
|
if (parent.name === STRAIGHT_SEGMENT) {
|
2024-09-13 21:14:14 +10:00
|
|
|
update = segmentUtils.straight.update
|
2024-04-04 11:07:51 +11:00
|
|
|
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
|
2024-09-13 21:14:14 +10:00
|
|
|
update = segmentUtils.tangentialArcTo.update
|
2024-09-23 22:42:51 +10:00
|
|
|
input = {
|
|
|
|
type: 'arc-segment',
|
|
|
|
from: parent.userData.from,
|
|
|
|
radius: parent.userData.radius,
|
|
|
|
center: parent.userData.center,
|
|
|
|
}
|
2024-09-13 21:14:14 +10:00
|
|
|
}
|
|
|
|
update &&
|
|
|
|
update({
|
2024-04-04 11:07:51 +11:00
|
|
|
prevSegment: parent.userData.prevSegment,
|
2024-09-13 21:14:14 +10:00
|
|
|
input,
|
2024-04-04 11:07:51 +11:00
|
|
|
group: parent,
|
|
|
|
scale: factor,
|
2024-09-13 21:14:14 +10:00
|
|
|
sceneInfra,
|
2024-04-04 11:07:51 +11:00
|
|
|
})
|
|
|
|
}
|
|
|
|
const isSelected = parent?.userData?.isSelected
|
|
|
|
colorSegment(
|
|
|
|
selected,
|
2024-05-09 08:38:42 -04:00
|
|
|
isSelected
|
|
|
|
? 0x0000ff
|
|
|
|
: parent?.userData?.baseColor ||
|
|
|
|
getThemeColorForThreeJs(sceneInfra._theme)
|
2024-04-04 11:07:51 +11:00
|
|
|
)
|
|
|
|
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
|
|
|
|
mat.color.set(obj.userData.baseColor)
|
|
|
|
if (obj.userData.isSelected) mat.color.offsetHSL(0, 0, 0.2)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2024-05-24 20:54:42 +10:00
|
|
|
resetOverlays() {
|
|
|
|
sceneInfra.modelingSend({
|
|
|
|
type: 'Set Segment Overlays',
|
|
|
|
data: {
|
|
|
|
type: 'clear',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
// calculations/pure-functions/easy to test so no excuse not to
|
|
|
|
|
2025-02-12 10:22:56 +13:00
|
|
|
function prepareTruncatedAst(
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchNodePaths: PathToNode[],
|
2024-10-30 16:52:17 -04:00
|
|
|
ast: Node<Program>,
|
2025-02-12 10:22:56 +13:00
|
|
|
variables: VariableMap,
|
2024-02-11 12:59:00 +11:00
|
|
|
draftSegment?: DraftSegment
|
2024-06-24 11:45:40 -04:00
|
|
|
):
|
|
|
|
| {
|
2024-10-30 16:52:17 -04:00
|
|
|
truncatedAst: Node<Program>
|
2025-02-15 00:57:04 +11:00
|
|
|
// can I remove the below?
|
2024-06-24 11:45:40 -04:00
|
|
|
variableDeclarationName: string
|
|
|
|
}
|
|
|
|
| Error {
|
2025-02-15 00:57:04 +11:00
|
|
|
const bodyStartIndex = Number(sketchNodePaths?.[0]?.[1]?.[0]) || 0
|
|
|
|
const bodyEndIndex =
|
|
|
|
Number(sketchNodePaths[sketchNodePaths.length - 1]?.[1]?.[0]) ||
|
|
|
|
ast.body.length
|
2024-07-25 20:11:46 -04:00
|
|
|
const _ast = structuredClone(ast)
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-10-30 16:52:17 -04:00
|
|
|
const _node = getNodeFromPath<Node<VariableDeclaration>>(
|
2024-06-24 11:45:40 -04:00
|
|
|
_ast,
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchNodePaths[0] || [],
|
2024-06-24 11:45:40 -04:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (err(_node)) return _node
|
2025-02-15 00:57:04 +11:00
|
|
|
const variableDeclarationName = _node.node?.declaration?.id?.name || ''
|
2024-09-27 15:44:44 -07:00
|
|
|
const sg = sketchFromKclValue(
|
2025-02-12 10:22:56 +13:00
|
|
|
variables[variableDeclarationName],
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
variableDeclarationName
|
|
|
|
)
|
|
|
|
if (err(sg)) return sg
|
2024-10-23 12:42:54 -05:00
|
|
|
const lastSeg = sg?.paths.slice(-1)[0]
|
2024-02-11 12:59:00 +11:00
|
|
|
if (draftSegment) {
|
|
|
|
// truncatedAst needs to setup with another segment at the end
|
|
|
|
let newSegment
|
|
|
|
if (draftSegment === 'line') {
|
KCL: Use keyword arguments for line, lineTo, extrude and close (#5249)
Part of #4600.
PR: https://github.com/KittyCAD/modeling-app/pull/4826
# Changes to KCL stdlib
- `line(point, sketch, tag)` and `lineTo(point, sketch, tag)` are combined into `line(@sketch, end?, endAbsolute?, tag?)`
- `close(sketch, tag?)` is now `close(@sketch, tag?)`
- `extrude(length, sketch)` is now `extrude(@sketch, length)`
Note that if a parameter starts with `@` like `@sketch`, it doesn't have any label when called, so you call it like this:
```
sketch = startSketchAt([0, 0])
line(sketch, end = [3, 3], tag = $hi)
```
Note also that if you're using a `|>` pipeline, you can omit the `@` argument and it will be assumed to be the LHS of the `|>`. So the above could be written as
```
sketch = startSketchAt([0, 0])
|> line(end = [3, 3], tag = $hi)
```
Also changes frontend tests to use KittyCAD/kcl-samples#139 instead of its main
The regex find-and-replace I use for migrating code (note these don't work with multi-line expressions) are:
```
line\(([^=]*), %\)
line(end = $1)
line\((.*), %, (.*)\)
line(end = $1, tag = $2)
lineTo\((.*), %\)
line(endAbsolute = $1)
lineTo\((.*), %, (.*)\)
line(endAbsolute = $1, tag = $2)
extrude\((.*), %\)
extrude(length = $1)
extrude\(([^=]*), ([a-zA-Z0-9]+)\)
extrude($2, length = $1)
close\(%, (.*)\)
close(tag = $1)
```
# Selected notes from commits before I squash them all
* Fix test 'yRelative to horizontal distance'
Fixes:
- Make a lineTo helper
- Fix pathToNode to go through the labeled arg .arg property
* Fix test by changing lookups into transformMap
Parts of the code assumed that `line` is always a relative call. But
actually now it might be absolute, if it's got an `endAbsolute` parameter.
So, change whether to look up `line` or `lineTo` and the relevant absolute
or relative line types based on that parameter.
* Stop asserting on exact source ranges
When I changed line to kwargs, all the source ranges we assert on became
slightly different. I find these assertions to be very very low value.
So I'm removing them.
* Fix more tests: getConstraintType calls weren't checking if the
'line' fn was absolute or relative.
* Fixed another queryAst test
There were 2 problems:
- Test was looking for the old style of `line` call to choose an offset
for pathToNode
- Test assumed that the `tag` param was always the third one, but in
a kwarg call, you have to look it up by label
* Fix test: traverse was not handling CallExpressionKw
* Fix another test, addTagKw
addTag helper was not aware of kw args.
* Convert close from positional to kwargs
If the close() call has 0 args, or a single unlabeled arg, the parser
interprets it as a CallExpression (positional) not a CallExpressionKw.
But then if a codemod wants to add a tag to it, it tries adding a kwarg
called 'tag', which fails because the CallExpression doesn't need
kwargs inserted into it.
The fix is: change the node from CallExpression to CallExpressionKw, and
update getNodeFromPath to take a 'replacement' arg, so we can replace
the old node with the new node in the AST.
* Fix the last test
Test was looking for `lineTo` as a substring of the input KCL program.
But there's no more lineTo function, so I changed it to look for
line() with an endAbsolute arg, which is the new equivalent.
Also changed the getConstraintInfo code to look up the lineTo if using
line with endAbsolute.
* Fix many bad regex find-replaces
I wrote a regex find-and-replace which converted `line` calls from
positional to keyword calls. But it was accidentally applied to more
places than it should be, for example, angledLine, xLine and yLine calls.
Fixes this.
* Fixes test 'Basic sketch › code pane closed at start'
Problem was, the getNodeFromPath call might not actually find a callExpressionKw,
it might find a callExpression. So the `giveSketchFnCallTag` thought
it was modifying a kwargs call, but it was actually modifying a positional
call.
This meant it tried to push a labeled argument in, rather than a normal
arg, and a lot of other problems. Fixed by doing runtime typechecking.
* Fix: Optional args given with wrong type were silently ignored
Optional args don't have to be given. But if the user gives them, they
should be the right type.
Bug: if the KCL interpreter found an optional arg, which was given, but
was the wrong type, it would ignore it and pretend the arg was never
given at all. This was confusing for users.
Fix: Now if you give an optional arg, but it's the wrong type, KCL will
emit a type error just like it would for a mandatory argument.
---------
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-04 08:31:43 -06:00
|
|
|
newSegment = createCallExpressionStdLibKw('line', null, [
|
|
|
|
createLabeledArg(
|
|
|
|
'end',
|
|
|
|
createArrayExpression([createLiteral(0), createLiteral(0)])
|
|
|
|
),
|
2024-02-11 12:59:00 +11:00
|
|
|
])
|
|
|
|
} else {
|
|
|
|
newSegment = createCallExpressionStdLib('tangentialArcTo', [
|
|
|
|
createArrayExpression([
|
|
|
|
createLiteral(lastSeg.to[0]),
|
|
|
|
createLiteral(lastSeg.to[1]),
|
|
|
|
]),
|
|
|
|
createPipeSubstitution(),
|
|
|
|
])
|
|
|
|
}
|
|
|
|
;(
|
2025-02-15 00:57:04 +11:00
|
|
|
(_ast.body[bodyStartIndex] as VariableDeclaration).declaration
|
2024-02-11 12:59:00 +11:00
|
|
|
.init as PipeExpression
|
|
|
|
).body.push(newSegment)
|
|
|
|
// update source ranges to section we just added.
|
2024-09-27 15:44:44 -07:00
|
|
|
// hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketch segments
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult))
|
|
|
|
return Error('Unexpected compilation error')
|
|
|
|
const updatedSrcRangeAst = pResult.program
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
const lastPipeItem = (
|
2025-02-15 00:57:04 +11:00
|
|
|
(updatedSrcRangeAst.body[bodyStartIndex] as VariableDeclaration)
|
|
|
|
.declaration.init as PipeExpression
|
2024-02-11 12:59:00 +11:00
|
|
|
).body.slice(-1)[0]
|
|
|
|
|
|
|
|
;(
|
2025-02-15 00:57:04 +11:00
|
|
|
(_ast.body[bodyStartIndex] as VariableDeclaration).declaration
|
2024-02-11 12:59:00 +11:00
|
|
|
.init as PipeExpression
|
|
|
|
).body.slice(-1)[0].start = lastPipeItem.start
|
|
|
|
|
|
|
|
_ast.end = lastPipeItem.end
|
2025-02-15 00:57:04 +11:00
|
|
|
const varDec = _ast.body[bodyStartIndex] as Node<VariableDeclaration>
|
2024-02-11 12:59:00 +11:00
|
|
|
varDec.end = lastPipeItem.end
|
2024-12-07 07:16:04 +13:00
|
|
|
const declarator = varDec.declaration
|
2024-02-11 12:59:00 +11:00
|
|
|
declarator.end = lastPipeItem.end
|
2024-10-30 16:52:17 -04:00
|
|
|
const init = declarator.init as Node<PipeExpression>
|
2024-02-11 12:59:00 +11:00
|
|
|
init.end = lastPipeItem.end
|
|
|
|
init.body.slice(-1)[0].end = lastPipeItem.end
|
|
|
|
}
|
2024-10-30 16:52:17 -04:00
|
|
|
const truncatedAst: Node<Program> = {
|
2024-02-11 12:59:00 +11:00
|
|
|
..._ast,
|
2025-02-15 00:57:04 +11:00
|
|
|
body: structuredClone(_ast.body.slice(bodyStartIndex, bodyEndIndex + 1)),
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
return {
|
|
|
|
truncatedAst,
|
|
|
|
variableDeclarationName,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getParentGroup(
|
|
|
|
object: any,
|
2024-09-23 22:42:51 +10:00
|
|
|
stopAt: string[] = SEGMENT_BODIES
|
2024-02-11 12:59:00 +11:00
|
|
|
): Group | null {
|
|
|
|
if (stopAt.includes(object?.userData?.type)) {
|
|
|
|
return object
|
|
|
|
} else if (object?.parent) {
|
|
|
|
return getParentGroup(object.parent, stopAt)
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
function sketchFromPathToNode({
|
2024-02-11 12:59:00 +11:00
|
|
|
pathToNode,
|
|
|
|
ast,
|
2025-02-12 10:22:56 +13:00
|
|
|
variables,
|
2024-02-11 12:59:00 +11:00
|
|
|
}: {
|
|
|
|
pathToNode: PathToNode
|
|
|
|
ast: Program
|
2025-02-12 10:22:56 +13:00
|
|
|
variables: VariableMap
|
2024-09-27 15:44:44 -07:00
|
|
|
}): Sketch | null | Error {
|
2024-06-24 11:45:40 -04:00
|
|
|
const _varDec = getNodeFromPath<VariableDeclarator>(
|
2024-02-11 12:59:00 +11:00
|
|
|
kclManager.ast,
|
|
|
|
pathToNode,
|
|
|
|
'VariableDeclarator'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(_varDec)) return _varDec
|
|
|
|
const varDec = _varDec.node
|
2025-02-12 10:22:56 +13:00
|
|
|
const result = variables[varDec?.id?.name || '']
|
2024-09-27 15:44:44 -07:00
|
|
|
if (result?.type === 'Solid') {
|
2025-01-22 09:42:09 +13:00
|
|
|
return result.value.sketch
|
2024-06-21 23:50:30 -07:00
|
|
|
}
|
2024-09-27 15:44:44 -07:00
|
|
|
const sg = sketchFromKclValue(result, varDec?.id?.name)
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
if (err(sg)) {
|
|
|
|
return null
|
2024-08-07 15:15:22 -04:00
|
|
|
}
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
return sg
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function colorSegment(object: any, color: number) {
|
2024-03-02 08:48:30 +11:00
|
|
|
const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START])
|
|
|
|
if (segmentHead) {
|
|
|
|
segmentHead.traverse((child) => {
|
2024-02-11 12:59:00 +11:00
|
|
|
if (child instanceof Mesh) {
|
|
|
|
child.material.color.set(color)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
const straightSegmentBody = getParentGroup(object, SEGMENT_BODIES)
|
2024-02-11 12:59:00 +11:00
|
|
|
if (straightSegmentBody) {
|
|
|
|
straightSegmentBody.traverse((child) => {
|
2024-04-03 13:22:56 +11:00
|
|
|
if (child instanceof Mesh && !child.userData.ignoreColorChange) {
|
2024-02-11 12:59:00 +11:00
|
|
|
child.material.color.set(color)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getSketchQuaternion(
|
|
|
|
sketchPathToNode: PathToNode,
|
|
|
|
sketchNormalBackUp: [number, number, number] | null
|
2024-06-24 11:45:40 -04:00
|
|
|
): Quaternion | Error {
|
2024-09-27 15:44:44 -07:00
|
|
|
const sketch = sketchFromPathToNode({
|
2024-02-11 12:59:00 +11:00
|
|
|
pathToNode: sketchPathToNode,
|
|
|
|
ast: kclManager.ast,
|
2025-02-12 10:22:56 +13:00
|
|
|
variables: kclManager.variables,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
2024-09-27 15:44:44 -07:00
|
|
|
if (err(sketch)) return sketch
|
|
|
|
const zAxis = sketch?.on.zAxis || sketchNormalBackUp
|
|
|
|
if (!zAxis) return Error('Sketch zAxis not found')
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-03-22 10:23:04 +11:00
|
|
|
return getQuaternionFromZAxis(massageFormats(zAxis))
|
|
|
|
}
|
2025-02-15 00:57:04 +11:00
|
|
|
export async function getSketchOrientationDetails(sketch: Sketch): Promise<{
|
2024-03-22 10:23:04 +11:00
|
|
|
quat: Quaternion
|
2025-02-15 00:57:04 +11:00
|
|
|
sketchDetails: Omit<
|
|
|
|
SketchDetails & { faceId?: string },
|
|
|
|
'sketchNodePaths' | 'sketchEntryNodePath' | 'planeNodePath'
|
|
|
|
>
|
2024-03-22 10:23:04 +11:00
|
|
|
}> {
|
2024-09-27 15:44:44 -07:00
|
|
|
if (sketch.on.type === 'plane') {
|
|
|
|
const zAxis = sketch?.on.zAxis
|
2024-03-22 10:23:04 +11:00
|
|
|
return {
|
|
|
|
quat: getQuaternionFromZAxis(massageFormats(zAxis)),
|
|
|
|
sketchDetails: {
|
|
|
|
zAxis: [zAxis.x, zAxis.y, zAxis.z],
|
2024-09-27 15:44:44 -07:00
|
|
|
yAxis: [sketch.on.yAxis.x, sketch.on.yAxis.y, sketch.on.yAxis.z],
|
2024-03-22 10:23:04 +11:00
|
|
|
origin: [0, 0, 0],
|
2024-09-27 15:44:44 -07:00
|
|
|
faceId: sketch.on.id,
|
2024-03-22 10:23:04 +11:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2025-02-15 00:57:04 +11:00
|
|
|
const faceInfo = await getFaceDetails(sketch.on.id)
|
|
|
|
|
|
|
|
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
|
|
|
return Promise.reject('face info')
|
|
|
|
const { z_axis, y_axis, origin } = faceInfo
|
|
|
|
const quaternion = quaternionFromUpNForward(
|
|
|
|
new Vector3(y_axis.x, y_axis.y, y_axis.z),
|
|
|
|
new Vector3(z_axis.x, z_axis.y, z_axis.z)
|
2024-03-22 10:23:04 +11:00
|
|
|
)
|
2025-02-15 00:57:04 +11:00
|
|
|
return {
|
|
|
|
quat: quaternion,
|
|
|
|
sketchDetails: {
|
|
|
|
zAxis: [z_axis.x, z_axis.y, z_axis.z],
|
|
|
|
yAxis: [y_axis.x, y_axis.y, y_axis.z],
|
|
|
|
origin: [origin.x, origin.y, origin.z],
|
|
|
|
faceId: sketch.on.id,
|
|
|
|
},
|
|
|
|
}
|
2024-03-22 10:23:04 +11:00
|
|
|
}
|
|
|
|
|
2024-04-22 20:14:06 +10:00
|
|
|
/**
|
|
|
|
* Retrieves orientation details for a given entity representing a face (brep face or default plane).
|
|
|
|
* This function asynchronously fetches and returns the origin, x-axis, y-axis, and z-axis details
|
|
|
|
* for a specified entity ID. It is primarily used to obtain the orientation of a face in the scene,
|
|
|
|
* which is essential for calculating the correct positioning and alignment of the client side sketch.
|
|
|
|
*
|
|
|
|
* @param entityId - The ID of the entity for which orientation details are being fetched.
|
|
|
|
* @returns A promise that resolves with the orientation details of the face.
|
|
|
|
*/
|
2024-06-29 10:36:04 -07:00
|
|
|
export async function getFaceDetails(
|
2024-04-22 20:14:06 +10:00
|
|
|
entityId: string
|
2024-06-29 10:36:04 -07:00
|
|
|
): Promise<Models['GetSketchModePlane_type']> {
|
2024-04-22 20:14:06 +10:00
|
|
|
// TODO mode engine connection to allow batching returns and batch the following
|
|
|
|
await engineCommandManager.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: {
|
|
|
|
type: 'enable_sketch_mode',
|
|
|
|
adjust_camera: false,
|
|
|
|
animated: false,
|
|
|
|
ortho: false,
|
|
|
|
entity_id: entityId,
|
|
|
|
},
|
|
|
|
})
|
2024-07-23 17:13:23 +10:00
|
|
|
const resp = await engineCommandManager.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: { type: 'get_sketch_mode_plane' },
|
|
|
|
})
|
|
|
|
const faceInfo =
|
|
|
|
resp?.success &&
|
|
|
|
resp?.resp.type === 'modeling' &&
|
|
|
|
resp?.resp?.data?.modeling_response?.type === 'get_sketch_mode_plane'
|
|
|
|
? resp?.resp?.data?.modeling_response.data
|
|
|
|
: ({} as Models['GetSketchModePlane_type'])
|
2024-04-22 20:14:06 +10:00
|
|
|
await engineCommandManager.sendSceneCommand({
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
cmd: { type: 'sketch_mode_disable' },
|
|
|
|
})
|
|
|
|
return faceInfo
|
|
|
|
}
|
|
|
|
|
2024-03-22 10:23:04 +11:00
|
|
|
export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
|
2024-02-11 12:59:00 +11:00
|
|
|
const dummyCam = new PerspectiveCamera()
|
|
|
|
dummyCam.up.set(0, 0, 1)
|
2024-03-22 10:23:04 +11:00
|
|
|
dummyCam.position.copy(zAxis)
|
2024-02-11 12:59:00 +11:00
|
|
|
dummyCam.lookAt(0, 0, 0)
|
|
|
|
dummyCam.updateMatrix()
|
|
|
|
const quaternion = dummyCam.quaternion.clone()
|
|
|
|
|
|
|
|
const isVert = isQuaternionVertical(quaternion)
|
|
|
|
|
|
|
|
// because vertical quaternions are a gimbal lock, for the orbit controls
|
|
|
|
// it's best to set them explicitly to the vertical position with a known good camera up
|
2024-03-22 10:23:04 +11:00
|
|
|
if (isVert && zAxis.z < 0) {
|
2024-02-11 12:59:00 +11:00
|
|
|
quaternion.set(0, 1, 0, 0)
|
|
|
|
} else if (isVert) {
|
|
|
|
quaternion.set(0, 0, 0, 1)
|
|
|
|
}
|
|
|
|
return quaternion
|
|
|
|
}
|
|
|
|
|
2024-08-22 16:08:49 -04:00
|
|
|
function massageFormats(a: Vec3Array | Point3d): Vector3 {
|
|
|
|
return isArray(a) ? new Vector3(a[0], a[1], a[2]) : new Vector3(a.x, a.y, a.z)
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-12-17 15:12:18 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
function getSketchesInfo({
|
|
|
|
sketchNodePaths,
|
|
|
|
ast,
|
|
|
|
variables,
|
|
|
|
}: {
|
|
|
|
sketchNodePaths: PathToNode[]
|
|
|
|
ast: Node<Program>
|
|
|
|
variables: VariableMap
|
|
|
|
}): {
|
|
|
|
sketch: Sketch
|
|
|
|
pathToNode: PathToNode
|
|
|
|
}[] {
|
|
|
|
const sketchesInfo: {
|
|
|
|
sketch: Sketch
|
|
|
|
pathToNode: PathToNode
|
|
|
|
}[] = []
|
|
|
|
for (const path of sketchNodePaths) {
|
|
|
|
const sketch = sketchFromPathToNode({
|
|
|
|
pathToNode: path,
|
|
|
|
ast,
|
|
|
|
variables,
|
|
|
|
})
|
|
|
|
if (err(sketch)) continue
|
|
|
|
if (!sketch) continue
|
|
|
|
sketchesInfo.push({
|
|
|
|
sketch,
|
|
|
|
pathToNode: path,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return sketchesInfo
|
|
|
|
}
|
2024-12-17 15:12:18 -05:00
|
|
|
/**
|
|
|
|
* Given a SourceRange [x,y,boolean] create a Selections object which contains
|
|
|
|
* graphSelections with the artifact and codeRef.
|
|
|
|
* This can be passed to 'Set selection' to internally set the selection of the
|
|
|
|
* modelingMachine from code.
|
|
|
|
*/
|
|
|
|
function computeSelectionFromSourceRangeAndAST(
|
|
|
|
sourceRange: SourceRange,
|
|
|
|
ast: Node<Program>
|
|
|
|
): Selections {
|
|
|
|
const artifactGraph = engineCommandManager.artifactGraph
|
|
|
|
const artifact = getArtifactFromRange(sourceRange, artifactGraph) || undefined
|
|
|
|
const selection: Selections = {
|
|
|
|
graphSelections: [
|
|
|
|
{
|
|
|
|
artifact,
|
|
|
|
codeRef: codeRefFromRange(sourceRange, ast),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
otherSelections: [],
|
|
|
|
}
|
|
|
|
return selection
|
|
|
|
}
|
2025-02-15 00:57:04 +11:00
|
|
|
|
|
|
|
function isGroupStartProfileForCurrentProfile(sketchEntryNodePath: PathToNode) {
|
|
|
|
return (group: Group<Object3DEventMap> | null) => {
|
|
|
|
if (group?.name !== PROFILE_START) return false
|
|
|
|
const groupExpressionIndex = Number(group.userData.pathToNode[1][0])
|
|
|
|
const isProfileStartOfCurrentExpr =
|
|
|
|
groupExpressionIndex === sketchEntryNodePath[1][0]
|
|
|
|
return isProfileStartOfCurrentExpr
|
|
|
|
}
|
|
|
|
}
|