2024-02-11 12:59:00 +11:00
|
|
|
import {
|
|
|
|
BoxGeometry,
|
2025-01-16 11:10:36 -05:00
|
|
|
Color,
|
2024-02-11 12:59:00 +11:00
|
|
|
DoubleSide,
|
|
|
|
Group,
|
2024-03-04 08:14:37 +11:00
|
|
|
Intersection,
|
2024-12-20 14:30:37 -05:00
|
|
|
Line,
|
|
|
|
LineDashedMaterial,
|
|
|
|
BufferGeometry,
|
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,
|
2024-12-20 14:30:37 -05:00
|
|
|
SphereGeometry,
|
2024-02-11 12:59:00 +11:00
|
|
|
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-12-20 14:30:37 -05:00
|
|
|
CIRCLE_3_POINT_DRAFT_POINT,
|
|
|
|
CIRCLE_3_POINT_DRAFT_CIRCLE,
|
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,
|
|
|
|
ProgramMemory,
|
|
|
|
recast,
|
2024-09-27 15:44:44 -07:00
|
|
|
Sketch,
|
2024-12-16 10:34:11 -05:00
|
|
|
Solid,
|
2024-02-11 12:59:00 +11:00
|
|
|
VariableDeclaration,
|
|
|
|
VariableDeclarator,
|
2024-09-27 15:44:44 -07:00
|
|
|
sketchFromKclValue,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchFromKclValueOptional,
|
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,
|
2024-02-11 12:59:00 +11:00
|
|
|
} from 'lang/wasm'
|
2025-01-16 11:10:36 -05:00
|
|
|
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
|
2024-04-17 20:18:07 -07:00
|
|
|
import {
|
|
|
|
engineCommandManager,
|
|
|
|
kclManager,
|
|
|
|
sceneInfra,
|
|
|
|
codeManager,
|
2024-04-19 14:24:40 -07:00
|
|
|
editorManager,
|
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,
|
2025-01-16 11:10:36 -05:00
|
|
|
createCircleGeometry,
|
2024-09-13 21:14:14 +10:00
|
|
|
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,
|
|
|
|
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 {
|
2024-12-16 10:34:11 -05:00
|
|
|
addStartProfileAt,
|
2024-02-11 12:59:00 +11:00
|
|
|
createArrayExpression,
|
|
|
|
createCallExpressionStdLib,
|
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,
|
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,
|
2024-04-19 11:56:21 -04:00
|
|
|
findUniqueName,
|
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'
|
2024-12-16 10:34:11 -05:00
|
|
|
import { SegmentOverlayPayload, SketchDetails } 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'
|
2024-12-16 10:34:11 -05:00
|
|
|
import { err, Reason, reportRejection, trap } from 'lib/trap'
|
2024-07-08 16:41:00 -04:00
|
|
|
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
2024-08-22 16:08:49 -04:00
|
|
|
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
|
2024-09-23 22:42:51 +10:00
|
|
|
import { SegmentInputs } from 'lang/std/stdTypes'
|
2024-10-30 16:52:17 -04:00
|
|
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
2025-01-16 11:10:36 -05:00
|
|
|
import { LabeledArg } from 'wasm-lib/kcl/bindings/LabeledArg'
|
|
|
|
import { Literal } from 'wasm-lib/kcl/bindings/Literal'
|
2024-11-13 09:41:27 -05:00
|
|
|
import { radToDeg } from 'three/src/math/MathUtils'
|
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'
|
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,
|
|
|
|
]
|
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
|
2024-07-22 19:43:40 -04:00
|
|
|
sceneProgramMemory: ProgramMemory = ProgramMemory.empty()
|
2024-02-11 12:59:00 +11:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
})
|
|
|
|
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,
|
|
|
|
}: {
|
|
|
|
sketchDetails: SketchDetails
|
2024-12-16 10:34:11 -05:00
|
|
|
afterClick: (args: OnClickCallbackArgs) => 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
|
|
|
|
)
|
2024-12-16 10:34:11 -05:00
|
|
|
if (!axisIntersection) 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()
|
2024-12-16 10:34:11 -05:00
|
|
|
if (axisIntersection.object.name === X_AXIS) {
|
2024-10-31 07:04:38 -07:00
|
|
|
snappedPoint.setComponent(1, 0)
|
2024-12-16 10:34:11 -05:00
|
|
|
} else {
|
2024-10-31 07:04:38 -07:00
|
|
|
snappedPoint.setComponent(0, 0)
|
|
|
|
}
|
|
|
|
// 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
|
2024-12-16 10:34:11 -05:00
|
|
|
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) 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,
|
|
|
|
}
|
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
const addStartProfileAtRes = addStartProfileAt(
|
2024-07-30 14:16:53 -04:00
|
|
|
kclManager.ast,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchDetails.sketchPathToNode,
|
|
|
|
[snappedClickPoint.x, snappedClickPoint.y]
|
2024-07-30 14:16:53 -04:00
|
|
|
)
|
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
if (trap(addStartProfileAtRes)) return
|
|
|
|
const { modifiedAst } = addStartProfileAtRes
|
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
|
2024-12-16 10:34:11 -05:00
|
|
|
afterClick(args)
|
2024-07-30 14:16:53 -04:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
async setupSketch({
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode,
|
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
|
|
|
}: {
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: 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
|
|
|
programMemoryOverride: ProgramMemory
|
2024-12-16 10:34:11 -05:00
|
|
|
sketch: Sketch
|
2024-03-25 15:20:43 +11:00
|
|
|
variableDeclarationName: string
|
|
|
|
}> {
|
2024-06-24 11:45:40 -04:00
|
|
|
const prepared = this.prepareTruncatedMemoryAndAst(
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode || [],
|
2024-06-24 11:45:40 -04:00
|
|
|
maybeModdedAst
|
|
|
|
)
|
|
|
|
if (err(prepared)) return Promise.reject(prepared)
|
2024-02-11 12:59:00 +11:00
|
|
|
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
2024-06-24 11:45:40 -04:00
|
|
|
prepared
|
|
|
|
|
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,
|
2024-12-05 19:51:06 -08:00
|
|
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
2024-02-11 12:59:00 +11:00
|
|
|
programMemoryOverride,
|
|
|
|
})
|
2024-10-09 19:38:40 -04:00
|
|
|
const programMemory = execState.memory
|
2024-12-16 10:34:11 -05:00
|
|
|
const sketch = sketchFromPathToNode({
|
|
|
|
pathToNode: sketchPathToNode,
|
2024-04-03 13:22:56 +11:00
|
|
|
ast: maybeModdedAst,
|
2024-02-11 12:59:00 +11:00
|
|
|
programMemory,
|
|
|
|
})
|
2024-12-16 10:34:11 -05:00
|
|
|
if (err(sketch)) return Promise.reject(sketch)
|
|
|
|
if (!sketch) return Promise.reject('sketch not found')
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
if (!isArray(sketch?.paths))
|
|
|
|
return {
|
|
|
|
truncatedAst,
|
|
|
|
programMemoryOverride,
|
|
|
|
sketch,
|
|
|
|
variableDeclarationName,
|
|
|
|
}
|
2024-02-14 05:35:05 +11:00
|
|
|
this.sceneProgramMemory = programMemory
|
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,
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode: sketchPathToNode,
|
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-12-16 10:34:11 -05:00
|
|
|
const segPathToNode = getNodePathFromSourceRange(
|
|
|
|
maybeModdedAst,
|
|
|
|
sourceRangeFromRust(sketch.start.__geoMeta.sourceRange)
|
|
|
|
)
|
|
|
|
if (sketch?.paths?.[0]?.type !== 'Circle') {
|
|
|
|
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)
|
|
|
|
})
|
|
|
|
group.add(_profileStart)
|
|
|
|
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
|
|
|
|
}
|
2024-05-24 20:54:42 +10:00
|
|
|
const callbacks: (() => SegmentOverlayPayload | null)[] = []
|
2024-12-16 10:34:11 -05:00
|
|
|
sketch.paths.forEach((segment, index) => {
|
|
|
|
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-02-11 12:59:00 +11:00
|
|
|
)
|
2024-12-16 10:34:11 -05:00
|
|
|
if (
|
|
|
|
draftExpressionsIndices &&
|
|
|
|
(sketch.paths[index - 1] || sketch.start)
|
|
|
|
) {
|
|
|
|
const previousSegment = sketch.paths[index - 1] || sketch.start
|
|
|
|
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
2024-04-19 11:56:21 -04:00
|
|
|
maybeModdedAst,
|
2024-12-16 10:34:11 -05:00
|
|
|
sourceRangeFromRust(previousSegment.__geoMeta.sourceRange)
|
2024-03-04 08:14:37 +11:00
|
|
|
)
|
2024-12-16 10:34:11 -05:00
|
|
|
const bodyIndex = previousSegmentPathToNode[1][0]
|
|
|
|
segPathToNode = getNodePathFromSourceRange(
|
|
|
|
truncatedAst,
|
|
|
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
2024-12-06 13:57:31 +13:00
|
|
|
)
|
2024-12-16 10:34:11 -05:00
|
|
|
segPathToNode[1][0] = bodyIndex
|
|
|
|
}
|
|
|
|
const isDraftSegment =
|
|
|
|
draftExpressionsIndices &&
|
|
|
|
index <= draftExpressionsIndices.end &&
|
|
|
|
index >= draftExpressionsIndices.start
|
|
|
|
const isSelected = selectionRanges?.graphSelections.some((selection) =>
|
|
|
|
isOverlap(
|
|
|
|
selection?.codeRef?.range,
|
|
|
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
2024-12-14 09:57:33 +11:00
|
|
|
)
|
2024-12-16 10:34:11 -05:00
|
|
|
)
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
let seg: Group
|
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 _node1 = getNodeFromPath<Node<CallExpression | CallExpressionKw>>(
|
2024-12-16 10:34:11 -05:00
|
|
|
maybeModdedAst,
|
|
|
|
segPathToNode,
|
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-12-16 10:34:11 -05:00
|
|
|
)
|
2024-12-17 15:12:18 -05:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
if (err(_node1)) return
|
|
|
|
const callExpName = _node1.node?.callee?.name
|
|
|
|
|
|
|
|
const initSegment =
|
|
|
|
segment.type === 'TangentialArcTo'
|
|
|
|
? segmentUtils.tangentialArcTo.init
|
|
|
|
: segment.type === 'Circle'
|
|
|
|
? segmentUtils.circle.init
|
|
|
|
: segmentUtils.straight.init
|
|
|
|
const input: SegmentInputs =
|
|
|
|
segment.type === 'Circle'
|
|
|
|
? {
|
|
|
|
type: 'arc-segment',
|
|
|
|
from: segment.from,
|
|
|
|
center: segment.center,
|
|
|
|
radius: segment.radius,
|
|
|
|
}
|
|
|
|
: {
|
|
|
|
type: 'straight-segment',
|
|
|
|
from: segment.from,
|
|
|
|
to: segment.to,
|
|
|
|
}
|
2024-12-17 15:12:18 -05:00
|
|
|
|
|
|
|
const startRange = _node1.node.start
|
|
|
|
const endRange = _node1.node.end
|
2025-01-17 14:34:36 -05:00
|
|
|
const sourceRange = topLevelRange(startRange, endRange)
|
2024-12-17 15:12:18 -05:00
|
|
|
const selection: Selections = computeSelectionFromSourceRangeAndAST(
|
|
|
|
sourceRange,
|
|
|
|
maybeModdedAst
|
|
|
|
)
|
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
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,
|
2024-12-17 15:12:18 -05:00
|
|
|
selection,
|
2024-12-14 09:57:33 +11:00
|
|
|
})
|
2024-12-16 10:34:11 -05:00
|
|
|
if (err(result)) return
|
|
|
|
const { group: _group, updateOverlaysCallback } = result
|
|
|
|
seg = _group
|
|
|
|
callbacks.push(updateOverlaysCallback)
|
|
|
|
seg.layers.set(SKETCH_LAYER)
|
|
|
|
seg.traverse((child) => {
|
|
|
|
child.layers.set(SKETCH_LAYER)
|
|
|
|
})
|
|
|
|
|
|
|
|
group.add(seg)
|
|
|
|
this.activeSegments[JSON.stringify(segPathToNode)] = seg
|
|
|
|
})
|
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,
|
|
|
|
programMemoryOverride,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketch,
|
2024-03-25 15:20:43 +11:00
|
|
|
variableDeclarationName,
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
updateAstAndRejigSketch = async (
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: PathToNode,
|
|
|
|
modifiedAst: Node<Program>,
|
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
|
|
|
) => {
|
2024-06-24 11:45:40 -04:00
|
|
|
const nextAst = await kclManager.updateAst(modifiedAst, false)
|
2024-12-16 10:34:11 -05:00
|
|
|
await 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({
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode,
|
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,
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode: sketchPathToNode,
|
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 (
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: 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,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode || [],
|
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(
|
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
|
|
|
kclManager.programMemory.get(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
|
|
|
|
2024-10-23 12:42:54 -05:00
|
|
|
const index = sg.paths.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
2024-04-03 13:22:56 +11:00
|
|
|
const mod = addNewSketchLn({
|
|
|
|
node: _ast,
|
2024-03-25 15:20:43 +11:00
|
|
|
programMemory: kclManager.programMemory,
|
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,
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode: sketchPathToNode,
|
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 }
|
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
|
2024-04-03 13:22:56 +11:00
|
|
|
sceneInfra.resetMouseListeners()
|
2024-06-29 18:10:07 -07:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
const { truncatedAst, programMemoryOverride, sketch } =
|
|
|
|
await this.setupSketch({
|
|
|
|
sketchPathToNode,
|
|
|
|
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]))
|
|
|
|
.find((a) => a?.name === PROFILE_START)
|
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
let modifiedAst
|
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,
|
|
|
|
programMemory: kclManager.programMemory,
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode: sketchPathToNode,
|
2024-05-23 00:53:15 -04:00
|
|
|
expressions: [
|
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
|
|
|
lastSegment.type === 'TangentialArcTo'
|
|
|
|
? createCallExpressionStdLib('tangentialArcTo', [
|
|
|
|
originCoords,
|
|
|
|
createPipeSubstitution(),
|
|
|
|
])
|
|
|
|
: createCallExpressionStdLibKw('line', null, [
|
|
|
|
createLabeledArg('endAbsolute', 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,
|
|
|
|
programMemory: kclManager.programMemory,
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode: sketchPathToNode,
|
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
|
2024-12-16 10:34:11 -05:00
|
|
|
if (lastSegment.type === '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,
|
|
|
|
programMemory: kclManager.programMemory,
|
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,
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode: sketchPathToNode,
|
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) {
|
2024-12-16 10:34:11 -05:00
|
|
|
sceneInfra.modelingSend({ type: 'CancelSketch' })
|
2024-05-23 00:53:15 -04:00
|
|
|
} else {
|
2024-11-16 16:49:44 -05:00
|
|
|
await this.setupDraftSegment(
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode,
|
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) => {
|
|
|
|
this.onDragSegment({
|
|
|
|
intersection2d: args.intersectionPoint.twoD,
|
2024-12-16 10:34:11 -05:00
|
|
|
object: Object.values(this.activeSegments).slice(-1)[0],
|
2024-03-25 15:20:43 +11:00
|
|
|
intersects: args.intersects,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode,
|
2024-03-25 15:20:43 +11:00
|
|
|
draftInfo: {
|
|
|
|
truncatedAst,
|
|
|
|
programMemoryOverride,
|
|
|
|
variableDeclarationName,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-04-19 11:56:21 -04:00
|
|
|
setupDraftRectangle = async (
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: 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]
|
2024-12-16 10:34:11 -05:00
|
|
|
) => {
|
2024-07-25 20:11:46 -04:00
|
|
|
let _ast = structuredClone(kclManager.ast)
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
const _node1 = getNodeFromPath<VariableDeclaration>(
|
2024-06-24 11:45:40 -04:00
|
|
|
_ast,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode || [],
|
|
|
|
'VariableDeclaration'
|
2024-12-14 09:57:33 +11:00
|
|
|
)
|
2024-12-16 10:34:11 -05:00
|
|
|
if (trap(_node1)) return Promise.reject(_node1)
|
|
|
|
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
|
|
|
const startSketchOn = _node1.node?.declaration
|
|
|
|
const startSketchOnInit = startSketchOn?.init
|
2024-12-14 09:57:33 +11:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
const tags: [string, string, string] = [
|
|
|
|
findUniqueName(_ast, 'rectangleSegmentA'),
|
|
|
|
findUniqueName(_ast, 'rectangleSegmentB'),
|
|
|
|
findUniqueName(_ast, 'rectangleSegmentC'),
|
|
|
|
]
|
2024-12-14 09:57:33 +11:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
startSketchOn.init = createPipeExpression([
|
|
|
|
startSketchOnInit,
|
|
|
|
...getRectangleCallExpressions(rectangleOrigin, tags),
|
|
|
|
])
|
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
|
|
|
|
|
|
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode,
|
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
|
2024-12-16 10:34:11 -05:00
|
|
|
const pathToNodeTwo = structuredClone(sketchPathToNode)
|
|
|
|
pathToNodeTwo[1][0] = 0
|
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,
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNodeTwo || [],
|
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') {
|
2024-12-16 10:34:11 -05:00
|
|
|
updateRectangleSketch(sketchInit, x, y, tags[0])
|
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,
|
2024-12-05 19:51:06 -08:00
|
|
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
2024-04-19 11:56:21 -04:00
|
|
|
programMemoryOverride,
|
|
|
|
})
|
2024-10-09 19:38:40 -04:00
|
|
|
const programMemory = execState.memory
|
2024-04-19 11:56:21 -04:00
|
|
|
this.sceneProgramMemory = programMemory
|
2024-12-16 10:34:11 -05:00
|
|
|
const sketch = sketchFromKclValue(
|
|
|
|
programMemory.get(variableDeclarationName),
|
|
|
|
variableDeclarationName
|
|
|
|
)
|
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)
|
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
2024-04-19 11:56:21 -04:00
|
|
|
sgPaths.forEach((seg, index) =>
|
2024-12-16 10:34:11 -05:00
|
|
|
this.updateSegment(seg, index, 0, _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,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode || [],
|
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
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
updateRectangleSketch(sketchInit, x, y, tags[0])
|
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-12-16 10:34:11 -05:00
|
|
|
const { execState } = await executeAst({
|
|
|
|
ast: _ast,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
|
|
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
|
|
|
programMemoryOverride,
|
|
|
|
})
|
|
|
|
const programMemory = execState.memory
|
|
|
|
|
|
|
|
// Prepare to update the THREEjs scene
|
|
|
|
this.sceneProgramMemory = programMemory
|
|
|
|
const sketch = sketchFromKclValue(
|
|
|
|
programMemory.get(variableDeclarationName),
|
|
|
|
variableDeclarationName
|
|
|
|
)
|
|
|
|
if (err(sketch)) return
|
|
|
|
const sgPaths = sketch.paths
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
|
|
|
// Update the starting segment of the THREEjs scene
|
|
|
|
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
|
|
|
// Update the rest of the segments of the THREEjs scene
|
|
|
|
sgPaths.forEach((seg, index) =>
|
|
|
|
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketch)
|
|
|
|
)
|
2024-04-19 11:56:21 -04:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
2024-11-18 10:04:09 -05:00
|
|
|
setupDraftCenterRectangle = async (
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: 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]
|
2024-12-16 10:34:11 -05:00
|
|
|
) => {
|
2024-11-18 10:04:09 -05:00
|
|
|
let _ast = structuredClone(kclManager.ast)
|
2024-12-16 10:34:11 -05:00
|
|
|
const _node1 = getNodeFromPath<VariableDeclaration>(
|
2024-11-18 10:04:09 -05:00
|
|
|
_ast,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode || [],
|
|
|
|
'VariableDeclaration'
|
2024-12-14 09:57:33 +11:00
|
|
|
)
|
2024-12-16 10:34:11 -05:00
|
|
|
if (trap(_node1)) return Promise.reject(_node1)
|
2024-11-18 10:04:09 -05:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
// startSketchOn already exists
|
|
|
|
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
|
|
|
const startSketchOn = _node1.node?.declaration
|
|
|
|
const startSketchOnInit = startSketchOn?.init
|
2024-12-14 09:57:33 +11:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
const tags: [string, string, string] = [
|
|
|
|
findUniqueName(_ast, 'rectangleSegmentA'),
|
|
|
|
findUniqueName(_ast, 'rectangleSegmentB'),
|
|
|
|
findUniqueName(_ast, 'rectangleSegmentC'),
|
|
|
|
]
|
2024-12-14 09:57:33 +11:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
startSketchOn.init = createPipeExpression([
|
|
|
|
startSketchOnInit,
|
|
|
|
...getRectangleCallExpressions(rectangleOrigin, tags),
|
2024-12-14 09:57:33 +11:00
|
|
|
])
|
2024-12-16 10:34:11 -05: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
|
|
|
|
|
|
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode,
|
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
|
2024-12-16 10:34:11 -05:00
|
|
|
const pathToNodeTwo = structuredClone(sketchPathToNode)
|
|
|
|
pathToNodeTwo[1][0] = 0
|
2024-11-18 10:04:09 -05:00
|
|
|
|
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
truncatedAst,
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNodeTwo || [],
|
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,
|
2024-12-16 10:34:11 -05:00
|
|
|
tags[0],
|
2024-11-18 10:04:09 -05:00
|
|
|
rectangleOrigin[0],
|
|
|
|
rectangleOrigin[1]
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const { execState } = await executeAst({
|
|
|
|
ast: truncatedAst,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
2024-12-05 19:51:06 -08:00
|
|
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
2024-11-18 10:04:09 -05:00
|
|
|
programMemoryOverride,
|
|
|
|
})
|
|
|
|
const programMemory = execState.memory
|
|
|
|
this.sceneProgramMemory = programMemory
|
2024-12-16 10:34:11 -05:00
|
|
|
const sketch = sketchFromKclValue(
|
|
|
|
programMemory.get(variableDeclarationName),
|
|
|
|
variableDeclarationName
|
|
|
|
)
|
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)
|
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
2024-11-18 10:04:09 -05:00
|
|
|
sgPaths.forEach((seg, index) =>
|
2024-12-16 10:34:11 -05:00
|
|
|
this.updateSegment(seg, index, 0, _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,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode || [],
|
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,
|
2024-12-16 10:34:11 -05:00
|
|
|
tags[0],
|
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-12-16 10:34:11 -05:00
|
|
|
const { execState } = await executeAst({
|
|
|
|
ast: _ast,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
|
|
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
|
|
|
programMemoryOverride,
|
|
|
|
})
|
|
|
|
const programMemory = execState.memory
|
|
|
|
|
|
|
|
// Prepare to update the THREEjs scene
|
|
|
|
this.sceneProgramMemory = programMemory
|
|
|
|
const sketch = sketchFromKclValue(
|
|
|
|
programMemory.get(variableDeclarationName),
|
|
|
|
variableDeclarationName
|
|
|
|
)
|
|
|
|
if (err(sketch)) return
|
|
|
|
const sgPaths = sketch.paths
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
|
|
|
// Update the starting segment of the THREEjs scene
|
|
|
|
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
|
|
|
// Update the rest of the segments of the THREEjs scene
|
|
|
|
sgPaths.forEach((seg, index) =>
|
|
|
|
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketch)
|
|
|
|
)
|
2024-11-18 10:04:09 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
2024-12-20 14:30:37 -05:00
|
|
|
|
|
|
|
// lee: Well, it appears all our code in sceneEntities each act as their own
|
|
|
|
// kind of classes. In this case, I'll keep utility functions pertaining to
|
|
|
|
// circle3Point here. Feel free to extract as needed.
|
2025-01-06 14:08:18 -05:00
|
|
|
entryDraftCircle3Point = (
|
|
|
|
done: () => void,
|
2024-12-20 14:30:37 -05:00
|
|
|
startSketchOnASTNodePath: PathToNode,
|
|
|
|
forward: Vector3,
|
|
|
|
up: Vector3,
|
|
|
|
sketchOrigin: Vector3
|
2025-01-06 14:08:18 -05:00
|
|
|
): (() => void) => {
|
2024-12-20 14:30:37 -05:00
|
|
|
// lee: Not a fan we need to re-iterate this dummy object all over the place
|
|
|
|
// just to get the scale but okie dokie.
|
|
|
|
const dummy = new Mesh()
|
|
|
|
dummy.position.set(0, 0, 0)
|
|
|
|
const scale = sceneInfra.getClientSceneScaleFactor(dummy)
|
|
|
|
|
|
|
|
const orientation = quaternionFromUpNForward(up, forward)
|
|
|
|
|
|
|
|
// Reminder: the intersection plane is the primary way to derive a XY
|
|
|
|
// position from a mouse click in ThreeJS.
|
|
|
|
// Here, we position and orient so it's facing the viewer.
|
|
|
|
this.intersectionPlane!.setRotationFromQuaternion(orientation)
|
|
|
|
this.intersectionPlane!.position.copy(sketchOrigin)
|
|
|
|
|
|
|
|
// Keep track of points in the scene with their ThreeJS ids.
|
|
|
|
const points: Map<number, Vector2> = new Map()
|
|
|
|
|
|
|
|
// Keep a reference so we can destroy and recreate as needed.
|
|
|
|
let groupCircle: Group | undefined
|
|
|
|
|
|
|
|
// Add our new group to the list of groups to render
|
|
|
|
const groupOfDrafts = new Group()
|
|
|
|
groupOfDrafts.name = 'circle-3-point-group'
|
|
|
|
groupOfDrafts.position.copy(sketchOrigin)
|
2025-01-16 11:10:36 -05:00
|
|
|
|
2024-12-20 14:30:37 -05:00
|
|
|
// lee: I'm keeping this here as a developer gotchya:
|
2025-01-16 11:10:36 -05:00
|
|
|
// If you use 3D points, do not rotate anything.
|
|
|
|
// If you use 2D points (easier to deal with, generally do this!), then
|
|
|
|
// rotate the group just like this! Remember to rotate other groups too!
|
|
|
|
groupOfDrafts.setRotationFromQuaternion(orientation)
|
2024-12-20 14:30:37 -05:00
|
|
|
this.scene.add(groupOfDrafts)
|
|
|
|
|
2025-01-16 11:10:36 -05:00
|
|
|
// How large the points on the circle will render as
|
|
|
|
const DRAFT_POINT_RADIUS = 10 // px
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-01-16 11:10:36 -05:00
|
|
|
// The target of our dragging
|
|
|
|
let target: Object3D | undefined = undefined
|
|
|
|
|
|
|
|
// The KCL this will generate.
|
|
|
|
const kclCircle3Point = parse(`circleThreePoint(
|
|
|
|
p1 = [0.0, 0.0],
|
|
|
|
p2 = [0.0, 0.0],
|
|
|
|
p3 = [0.0, 0.0],
|
|
|
|
)`)
|
|
|
|
|
|
|
|
const createPoint = (
|
|
|
|
center: Vector3,
|
|
|
|
opts?: { noInteraction?: boolean }
|
|
|
|
): Mesh => {
|
2024-12-20 14:30:37 -05:00
|
|
|
const geometry = new SphereGeometry(DRAFT_POINT_RADIUS)
|
|
|
|
const color = getThemeColorForThreeJs(sceneInfra._theme)
|
2025-01-16 11:10:36 -05:00
|
|
|
|
|
|
|
const material = new MeshBasicMaterial({
|
|
|
|
color: opts?.noInteraction
|
|
|
|
? sceneInfra._theme === 'light'
|
|
|
|
? new Color(color).multiplyScalar(0.15)
|
|
|
|
: new Color(0x010101).multiplyScalar(2000)
|
|
|
|
: color,
|
|
|
|
})
|
2024-12-20 14:30:37 -05:00
|
|
|
|
|
|
|
const mesh = new Mesh(geometry, material)
|
2025-01-16 11:10:36 -05:00
|
|
|
mesh.userData = {
|
|
|
|
type: opts?.noInteraction ? 'ghost' : CIRCLE_3_POINT_DRAFT_POINT,
|
|
|
|
}
|
|
|
|
mesh.renderOrder = 1000
|
2024-12-20 14:30:37 -05:00
|
|
|
mesh.layers.set(SKETCH_LAYER)
|
|
|
|
mesh.position.copy(center)
|
|
|
|
mesh.scale.set(scale, scale, scale)
|
|
|
|
mesh.renderOrder = 100
|
|
|
|
|
2025-01-16 11:10:36 -05:00
|
|
|
return mesh
|
2024-12-20 14:30:37 -05:00
|
|
|
}
|
|
|
|
|
2025-01-16 11:10:36 -05:00
|
|
|
const createCircle3PointGraphic = async (
|
|
|
|
points: Vector2[],
|
|
|
|
center: Vector2,
|
|
|
|
radius: number
|
|
|
|
) => {
|
|
|
|
if (
|
|
|
|
Number.isNaN(radius) ||
|
|
|
|
Number.isNaN(center.x) ||
|
|
|
|
Number.isNaN(center.y)
|
|
|
|
)
|
|
|
|
return
|
2024-12-20 14:30:37 -05:00
|
|
|
|
|
|
|
const color = getThemeColorForThreeJs(sceneInfra._theme)
|
2025-01-16 11:10:36 -05:00
|
|
|
const lineCircle = createCircleGeometry({
|
|
|
|
center: [center.x, center.y],
|
|
|
|
radius,
|
|
|
|
color,
|
|
|
|
isDashed: false,
|
|
|
|
scale: 1,
|
2024-12-20 14:30:37 -05:00
|
|
|
})
|
2025-01-16 11:10:36 -05:00
|
|
|
lineCircle.userData = { type: CIRCLE_3_POINT_DRAFT_CIRCLE }
|
|
|
|
lineCircle.layers.set(SKETCH_LAYER)
|
|
|
|
// devnote: it's a mistake to use these with EllipseCurve :)
|
|
|
|
// lineCircle.position.set(center.x, center.y, 0)
|
|
|
|
// lineCircle.scale.set(scale, scale, scale)
|
2024-12-20 14:30:37 -05:00
|
|
|
|
|
|
|
if (groupCircle) groupOfDrafts.remove(groupCircle)
|
|
|
|
groupCircle = new Group()
|
|
|
|
groupCircle.renderOrder = 1
|
2025-01-16 11:10:36 -05:00
|
|
|
groupCircle.add(lineCircle)
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-01-16 11:10:36 -05:00
|
|
|
const pointMesh = createPoint(new Vector3(center.x, center.y, 0), {
|
|
|
|
noInteraction: true,
|
|
|
|
})
|
|
|
|
groupCircle.add(pointMesh)
|
2024-12-20 14:30:37 -05:00
|
|
|
|
|
|
|
const geometryPolyLine = new BufferGeometry().setFromPoints([
|
2025-01-16 11:10:36 -05:00
|
|
|
...points.map((p) => new Vector3(p.x, p.y, 0)),
|
|
|
|
new Vector3(points[0].x, points[0].y, 0),
|
2024-12-20 14:30:37 -05:00
|
|
|
])
|
|
|
|
const materialPolyLine = new LineDashedMaterial({
|
|
|
|
color,
|
2025-01-16 11:10:36 -05:00
|
|
|
scale: 1 / scale,
|
2024-12-20 14:30:37 -05:00
|
|
|
dashSize: 6,
|
|
|
|
gapSize: 6,
|
|
|
|
})
|
|
|
|
const meshPolyLine = new Line(geometryPolyLine, materialPolyLine)
|
|
|
|
meshPolyLine.computeLineDistances()
|
|
|
|
groupCircle.add(meshPolyLine)
|
|
|
|
|
|
|
|
groupOfDrafts.add(groupCircle)
|
|
|
|
}
|
|
|
|
|
2025-01-16 11:10:36 -05:00
|
|
|
const insertCircle3PointKclIntoAstSnapshot = (
|
|
|
|
points: Vector2[]
|
|
|
|
): Program => {
|
|
|
|
if (err(kclCircle3Point) || kclCircle3Point.program === null)
|
|
|
|
return kclManager.ast
|
|
|
|
if (kclCircle3Point.program.body[0].type !== 'ExpressionStatement')
|
|
|
|
return kclManager.ast
|
|
|
|
if (
|
|
|
|
kclCircle3Point.program.body[0].expression.type !== 'CallExpressionKw'
|
|
|
|
)
|
|
|
|
return kclManager.ast
|
|
|
|
|
|
|
|
const arg = (x: LabeledArg): Literal[] | undefined => {
|
|
|
|
if (
|
|
|
|
'arg' in x &&
|
|
|
|
'elements' in x.arg &&
|
|
|
|
x.arg.type === 'ArrayExpression'
|
|
|
|
) {
|
|
|
|
if (x.arg.elements.every((x) => x.type === 'Literal')) {
|
|
|
|
return x.arg.elements
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
const kclCircle3PointArgs =
|
|
|
|
kclCircle3Point.program.body[0].expression.arguments
|
|
|
|
|
|
|
|
const arg0 = arg(kclCircle3PointArgs[0])
|
|
|
|
if (!arg0) return kclManager.ast
|
2025-01-22 08:29:30 +13:00
|
|
|
arg0[0].value = { value: points[0].x, suffix: 'None' }
|
2025-01-16 11:10:36 -05:00
|
|
|
arg0[0].raw = points[0].x.toString()
|
2025-01-22 08:29:30 +13:00
|
|
|
arg0[1].value = { value: points[0].y, suffix: 'None' }
|
2025-01-16 11:10:36 -05:00
|
|
|
arg0[1].raw = points[0].y.toString()
|
|
|
|
|
|
|
|
const arg1 = arg(kclCircle3PointArgs[1])
|
|
|
|
if (!arg1) return kclManager.ast
|
2025-01-22 08:29:30 +13:00
|
|
|
arg1[0].value = { value: points[1].x, suffix: 'None' }
|
2025-01-16 11:10:36 -05:00
|
|
|
arg1[0].raw = points[1].x.toString()
|
2025-01-22 08:29:30 +13:00
|
|
|
arg1[1].value = { value: points[1].y, suffix: 'None' }
|
2025-01-16 11:10:36 -05:00
|
|
|
arg1[1].raw = points[1].y.toString()
|
|
|
|
|
|
|
|
const arg2 = arg(kclCircle3PointArgs[2])
|
|
|
|
if (!arg2) return kclManager.ast
|
2025-01-22 08:29:30 +13:00
|
|
|
arg2[0].value = { value: points[2].x, suffix: 'None' }
|
2025-01-16 11:10:36 -05:00
|
|
|
arg2[0].raw = points[2].x.toString()
|
2025-01-22 08:29:30 +13:00
|
|
|
arg2[1].value = { value: points[2].y, suffix: 'None' }
|
2025-01-16 11:10:36 -05:00
|
|
|
arg2[1].raw = points[2].y.toString()
|
|
|
|
|
|
|
|
const astSnapshot = structuredClone(kclManager.ast)
|
|
|
|
const startSketchOnASTNode = getNodeFromPath<VariableDeclaration>(
|
|
|
|
astSnapshot,
|
|
|
|
startSketchOnASTNodePath,
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (err(startSketchOnASTNode)) return astSnapshot
|
|
|
|
|
|
|
|
// It's possible we're already dealing with a PipeExpression.
|
|
|
|
// Modify the current one.
|
|
|
|
if (
|
|
|
|
startSketchOnASTNode.node.declaration.init.type === 'PipeExpression' &&
|
|
|
|
startSketchOnASTNode.node.declaration.init.body[1].type ===
|
|
|
|
'CallExpressionKw' &&
|
|
|
|
startSketchOnASTNode.node.declaration.init.body.length >= 2
|
|
|
|
) {
|
|
|
|
startSketchOnASTNode.node.declaration.init.body[1].arguments =
|
|
|
|
kclCircle3Point.program.body[0].expression.arguments
|
|
|
|
} else {
|
|
|
|
// Clone a new node based on the old, and replace the old with the new.
|
|
|
|
const clonedStartSketchOnASTNode = structuredClone(startSketchOnASTNode)
|
|
|
|
startSketchOnASTNode.node.declaration.init = createPipeExpression([
|
|
|
|
clonedStartSketchOnASTNode.node.declaration.init,
|
|
|
|
kclCircle3Point.program.body[0].expression,
|
|
|
|
])
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the `Program`
|
|
|
|
return astSnapshot
|
|
|
|
}
|
|
|
|
|
|
|
|
const updateCircle3Point = async (opts?: { execute?: true }) => {
|
|
|
|
const points_ = Array.from(points.values())
|
|
|
|
const circleParams = calculate_circle_from_3_points(
|
|
|
|
points_[0].x,
|
|
|
|
points_[0].y,
|
|
|
|
points_[1].x,
|
|
|
|
points_[1].y,
|
|
|
|
points_[2].x,
|
|
|
|
points_[2].y
|
|
|
|
)
|
|
|
|
|
|
|
|
if (Number.isNaN(circleParams.radius)) return
|
|
|
|
|
|
|
|
await createCircle3PointGraphic(
|
|
|
|
points_,
|
|
|
|
new Vector2(circleParams.center_x, circleParams.center_y),
|
|
|
|
circleParams.radius
|
|
|
|
)
|
|
|
|
const astWithNewCode = insertCircle3PointKclIntoAstSnapshot(points_)
|
|
|
|
const codeAsString = recast(astWithNewCode)
|
|
|
|
if (err(codeAsString)) return
|
|
|
|
codeManager.updateCodeStateEditor(codeAsString)
|
|
|
|
}
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-01-06 14:08:18 -05:00
|
|
|
const cleanupFn = () => {
|
|
|
|
this.scene.remove(groupOfDrafts)
|
|
|
|
}
|
|
|
|
|
2025-01-16 11:10:36 -05:00
|
|
|
// The AST node we extracted earlier may already have a circleThreePoint!
|
|
|
|
// Use the points in the AST as starting points.
|
|
|
|
const astSnapshot = structuredClone(kclManager.ast)
|
|
|
|
const maybeVariableDeclaration = getNodeFromPath<VariableDeclaration>(
|
|
|
|
astSnapshot,
|
|
|
|
startSketchOnASTNodePath,
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (err(maybeVariableDeclaration))
|
|
|
|
return () => {
|
|
|
|
done()
|
|
|
|
}
|
|
|
|
|
|
|
|
const maybeCallExpressionKw = maybeVariableDeclaration.node.declaration.init
|
|
|
|
if (
|
|
|
|
maybeCallExpressionKw.type === 'PipeExpression' &&
|
|
|
|
maybeCallExpressionKw.body[1].type === 'CallExpressionKw' &&
|
|
|
|
maybeCallExpressionKw.body[1]?.callee.name === 'circleThreePoint'
|
|
|
|
) {
|
|
|
|
maybeCallExpressionKw?.body[1].arguments
|
|
|
|
.map(
|
|
|
|
({ arg }: any) =>
|
|
|
|
new Vector2(arg.elements[0].value, arg.elements[1].value)
|
|
|
|
)
|
|
|
|
.forEach((point: Vector2) => {
|
|
|
|
const pointMesh = createPoint(new Vector3(point.x, point.y, 0))
|
|
|
|
groupOfDrafts.add(pointMesh)
|
|
|
|
points.set(pointMesh.id, point)
|
|
|
|
})
|
|
|
|
void updateCircle3Point()
|
|
|
|
}
|
|
|
|
|
2024-12-20 14:30:37 -05:00
|
|
|
sceneInfra.setCallbacks({
|
|
|
|
async onDrag(args) {
|
|
|
|
const draftPointsIntersected = args.intersects.filter(
|
|
|
|
(intersected) =>
|
|
|
|
intersected.object.userData.type === CIRCLE_3_POINT_DRAFT_POINT
|
|
|
|
)
|
|
|
|
|
|
|
|
const firstPoint = draftPointsIntersected[0]
|
|
|
|
if (firstPoint && !target) {
|
|
|
|
target = firstPoint.object
|
|
|
|
}
|
|
|
|
|
|
|
|
// The user was off their mark! Missed the object to select.
|
|
|
|
if (!target) return
|
|
|
|
|
2025-01-16 11:10:36 -05:00
|
|
|
target.position.copy(
|
|
|
|
new Vector3(
|
|
|
|
args.intersectionPoint.twoD.x,
|
|
|
|
args.intersectionPoint.twoD.y,
|
|
|
|
0
|
|
|
|
)
|
|
|
|
)
|
2024-12-20 14:30:37 -05:00
|
|
|
points.set(target.id, args.intersectionPoint.twoD)
|
2025-01-16 11:10:36 -05:00
|
|
|
|
|
|
|
if (points.size <= 2) return
|
|
|
|
|
|
|
|
await updateCircle3Point()
|
2024-12-20 14:30:37 -05:00
|
|
|
},
|
|
|
|
async onDragEnd(_args) {
|
|
|
|
target = undefined
|
|
|
|
},
|
|
|
|
async onClick(args) {
|
|
|
|
if (points.size >= 3) return
|
|
|
|
if (!args.intersectionPoint) return
|
|
|
|
|
2025-01-16 11:10:36 -05:00
|
|
|
const pointMesh = createPoint(
|
|
|
|
new Vector3(
|
|
|
|
args.intersectionPoint.twoD.x,
|
|
|
|
args.intersectionPoint.twoD.y,
|
|
|
|
0
|
|
|
|
)
|
2024-12-20 14:30:37 -05:00
|
|
|
)
|
2025-01-16 11:10:36 -05:00
|
|
|
groupOfDrafts.add(pointMesh)
|
|
|
|
points.set(pointMesh.id, args.intersectionPoint.twoD)
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-01-16 11:10:36 -05:00
|
|
|
if (points.size <= 2) return
|
2024-12-20 14:30:37 -05:00
|
|
|
|
2025-01-16 11:10:36 -05:00
|
|
|
await updateCircle3Point()
|
2024-12-20 14:30:37 -05:00
|
|
|
},
|
|
|
|
})
|
2025-01-06 14:08:18 -05:00
|
|
|
|
|
|
|
return cleanupFn
|
2024-12-20 14:30:37 -05:00
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
setupDraftCircle = async (
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: 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]
|
2024-12-16 10:34:11 -05:00
|
|
|
) => {
|
2024-09-23 22:42:51 +10:00
|
|
|
let _ast = structuredClone(kclManager.ast)
|
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
const _node1 = getNodeFromPath<VariableDeclaration>(
|
2024-09-23 22:42:51 +10:00
|
|
|
_ast,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode || [],
|
|
|
|
'VariableDeclaration'
|
2024-09-23 22:42:51 +10:00
|
|
|
)
|
2024-12-16 10:34:11 -05:00
|
|
|
if (trap(_node1)) return Promise.reject(_node1)
|
|
|
|
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
|
|
|
const startSketchOn = _node1.node?.declaration
|
|
|
|
const startSketchOnInit = startSketchOn?.init
|
2024-09-23 22:42:51 +10:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
startSketchOn.init = createPipeExpression([
|
|
|
|
startSketchOnInit,
|
2024-09-23 22:42:51 +10:00
|
|
|
createCallExpressionStdLib('circle', [
|
|
|
|
createObjectExpression({
|
|
|
|
center: createArrayExpression([
|
|
|
|
createLiteral(roundOff(circleCenter[0])),
|
|
|
|
createLiteral(roundOff(circleCenter[1])),
|
|
|
|
]),
|
|
|
|
radius: createLiteral(1),
|
|
|
|
}),
|
2024-12-16 10:34:11 -05:00
|
|
|
createPipeSubstitution(),
|
|
|
|
]),
|
|
|
|
])
|
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)
|
|
|
|
|
|
|
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode,
|
2024-09-23 22:42:51 +10:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: sketchOrigin,
|
|
|
|
maybeModdedAst: _ast,
|
|
|
|
draftExpressionsIndices: { start: 0, end: 0 },
|
|
|
|
})
|
|
|
|
|
|
|
|
sceneInfra.setCallbacks({
|
|
|
|
onMove: async (args) => {
|
2024-12-16 10:34:11 -05:00
|
|
|
const pathToNodeTwo = structuredClone(sketchPathToNode)
|
|
|
|
pathToNodeTwo[1][0] = 0
|
|
|
|
|
2024-09-23 22:42:51 +10:00
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
truncatedAst,
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNodeTwo || [],
|
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]
|
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
if (sketchInit.type === 'PipeExpression') {
|
2024-09-23 22:42:51 +10:00
|
|
|
const moddedResult = changeSketchArguments(
|
|
|
|
modded,
|
|
|
|
kclManager.programMemory,
|
|
|
|
{
|
|
|
|
type: 'path',
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode: [
|
|
|
|
..._node.deepPath,
|
|
|
|
['body', 'PipeExpression'],
|
|
|
|
[1, 'index'],
|
|
|
|
],
|
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-10-09 19:38:40 -04:00
|
|
|
const { execState } = await executeAst({
|
2024-09-23 22:42:51 +10:00
|
|
|
ast: modded,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
2024-12-05 19:51:06 -08:00
|
|
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
2024-09-23 22:42:51 +10:00
|
|
|
programMemoryOverride,
|
|
|
|
})
|
2024-10-09 19:38:40 -04:00
|
|
|
const programMemory = execState.memory
|
2024-09-23 22:42:51 +10:00
|
|
|
this.sceneProgramMemory = programMemory
|
2024-12-16 10:34:11 -05:00
|
|
|
const sketch = sketchFromKclValue(
|
|
|
|
programMemory.get(variableDeclarationName),
|
|
|
|
variableDeclarationName
|
|
|
|
)
|
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)
|
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
2024-09-23 22:42:51 +10:00
|
|
|
sgPaths.forEach((seg, index) =>
|
2024-12-16 10:34:11 -05:00
|
|
|
this.updateSegment(seg, index, 0, _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,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode || [],
|
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)
|
2024-12-16 10:34:11 -05:00
|
|
|
if (sketchInit.type === 'PipeExpression') {
|
2024-09-23 22:42:51 +10:00
|
|
|
const moddedResult = changeSketchArguments(
|
|
|
|
modded,
|
|
|
|
kclManager.programMemory,
|
|
|
|
{
|
|
|
|
type: 'path',
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode: [
|
|
|
|
..._node.deepPath,
|
|
|
|
['body', 'PipeExpression'],
|
|
|
|
[1, 'index'],
|
|
|
|
],
|
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
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
2024-04-03 13:22:56 +11:00
|
|
|
setupSketchIdleCallbacks = ({
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode,
|
2024-04-03 13:22:56 +11:00
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
}: {
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode: 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') {
|
2024-12-16 10:34:11 -05:00
|
|
|
await 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({
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: pathToNode,
|
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({
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode,
|
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,
|
|
|
|
programMemory: kclManager.programMemory,
|
|
|
|
})
|
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,
|
|
|
|
programMemory: kclManager.programMemory,
|
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)
|
2024-12-16 10:34:11 -05:00
|
|
|
await 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({
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: pathToNode,
|
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({
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: pathToNodeForNewSegment,
|
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,
|
|
|
|
intersects,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: pathToNode,
|
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
|
|
|
})
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
prepareTruncatedMemoryAndAst = (
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: PathToNode,
|
2024-10-30 16:52:17 -04:00
|
|
|
ast?: Node<Program>,
|
2024-02-11 12:59:00 +11:00
|
|
|
draftSegment?: DraftSegment
|
|
|
|
) =>
|
|
|
|
prepareTruncatedMemoryAndAst(
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode,
|
2024-02-11 12:59:00 +11:00
|
|
|
ast || kclManager.ast,
|
2024-10-02 13:15:40 +10:00
|
|
|
kclManager.lastSuccessfulProgramMemory,
|
2024-02-11 12:59:00 +11:00
|
|
|
draftSegment
|
|
|
|
)
|
|
|
|
onDragSegment({
|
|
|
|
object,
|
2024-03-04 08:14:37 +11:00
|
|
|
intersection2d: _intersection2d,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode,
|
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
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: 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
|
|
|
programMemoryOverride: ProgramMemory
|
|
|
|
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]))
|
|
|
|
.find((a) => a?.name === PROFILE_START)
|
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)
|
|
|
|
const subGroup = getParentGroup(object, [ARROWHEAD, CIRCLE_CENTER_HANDLE])
|
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-12-16 10:34:11 -05:00
|
|
|
if (draftInfo) {
|
|
|
|
pathToNode[1][0] = 0
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
const from: [number, number] = [
|
|
|
|
group.userData.from[0],
|
|
|
|
group.userData.from[1],
|
|
|
|
]
|
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 }
|
|
|
|
|
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,
|
2024-12-16 10:34:11 -05:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
},
|
2024-03-02 08:48:30 +11:00
|
|
|
previousProgramMemory: kclManager.programMemory,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
modded = changeSketchArguments(
|
|
|
|
modifiedAst,
|
|
|
|
kclManager.programMemory,
|
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
|
2024-12-16 10:34:11 -05:00
|
|
|
: this.prepareTruncatedMemoryAndAst(pathToNode || [])
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(info, { suppress: true })) return
|
2024-12-16 10:34:11 -05:00
|
|
|
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
|
|
|
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,
|
2024-12-05 19:51:06 -08:00
|
|
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
2024-02-11 12:59:00 +11:00
|
|
|
programMemoryOverride,
|
|
|
|
})
|
2024-10-09 19:38:40 -04:00
|
|
|
const programMemory = execState.memory
|
2024-02-11 12:59:00 +11:00
|
|
|
this.sceneProgramMemory = programMemory
|
2024-07-08 17:57:37 -04:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
const maybeSketch = programMemory.get(variableDeclarationName)
|
|
|
|
let sketch: Sketch | undefined
|
|
|
|
const sk = sketchFromKclValueOptional(
|
|
|
|
maybeSketch,
|
|
|
|
variableDeclarationName
|
|
|
|
)
|
|
|
|
if (!(sk instanceof Reason)) {
|
|
|
|
sketch = sk
|
2025-01-22 09:42:09 +13:00
|
|
|
} else if (maybeSketch && (maybeSketch.value as Solid)?.sketch) {
|
|
|
|
sketch = (maybeSketch.value as Solid).sketch
|
2024-12-16 10:34:11 -05:00
|
|
|
}
|
|
|
|
if (!sketch) return
|
2024-03-02 08:48:30 +11:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
const sgPaths = sketch.paths
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
2024-05-24 20:54:42 +10:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
this.updateSegment(
|
|
|
|
sketch.start,
|
|
|
|
0,
|
|
|
|
varDecIndex,
|
|
|
|
modifiedAst,
|
|
|
|
orthoFactor,
|
|
|
|
sketch
|
|
|
|
)
|
|
|
|
|
|
|
|
const callBacks = sgPaths.map((group, index) =>
|
2024-04-19 11:56:21 -04:00
|
|
|
this.updateSegment(
|
2024-12-16 10:34:11 -05:00
|
|
|
group,
|
|
|
|
index,
|
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
|
|
|
)
|
2024-12-16 10:34:11 -05:00
|
|
|
)
|
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]
|
2024-12-16 10:34:11 -05:00
|
|
|
// const prevSegment = sketch.slice(index - 1)[0]
|
2024-04-19 11:56:21 -04:00
|
|
|
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,
|
|
|
|
}
|
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)
|
|
|
|
}
|
2024-12-16 10:34:11 -05:00
|
|
|
private _tearDownSketch(
|
|
|
|
callDepth = 0,
|
|
|
|
resolve: (val: unknown) => void,
|
|
|
|
reject: () => void,
|
|
|
|
{ removeAxis = true }: { removeAxis?: boolean }
|
|
|
|
) {
|
2024-11-22 11:05:04 -05:00
|
|
|
// Remove all draft groups
|
|
|
|
this.draftPointGroups.forEach((draftPointGroup) => {
|
|
|
|
this.scene.remove(draftPointGroup)
|
|
|
|
})
|
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
|
|
|
|
)
|
2024-12-16 10:34:11 -05:00
|
|
|
let shouldResolve = false
|
2024-02-11 12:59:00 +11:00
|
|
|
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-12-16 10:34:11 -05:00
|
|
|
shouldResolve = true
|
|
|
|
} else {
|
|
|
|
const delay = 100
|
|
|
|
const maxTimeRetries = 3000 // 3 seconds
|
|
|
|
const maxCalls = maxTimeRetries / delay
|
|
|
|
if (callDepth < maxCalls) {
|
|
|
|
setTimeout(() => {
|
|
|
|
this._tearDownSketch(callDepth + 1, resolve, reject, { removeAxis })
|
|
|
|
}, delay)
|
|
|
|
} else {
|
|
|
|
resolve(true)
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
sceneInfra.camControls.enableRotate = true
|
2024-02-11 12:59:00 +11:00
|
|
|
this.activeSegments = {}
|
2024-12-16 10:34:11 -05:00
|
|
|
// maybe should reset onMove etc handlers
|
|
|
|
if (shouldResolve) resolve(true)
|
|
|
|
}
|
|
|
|
async tearDownSketch({
|
|
|
|
removeAxis = true,
|
|
|
|
}: {
|
|
|
|
removeAxis?: boolean
|
|
|
|
} = {}) {
|
|
|
|
// I think promisifying this is mostly a side effect of not having
|
|
|
|
// "setupSketch" correctly capture a promise when it's done
|
|
|
|
// so we're effectively waiting for to be finished setting up the scene just to tear it down
|
|
|
|
// TODO is to fix that
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
this._tearDownSketch(0, resolve, reject, { removeAxis })
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
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
|
|
|
|
|
|
|
|
function prepareTruncatedMemoryAndAst(
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: PathToNode,
|
2024-10-30 16:52:17 -04:00
|
|
|
ast: Node<Program>,
|
2024-02-11 12:59:00 +11:00
|
|
|
programMemory: ProgramMemory,
|
|
|
|
draftSegment?: DraftSegment
|
2024-06-24 11:45:40 -04:00
|
|
|
):
|
|
|
|
| {
|
2024-10-30 16:52:17 -04:00
|
|
|
truncatedAst: Node<Program>
|
2024-06-24 11:45:40 -04:00
|
|
|
programMemoryOverride: ProgramMemory
|
|
|
|
variableDeclarationName: string
|
|
|
|
}
|
|
|
|
| Error {
|
2024-12-16 10:34:11 -05:00
|
|
|
const bodyIndex = Number(sketchPathToNode?.[1]?.[0]) || 0
|
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,
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode || [],
|
2024-06-24 11:45:40 -04:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (err(_node)) return _node
|
2024-12-07 07:16:04 +13:00
|
|
|
const variableDeclarationName = _node.node?.declaration.id?.name || ''
|
2024-09-27 15:44:44 -07:00
|
|
|
const sg = sketchFromKclValue(
|
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
|
|
|
programMemory.get(variableDeclarationName),
|
|
|
|
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(),
|
|
|
|
])
|
|
|
|
}
|
|
|
|
;(
|
2024-12-16 10:34:11 -05:00
|
|
|
(_ast.body[bodyIndex] 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 = (
|
2024-12-16 10:34:11 -05:00
|
|
|
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration).declaration
|
|
|
|
.init as PipeExpression
|
2024-02-11 12:59:00 +11:00
|
|
|
).body.slice(-1)[0]
|
|
|
|
|
|
|
|
;(
|
2024-12-16 10:34:11 -05:00
|
|
|
(_ast.body[bodyIndex] 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
|
2024-12-16 10:34:11 -05:00
|
|
|
const varDec = _ast.body[bodyIndex] 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,
|
2024-12-16 10:34:11 -05:00
|
|
|
body: [structuredClone(_ast.body[bodyIndex])],
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-06-24 22:39:04 -07:00
|
|
|
// Grab all the TagDeclarators and TagIdentifiers from memory.
|
|
|
|
let start = _node.node.start
|
2024-07-22 19:43:40 -04:00
|
|
|
const programMemoryOverride = programMemory.filterVariables(true, (value) => {
|
2024-06-24 22:39:04 -07:00
|
|
|
if (
|
2024-07-22 19:43:40 -04:00
|
|
|
!('__meta' in value) ||
|
2024-06-24 22:39:04 -07:00
|
|
|
value.__meta === undefined ||
|
|
|
|
value.__meta.length === 0 ||
|
|
|
|
value.__meta[0].sourceRange === undefined
|
|
|
|
) {
|
2024-07-22 19:43:40 -04:00
|
|
|
return false
|
2024-06-24 22:39:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (value.__meta[0].sourceRange[0] >= start) {
|
|
|
|
// We only want things before our start point.
|
2024-07-22 19:43:40 -04:00
|
|
|
return false
|
2024-06-24 22:39:04 -07:00
|
|
|
}
|
|
|
|
|
2024-07-22 19:43:40 -04:00
|
|
|
return value.type === 'TagIdentifier'
|
|
|
|
})
|
|
|
|
if (err(programMemoryOverride)) return programMemoryOverride
|
2024-06-24 22:39:04 -07:00
|
|
|
|
2024-12-16 10:34:11 -05:00
|
|
|
for (let i = 0; i < bodyIndex; i++) {
|
2024-02-11 12:59:00 +11:00
|
|
|
const node = _ast.body[i]
|
|
|
|
if (node.type !== 'VariableDeclaration') {
|
|
|
|
continue
|
|
|
|
}
|
2024-12-07 07:16:04 +13:00
|
|
|
const name = node.declaration.id.name
|
2024-07-22 19:43:40 -04:00
|
|
|
const memoryItem = programMemory.get(name)
|
2024-02-11 12:59:00 +11:00
|
|
|
if (!memoryItem) {
|
|
|
|
continue
|
|
|
|
}
|
2024-07-25 20:11:46 -04:00
|
|
|
const error = programMemoryOverride.set(name, structuredClone(memoryItem))
|
2024-07-22 19:43:40 -04:00
|
|
|
if (err(error)) return error
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
return {
|
|
|
|
truncatedAst,
|
|
|
|
programMemoryOverride,
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
export function sketchFromPathToNode({
|
2024-02-11 12:59:00 +11:00
|
|
|
pathToNode,
|
|
|
|
ast,
|
|
|
|
programMemory,
|
|
|
|
}: {
|
|
|
|
pathToNode: PathToNode
|
|
|
|
ast: Program
|
|
|
|
programMemory: ProgramMemory
|
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
|
2024-07-22 19:43:40 -04:00
|
|
|
const result = programMemory.get(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,
|
|
|
|
programMemory: kclManager.programMemory,
|
|
|
|
})
|
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))
|
|
|
|
}
|
|
|
|
export async function getSketchOrientationDetails(
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode: PathToNode
|
2024-03-22 10:23:04 +11:00
|
|
|
): Promise<{
|
|
|
|
quat: Quaternion
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchDetails: SketchDetails & { faceId?: string }
|
2024-03-22 10:23:04 +11:00
|
|
|
}> {
|
2024-09-27 15:44:44 -07:00
|
|
|
const sketch = sketchFromPathToNode({
|
2024-12-16 10:34:11 -05:00
|
|
|
pathToNode: sketchPathToNode,
|
2024-03-22 10:23:04 +11:00
|
|
|
ast: kclManager.ast,
|
|
|
|
programMemory: kclManager.programMemory,
|
|
|
|
})
|
2024-09-27 15:44:44 -07:00
|
|
|
if (err(sketch)) return Promise.reject(sketch)
|
|
|
|
if (!sketch) return Promise.reject('sketch not found')
|
2024-06-24 11:45:40 -04: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: {
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode,
|
2024-03-22 10:23:04 +11:00
|
|
|
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
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
if (sketch.on.type === 'face') {
|
|
|
|
const faceInfo = await getFaceDetails(sketch.on.id)
|
2024-04-22 20:14:06 +10:00
|
|
|
|
2024-03-22 10:23:04 +11:00
|
|
|
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
2024-06-24 11:45:40 -04:00
|
|
|
return Promise.reject('face info')
|
2024-03-22 10:23:04 +11:00
|
|
|
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)
|
|
|
|
)
|
|
|
|
return {
|
|
|
|
quat: quaternion,
|
|
|
|
sketchDetails: {
|
2024-12-16 10:34:11 -05:00
|
|
|
sketchPathToNode,
|
2024-03-22 10:23:04 +11:00
|
|
|
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],
|
2024-09-27 15:44:44 -07:00
|
|
|
faceId: sketch.on.id,
|
2024-03-22 10:23:04 +11:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
return Promise.reject(
|
2024-09-27 15:44:44 -07:00
|
|
|
'sketch.on.type not recognized, has a new type been added?'
|
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
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
}
|