2024-02-11 12:59:00 +11:00
|
|
|
import {
|
|
|
|
BoxGeometry,
|
|
|
|
DoubleSide,
|
|
|
|
Group,
|
2024-03-04 08:14:37 +11:00
|
|
|
Intersection,
|
2024-02-11 12:59:00 +11:00
|
|
|
Mesh,
|
|
|
|
MeshBasicMaterial,
|
2024-03-04 08:14:37 +11:00
|
|
|
Object3D,
|
|
|
|
Object3DEventMap,
|
2024-02-11 12:59:00 +11:00
|
|
|
OrthographicCamera,
|
|
|
|
PerspectiveCamera,
|
|
|
|
PlaneGeometry,
|
2024-04-03 13:22:56 +11:00
|
|
|
Points,
|
2024-02-11 12:59:00 +11:00
|
|
|
Quaternion,
|
|
|
|
Scene,
|
|
|
|
Vector2,
|
|
|
|
Vector3,
|
|
|
|
} from 'three'
|
|
|
|
import {
|
2024-11-13 09:41:27 -05:00
|
|
|
ANGLE_SNAP_THRESHOLD_DEGREES,
|
2024-02-11 12:59:00 +11:00
|
|
|
ARROWHEAD,
|
|
|
|
AXIS_GROUP,
|
2024-10-31 07:04:38 -07:00
|
|
|
DRAFT_POINT,
|
|
|
|
DRAFT_POINT_GROUP,
|
2024-02-17 07:04:24 +11:00
|
|
|
getSceneScale,
|
2024-02-11 12:59:00 +11:00
|
|
|
INTERSECTION_PLANE_LAYER,
|
2024-07-30 14:16:53 -04:00
|
|
|
OnClickCallbackArgs,
|
2024-03-04 08:14:37 +11:00
|
|
|
OnMouseEnterLeaveArgs,
|
2024-02-11 12:59:00 +11:00
|
|
|
RAYCASTABLE_PLANE,
|
|
|
|
SKETCH_GROUP_SEGMENTS,
|
|
|
|
SKETCH_LAYER,
|
|
|
|
X_AXIS,
|
|
|
|
Y_AXIS,
|
2024-02-14 08:03:20 +11:00
|
|
|
} from './sceneInfra'
|
2024-03-22 10:23:04 +11:00
|
|
|
import { isQuaternionVertical, quaternionFromUpNForward } from './helpers'
|
2024-02-11 12:59:00 +11:00
|
|
|
import {
|
|
|
|
CallExpression,
|
|
|
|
parse,
|
|
|
|
Path,
|
|
|
|
PathToNode,
|
|
|
|
PipeExpression,
|
|
|
|
Program,
|
|
|
|
ProgramMemory,
|
|
|
|
recast,
|
2024-09-27 15:44:44 -07:00
|
|
|
Sketch,
|
2024-02-11 12:59:00 +11:00
|
|
|
VariableDeclaration,
|
|
|
|
VariableDeclarator,
|
2024-09-27 15:44:44 -07:00
|
|
|
sketchFromKclValue,
|
2024-12-06 13:57:31 +13:00
|
|
|
defaultSourceRange,
|
|
|
|
sourceRangeFromRust,
|
|
|
|
resultIsOk,
|
2024-02-11 12:59:00 +11:00
|
|
|
} from 'lang/wasm'
|
2024-04-17 20:18:07 -07:00
|
|
|
import {
|
|
|
|
engineCommandManager,
|
|
|
|
kclManager,
|
|
|
|
sceneInfra,
|
|
|
|
codeManager,
|
2024-04-19 14:24:40 -07:00
|
|
|
editorManager,
|
2024-04-17 20:18:07 -07:00
|
|
|
} from 'lib/singletons'
|
2024-02-11 12:59:00 +11:00
|
|
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
2024-10-31 07:04:38 -07:00
|
|
|
import { executeAst, ToolTip } from 'lang/langHelpers'
|
2024-02-11 12:59:00 +11:00
|
|
|
import {
|
2024-09-13 21:14:14 +10:00
|
|
|
createProfileStartHandle,
|
|
|
|
SegmentUtils,
|
|
|
|
segmentUtils,
|
2024-02-11 12:59:00 +11:00
|
|
|
} from './segments'
|
|
|
|
import {
|
2024-05-23 00:53:15 -04:00
|
|
|
addCallExpressionsToPipe,
|
2024-02-11 12:59:00 +11:00
|
|
|
addCloseToPipe,
|
|
|
|
addNewSketchLn,
|
|
|
|
changeSketchArguments,
|
2024-03-02 08:48:30 +11:00
|
|
|
updateStartProfileAtArgs,
|
2024-02-11 12:59:00 +11:00
|
|
|
} from 'lang/std/sketch'
|
2024-09-13 21:14:14 +10:00
|
|
|
import { isArray, isOverlap, roundOff } from 'lib/utils'
|
2024-02-11 12:59:00 +11:00
|
|
|
import {
|
|
|
|
createArrayExpression,
|
|
|
|
createCallExpressionStdLib,
|
2024-12-14 09:57:33 +11:00
|
|
|
createIdentifier,
|
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-12-14 09:57:33 +11:00
|
|
|
createVariableDeclaration,
|
2024-04-19 11:56:21 -04:00
|
|
|
findUniqueName,
|
2024-12-14 09:57:33 +11:00
|
|
|
getInsertIndex,
|
|
|
|
insertNewStartProfileAt,
|
|
|
|
updateSketchNodePathsWithInsertIndex,
|
2024-02-11 12:59:00 +11:00
|
|
|
} from 'lang/modifyAst'
|
2024-08-03 18:08:51 +10:00
|
|
|
import { Selections, getEventForSegmentSelection } from 'lib/selections'
|
2024-02-11 12:59:00 +11:00
|
|
|
import { createGridHelper, orthoScale, perspScale } from './helpers'
|
2024-03-22 10:23:04 +11:00
|
|
|
import { Models } from '@kittycad/lib'
|
2024-04-03 19:38:16 +11:00
|
|
|
import { uuidv4 } from 'lib/utils'
|
2024-12-14 09:57:33 +11:00
|
|
|
import {
|
|
|
|
SegmentOverlayPayload,
|
|
|
|
SketchDetails,
|
|
|
|
SketchDetailsUpdate,
|
|
|
|
SketchTool,
|
|
|
|
} from 'machines/modelingMachine'
|
2024-07-25 19:03:56 +10:00
|
|
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
2024-04-19 11:56:21 -04:00
|
|
|
import {
|
|
|
|
getRectangleCallExpressions,
|
|
|
|
updateRectangleSketch,
|
2024-11-18 10:04:09 -05:00
|
|
|
updateCenterRectangleSketch,
|
2024-04-19 11:56:21 -04:00
|
|
|
} from 'lib/rectangleTool'
|
2024-09-10 13:30:39 -04:00
|
|
|
import { getThemeColorForThreeJs, Themes } from 'lib/theme'
|
2024-12-14 09:57:33 +11:00
|
|
|
import { err, reportRejection, trap } from 'lib/trap'
|
2024-07-08 16:41:00 -04:00
|
|
|
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
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'
|
2024-11-13 09:41:27 -05:00
|
|
|
import { radToDeg } from 'three/src/math/MathUtils'
|
2024-12-14 09:57:33 +11:00
|
|
|
import toast from 'react-hot-toast'
|
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,
|
2024-12-14 09:57:33 +11:00
|
|
|
// default is 12, this makes the draft point pop a bit more,
|
|
|
|
// especially when snapping to the startProfileAt handle as it's it was the exact same size
|
|
|
|
size: 16,
|
2024-10-31 07:04:38 -07:00
|
|
|
})
|
|
|
|
draftPoint.layers.set(SKETCH_LAYER)
|
|
|
|
group.add(draftPoint)
|
|
|
|
}
|
2024-11-22 11:05:04 -05:00
|
|
|
|
2024-10-31 07:04:38 -07:00
|
|
|
removeDraftPoint() {
|
|
|
|
const draftPoint = this.getDraftPoint()
|
|
|
|
if (draftPoint) draftPoint.removeFromParent()
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-07-30 14:16:53 -04:00
|
|
|
setupNoPointsListener({
|
|
|
|
sketchDetails,
|
|
|
|
afterClick,
|
2024-12-14 09:57:33 +11:00
|
|
|
currentTool,
|
2024-07-30 14:16:53 -04:00
|
|
|
}: {
|
|
|
|
sketchDetails: SketchDetails
|
2024-12-14 09:57:33 +11:00
|
|
|
currentTool: SketchTool
|
|
|
|
afterClick: (
|
|
|
|
args: OnClickCallbackArgs,
|
|
|
|
updatedPaths: {
|
|
|
|
sketchNodePaths: PathToNode[]
|
|
|
|
sketchEntryNodePath: PathToNode
|
|
|
|
}
|
|
|
|
) => void
|
2024-07-30 14:16:53 -04:00
|
|
|
}) {
|
2024-10-31 07:04:38 -07:00
|
|
|
// TODO: Consolidate shared logic between this and setupSketch
|
|
|
|
// Which should just fire when the sketch mode is entered,
|
|
|
|
// instead of in these two separate XState states.
|
2024-11-22 11:05:04 -05:00
|
|
|
|
2024-10-31 07:04:38 -07:00
|
|
|
const draftPointGroup = new Group()
|
2024-11-22 11:05:04 -05:00
|
|
|
this.draftPointGroups.push(draftPointGroup)
|
2024-10-31 07:04:38 -07:00
|
|
|
draftPointGroup.name = DRAFT_POINT_GROUP
|
|
|
|
sketchDetails.origin &&
|
|
|
|
draftPointGroup.position.set(...sketchDetails.origin)
|
|
|
|
if (!(sketchDetails.yAxis && sketchDetails)) {
|
|
|
|
console.error('No sketch quaternion or sketch details found')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.currentSketchQuaternion = quaternionFromUpNForward(
|
|
|
|
new Vector3(...sketchDetails.yAxis),
|
|
|
|
new Vector3(...sketchDetails.zAxis)
|
|
|
|
)
|
|
|
|
draftPointGroup.setRotationFromQuaternion(this.currentSketchQuaternion)
|
|
|
|
this.scene.add(draftPointGroup)
|
|
|
|
|
2024-07-30 14:16:53 -04:00
|
|
|
const quaternion = quaternionFromUpNForward(
|
|
|
|
new Vector3(...sketchDetails.yAxis),
|
|
|
|
new Vector3(...sketchDetails.zAxis)
|
|
|
|
)
|
|
|
|
|
|
|
|
// Position the click raycast plane
|
2024-10-31 07:04:38 -07:00
|
|
|
this.intersectionPlane!.setRotationFromQuaternion(quaternion)
|
|
|
|
this.intersectionPlane!.position.copy(
|
|
|
|
new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
|
|
|
|
)
|
2024-07-30 14:16:53 -04:00
|
|
|
sceneInfra.setCallbacks({
|
2024-10-31 07:04:38 -07:00
|
|
|
onMove: (args) => {
|
|
|
|
if (!args.intersects.length) return
|
|
|
|
const axisIntersection = args.intersects.find(
|
|
|
|
(sceneObject) =>
|
|
|
|
sceneObject.object.name === X_AXIS ||
|
|
|
|
sceneObject.object.name === Y_AXIS
|
|
|
|
)
|
2024-12-14 09:57:33 +11:00
|
|
|
|
|
|
|
const arrowHead = getParentGroup(args.intersects[0].object, [ARROWHEAD])
|
|
|
|
const parent = getParentGroup(
|
|
|
|
args.intersects[0].object,
|
|
|
|
SEGMENT_BODIES_PLUS_PROFILE_START
|
|
|
|
)
|
|
|
|
if (
|
|
|
|
!axisIntersection &&
|
|
|
|
!(
|
|
|
|
parent?.userData?.isLastInProfile &&
|
|
|
|
(arrowHead || parent?.name === PROFILE_START)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return
|
2024-10-31 07:04:38 -07:00
|
|
|
const { intersectionPoint } = args
|
|
|
|
// We're hovering over an axis, so we should show a draft point
|
|
|
|
const snappedPoint = intersectionPoint.twoD.clone()
|
2024-12-14 09:57:33 +11:00
|
|
|
if (axisIntersection?.object.name === X_AXIS) {
|
2024-10-31 07:04:38 -07:00
|
|
|
snappedPoint.setComponent(1, 0)
|
2024-12-14 09:57:33 +11:00
|
|
|
} else if (axisIntersection?.object.name === X_AXIS) {
|
2024-10-31 07:04:38 -07:00
|
|
|
snappedPoint.setComponent(0, 0)
|
2024-12-14 09:57:33 +11:00
|
|
|
} else if (arrowHead) {
|
|
|
|
snappedPoint.set(arrowHead.position.x, arrowHead.position.y)
|
|
|
|
} else if (parent?.name === PROFILE_START) {
|
|
|
|
snappedPoint.set(parent.position.x, parent.position.y)
|
2024-10-31 07:04:38 -07:00
|
|
|
}
|
|
|
|
// Either create a new one or update the existing one
|
|
|
|
const draftPoint = this.getDraftPoint()
|
|
|
|
|
|
|
|
if (!draftPoint) {
|
|
|
|
this.createDraftPoint({
|
|
|
|
point: snappedPoint,
|
|
|
|
group: draftPointGroup,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// Ignore if there are huge jumps in the mouse position,
|
|
|
|
// that is likely a strange behavior
|
|
|
|
if (
|
|
|
|
draftPoint.position.distanceTo(
|
|
|
|
new Vector3(snappedPoint.x, snappedPoint.y, 0)
|
|
|
|
) > 100
|
|
|
|
) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
draftPoint.position.set(snappedPoint.x, snappedPoint.y, 0)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onMouseLeave: () => {
|
|
|
|
this.removeDraftPoint()
|
|
|
|
},
|
2024-07-30 14:16:53 -04:00
|
|
|
onClick: async (args) => {
|
2024-10-31 07:04:38 -07:00
|
|
|
this.removeDraftPoint()
|
2024-07-30 14:16:53 -04:00
|
|
|
if (!args) return
|
2024-10-24 06:26:33 -07:00
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
|
|
|
)
|
|
|
|
if (interaction !== 'none') return
|
2024-07-30 14:16:53 -04:00
|
|
|
if (args.mouseEvent.which !== 1) return
|
|
|
|
const { intersectionPoint } = args
|
2024-12-14 09:57:33 +11:00
|
|
|
if (!intersectionPoint?.twoD || !sketchDetails?.sketchEntryNodePath)
|
|
|
|
return
|
|
|
|
|
|
|
|
const parent = getParentGroup(
|
|
|
|
args?.intersects?.[0]?.object,
|
|
|
|
SEGMENT_BODIES_PLUS_PROFILE_START
|
|
|
|
)
|
|
|
|
if (parent?.userData?.isLastInProfile) {
|
|
|
|
afterClick(args, {
|
|
|
|
sketchNodePaths: sketchDetails.sketchNodePaths,
|
|
|
|
sketchEntryNodePath: parent.userData.pathToNode,
|
|
|
|
})
|
|
|
|
return
|
|
|
|
} else if (currentTool === 'tangentialArc') {
|
|
|
|
toast.error(
|
|
|
|
'Tangential Arc must continue an existing profile, please click on the last segment of the profile'
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
2024-10-31 07:04:38 -07:00
|
|
|
|
|
|
|
// Snap to either or both axes
|
|
|
|
// if the click intersects their meshes
|
|
|
|
const yAxisIntersection = args.intersects.find(
|
|
|
|
(sceneObject) => sceneObject.object.name === Y_AXIS
|
|
|
|
)
|
|
|
|
const xAxisIntersection = args.intersects.find(
|
|
|
|
(sceneObject) => sceneObject.object.name === X_AXIS
|
|
|
|
)
|
|
|
|
|
|
|
|
const snappedClickPoint = {
|
|
|
|
x: yAxisIntersection ? 0 : intersectionPoint.twoD.x,
|
|
|
|
y: xAxisIntersection ? 0 : intersectionPoint.twoD.y,
|
|
|
|
}
|
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
const inserted = insertNewStartProfileAt(
|
2024-07-30 14:16:53 -04:00
|
|
|
kclManager.ast,
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchDetails.sketchEntryNodePath,
|
|
|
|
sketchDetails.sketchNodePaths,
|
|
|
|
sketchDetails.planeNodePath,
|
|
|
|
[snappedClickPoint.x, snappedClickPoint.y],
|
|
|
|
'end'
|
2024-07-30 14:16:53 -04:00
|
|
|
)
|
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
if (trap(inserted)) return
|
|
|
|
const { modifiedAst } = inserted
|
2024-07-30 14:16:53 -04:00
|
|
|
|
|
|
|
await kclManager.updateAst(modifiedAst, false)
|
2024-11-16 16:49:44 -05:00
|
|
|
|
2024-10-31 07:04:38 -07:00
|
|
|
this.scene.remove(draftPointGroup)
|
2024-07-30 14:16:53 -04:00
|
|
|
|
|
|
|
// Now perform the caller-specified action
|
2024-12-14 09:57:33 +11:00
|
|
|
afterClick(args, {
|
|
|
|
sketchNodePaths: inserted.updatedSketchNodePaths,
|
|
|
|
sketchEntryNodePath: inserted.updatedEntryNodePath,
|
|
|
|
})
|
2024-07-30 14:16:53 -04:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
async setupSketch({
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
2024-03-22 10:23:04 +11:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position,
|
2024-03-25 15:20:43 +11:00
|
|
|
maybeModdedAst,
|
|
|
|
draftExpressionsIndices,
|
2024-06-22 04:49:31 -04:00
|
|
|
selectionRanges,
|
2024-02-11 12:59:00 +11:00
|
|
|
}: {
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: PathToNode
|
|
|
|
sketchNodePaths: PathToNode[]
|
2024-10-30 16:52:17 -04:00
|
|
|
maybeModdedAst: Node<Program>
|
2024-03-25 15:20:43 +11:00
|
|
|
draftExpressionsIndices?: { start: number; end: number }
|
2024-03-22 10:23:04 +11:00
|
|
|
forward: [number, number, number]
|
|
|
|
up: [number, number, number]
|
|
|
|
position?: [number, number, number]
|
2024-06-22 04:49:31 -04:00
|
|
|
selectionRanges?: Selections
|
2024-03-25 15:20:43 +11:00
|
|
|
}): Promise<{
|
2024-10-30 16:52:17 -04:00
|
|
|
truncatedAst: Node<Program>
|
2024-03-25 15:20:43 +11:00
|
|
|
programMemoryOverride: ProgramMemory
|
|
|
|
variableDeclarationName: string
|
|
|
|
}> {
|
2024-12-14 09:57:33 +11:00
|
|
|
this.createIntersectionPlane()
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const prepared = this.prepareTruncatedMemoryAndAst(
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchNodePaths,
|
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-14 09:57:33 +11:00
|
|
|
const sketchesInfo = getSketchesInfo({
|
|
|
|
sketchNodePaths,
|
2024-04-03 13:22:56 +11:00
|
|
|
ast: maybeModdedAst,
|
2024-02-11 12:59:00 +11:00
|
|
|
programMemory,
|
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
|
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-14 09:57:33 +11:00
|
|
|
pathToNode: sketchEntryNodePath,
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
const dummy = new Mesh()
|
2024-06-21 19:54:18 -07:00
|
|
|
// TODO: When we actually have sketch positions and rotations we can use them here.
|
|
|
|
dummy.position.set(0, 0, 0)
|
2024-10-31 07:04:38 -07:00
|
|
|
const scale = sceneInfra.getClientSceneScaleFactor(dummy)
|
2024-03-02 08:48:30 +11:00
|
|
|
|
2024-05-24 20:54:42 +10:00
|
|
|
const callbacks: (() => SegmentOverlayPayload | null)[] = []
|
2024-12-14 09:57:33 +11:00
|
|
|
|
|
|
|
for (const sketchInfo of sketchesInfo) {
|
|
|
|
const { sketch } = sketchInfo
|
|
|
|
const segPathToNode = getNodePathFromSourceRange(
|
2024-04-19 11:56:21 -04:00
|
|
|
maybeModdedAst,
|
2024-12-14 09:57:33 +11:00
|
|
|
sourceRangeFromRust(sketch.start.__geoMeta.sourceRange)
|
2024-02-11 12:59:00 +11:00
|
|
|
)
|
2024-12-14 09:57:33 +11:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
if (!sketch.paths.length) {
|
|
|
|
_profileStart.userData.isLastInProfile = true
|
|
|
|
}
|
|
|
|
group.add(_profileStart)
|
|
|
|
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
|
|
|
|
}
|
|
|
|
sketch.paths.forEach((segment, index) => {
|
|
|
|
const isLastInProfile =
|
|
|
|
index === sketch.paths.length - 1 && segment.type !== 'Circle'
|
|
|
|
let segPathToNode = getNodePathFromSourceRange(
|
2024-04-19 11:56:21 -04:00
|
|
|
maybeModdedAst,
|
2024-12-06 13:57:31 +13:00
|
|
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
2024-03-04 08:14:37 +11:00
|
|
|
)
|
2024-12-14 09:57:33 +11:00
|
|
|
if (
|
|
|
|
draftExpressionsIndices &&
|
|
|
|
(sketch.paths[index - 1] || sketch.start)
|
|
|
|
) {
|
|
|
|
const previousSegment = sketch.paths[index - 1] || sketch.start
|
|
|
|
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
|
|
|
maybeModdedAst,
|
|
|
|
sourceRangeFromRust(previousSegment.__geoMeta.sourceRange)
|
|
|
|
)
|
|
|
|
const bodyIndex = previousSegmentPathToNode[1][0]
|
|
|
|
segPathToNode = getNodePathFromSourceRange(
|
|
|
|
truncatedAst,
|
|
|
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
|
|
|
)
|
|
|
|
segPathToNode[1][0] = bodyIndex
|
|
|
|
}
|
|
|
|
const isDraftSegment =
|
|
|
|
draftExpressionsIndices &&
|
|
|
|
index <= draftExpressionsIndices.end &&
|
|
|
|
index >= draftExpressionsIndices.start &&
|
|
|
|
// the following line is not robust to sketches defined within a function
|
|
|
|
sketchInfo.pathToNode[1][0] === sketchEntryNodePath[1][0]
|
|
|
|
const isSelected = selectionRanges?.graphSelections.some((selection) =>
|
|
|
|
isOverlap(
|
|
|
|
selection?.codeRef?.range,
|
|
|
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
|
|
|
)
|
2024-12-06 13:57:31 +13:00
|
|
|
)
|
2024-06-22 04:49:31 -04:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
let seg: Group
|
|
|
|
const _node1 = getNodeFromPath<CallExpression>(
|
|
|
|
maybeModdedAst,
|
|
|
|
segPathToNode,
|
|
|
|
'CallExpression'
|
|
|
|
)
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
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,
|
|
|
|
})
|
|
|
|
if (err(result)) return
|
|
|
|
const { group: _group, updateOverlaysCallback } = result
|
|
|
|
seg = _group
|
|
|
|
if (isLastInProfile) {
|
|
|
|
seg.userData.isLastInProfile = true
|
|
|
|
}
|
|
|
|
callbacks.push(updateOverlaysCallback)
|
|
|
|
seg.layers.set(SKETCH_LAYER)
|
|
|
|
seg.traverse((child) => {
|
|
|
|
child.layers.set(SKETCH_LAYER)
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
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,
|
|
|
|
variableDeclarationName,
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
updateAstAndRejigSketch = async (
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: PathToNode,
|
|
|
|
sketchNodePaths: PathToNode[],
|
|
|
|
planeNodePath: PathToNode,
|
|
|
|
modifiedAst: Node<Program> | Error,
|
2024-03-22 10:23:04 +11:00
|
|
|
forward: [number, number, number],
|
|
|
|
up: [number, number, number],
|
2024-05-02 22:26:29 +10:00
|
|
|
origin: [number, number, number]
|
2024-02-11 12:59:00 +11:00
|
|
|
) => {
|
2024-12-14 09:57:33 +11:00
|
|
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
2024-06-24 11:45:40 -04:00
|
|
|
const nextAst = await kclManager.updateAst(modifiedAst, false)
|
2024-12-14 09:57:33 +11:00
|
|
|
this.tearDownSketch({ removeAxis: false })
|
2024-04-03 13:22:56 +11:00
|
|
|
sceneInfra.resetMouseListeners()
|
2024-03-25 15:20:43 +11:00
|
|
|
await this.setupSketch({
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
2024-03-25 15:20:43 +11:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: origin,
|
2024-06-24 11:45:40 -04:00
|
|
|
maybeModdedAst: nextAst.newAst,
|
2024-03-25 15:20:43 +11:00
|
|
|
})
|
2024-04-03 13:22:56 +11:00
|
|
|
this.setupSketchIdleCallbacks({
|
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: origin,
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
|
|
|
planeNodePath,
|
2024-04-03 13:22:56 +11:00
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
return nextAst
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-11-16 16:49:44 -05:00
|
|
|
setupDraftSegment = async (
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: PathToNode,
|
|
|
|
sketchNodePaths: PathToNode[],
|
|
|
|
planeNodePath: PathToNode,
|
2024-03-22 10:23:04 +11:00
|
|
|
forward: [number, number, number],
|
2024-03-25 15:20:43 +11:00
|
|
|
up: [number, number, number],
|
|
|
|
origin: [number, number, number],
|
|
|
|
segmentName: 'line' | 'tangentialArcTo' = 'line',
|
|
|
|
shouldTearDown = true
|
2024-03-22 10:23:04 +11:00
|
|
|
) => {
|
2024-07-25 20:11:46 -04:00
|
|
|
const _ast = structuredClone(kclManager.ast)
|
2024-03-25 15:20:43 +11:00
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node1 = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath || [],
|
2024-06-24 11:45:40 -04:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (trap(_node1)) return Promise.reject(_node1)
|
2024-12-07 07:16:04 +13:00
|
|
|
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
const sg = sketchFromKclValue(
|
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-14 09:57:33 +11:00
|
|
|
pathToNode: sketchEntryNodePath,
|
2024-04-03 13:22:56 +11:00
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(mod)) return Promise.reject(mod)
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(mod.modifiedAst))
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
|
|
|
const modifiedAst = pResult.program
|
2024-03-25 15:20:43 +11:00
|
|
|
|
|
|
|
const draftExpressionsIndices = { start: index, end: index }
|
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
if (shouldTearDown) this.tearDownSketch({ removeAxis: false })
|
2024-04-03 13:22:56 +11:00
|
|
|
sceneInfra.resetMouseListeners()
|
2024-06-29 18:10:07 -07:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
const { truncatedAst, programMemoryOverride } = await this.setupSketch({
|
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
|
|
|
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-14 09:57:33 +11:00
|
|
|
let modifiedAst: Program | Error = structuredClone(kclManager.ast)
|
|
|
|
|
|
|
|
const sketch = sketchFromPathToNode({
|
|
|
|
pathToNode: sketchEntryNodePath,
|
|
|
|
ast: kclManager.ast,
|
|
|
|
programMemory: kclManager.programMemory,
|
|
|
|
})
|
|
|
|
if (err(sketch)) return Promise.reject(sketch)
|
|
|
|
if (!sketch) return Promise.reject(new Error('No sketch found'))
|
2024-10-31 07:04:38 -07:00
|
|
|
|
|
|
|
// Snapping logic for the profile start handle
|
|
|
|
if (intersectsProfileStart) {
|
2024-10-23 12:42:54 -05:00
|
|
|
const lastSegment = sketch.paths.slice(-1)[0]
|
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-14 09:57:33 +11:00
|
|
|
pathToNode: sketchEntryNodePath,
|
2024-05-23 00:53:15 -04:00
|
|
|
expressions: [
|
|
|
|
createCallExpressionStdLib(
|
|
|
|
lastSegment.type === 'TangentialArcTo'
|
|
|
|
? 'tangentialArcTo'
|
|
|
|
: 'lineTo',
|
|
|
|
[
|
|
|
|
createArrayExpression([
|
|
|
|
createCallExpressionStdLib('profileStartX', [
|
|
|
|
createPipeSubstitution(),
|
|
|
|
]),
|
|
|
|
createCallExpressionStdLib('profileStartY', [
|
|
|
|
createPipeSubstitution(),
|
|
|
|
]),
|
|
|
|
]),
|
|
|
|
createPipeSubstitution(),
|
|
|
|
]
|
|
|
|
),
|
|
|
|
],
|
|
|
|
})
|
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-14 09:57:33 +11:00
|
|
|
pathToNode: sketchEntryNodePath,
|
2024-03-25 15:20:43 +11:00
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
2024-03-25 15:20:43 +11:00
|
|
|
} else if (intersection2d) {
|
2024-10-31 07:04:38 -07:00
|
|
|
const intersectsYAxis = args.intersects.find(
|
|
|
|
(sceneObject) => sceneObject.object.name === Y_AXIS
|
|
|
|
)
|
|
|
|
const intersectsXAxis = args.intersects.find(
|
|
|
|
(sceneObject) => sceneObject.object.name === X_AXIS
|
|
|
|
)
|
|
|
|
|
2024-11-13 09:41:27 -05:00
|
|
|
const lastSegment = sketch.paths.slice(-1)[0] || sketch.start
|
2024-10-31 07:04:38 -07:00
|
|
|
const snappedPoint = {
|
|
|
|
x: intersectsYAxis ? 0 : intersection2d.x,
|
|
|
|
y: intersectsXAxis ? 0 : intersection2d.y,
|
|
|
|
}
|
2024-11-13 09:41:27 -05:00
|
|
|
// Get the angle between the previous segment (or sketch start)'s end and this one's
|
|
|
|
const angle = Math.atan2(
|
|
|
|
snappedPoint.y - lastSegment.to[1],
|
|
|
|
snappedPoint.x - lastSegment.to[0]
|
|
|
|
)
|
|
|
|
|
|
|
|
const isHorizontal =
|
|
|
|
radToDeg(Math.abs(angle)) < ANGLE_SNAP_THRESHOLD_DEGREES ||
|
|
|
|
Math.abs(radToDeg(Math.abs(angle) - Math.PI)) <
|
|
|
|
ANGLE_SNAP_THRESHOLD_DEGREES
|
|
|
|
const isVertical =
|
|
|
|
Math.abs(radToDeg(Math.abs(angle) - Math.PI / 2)) <
|
|
|
|
ANGLE_SNAP_THRESHOLD_DEGREES
|
2024-10-31 07:04:38 -07:00
|
|
|
|
|
|
|
let resolvedFunctionName: ToolTip = 'line'
|
|
|
|
|
|
|
|
// This might need to become its own function if we want more
|
|
|
|
// case-based logic for different segment types
|
2024-12-14 09:57:33 +11:00
|
|
|
if (
|
|
|
|
(lastSegment.type === 'TangentialArcTo' &&
|
|
|
|
segmentName !== 'line') ||
|
|
|
|
segmentName === 'tangentialArcTo'
|
|
|
|
) {
|
2024-10-31 07:04:38 -07:00
|
|
|
resolvedFunctionName = 'tangentialArcTo'
|
2024-11-13 09:41:27 -05:00
|
|
|
} else if (isHorizontal) {
|
|
|
|
// If the angle between is 0 or 180 degrees (+/- the snapping angle), make the line an xLine
|
|
|
|
resolvedFunctionName = 'xLine'
|
|
|
|
} else if (isVertical) {
|
|
|
|
// If the angle between is 90 or 270 degrees (+/- the snapping angle), make the line a yLine
|
|
|
|
resolvedFunctionName = 'yLine'
|
2024-10-31 07:04:38 -07:00
|
|
|
} else if (snappedPoint.x === 0 || snappedPoint.y === 0) {
|
|
|
|
// We consider a point placed on axes or origin to be absolute
|
|
|
|
resolvedFunctionName = 'lineTo'
|
|
|
|
}
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const tmp = addNewSketchLn({
|
2024-03-25 15:20:43 +11:00
|
|
|
node: kclManager.ast,
|
|
|
|
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-14 09:57:33 +11:00
|
|
|
pathToNode: sketchEntryNodePath,
|
2024-06-24 11:45:40 -04:00
|
|
|
})
|
|
|
|
if (trap(tmp)) return Promise.reject(tmp)
|
|
|
|
modifiedAst = tmp.modifiedAst
|
|
|
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
2024-03-25 15:20:43 +11:00
|
|
|
} else {
|
|
|
|
// return early as we didn't modify the ast
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-17 20:18:07 -07:00
|
|
|
await kclManager.executeAstMock(modifiedAst)
|
2024-11-16 16:49:44 -05:00
|
|
|
|
2024-10-31 07:04:38 -07:00
|
|
|
if (intersectsProfileStart) {
|
2024-12-14 09:57:33 +11:00
|
|
|
sceneInfra.modelingSend({ type: 'Close sketch' })
|
2024-05-23 00:53:15 -04:00
|
|
|
} else {
|
2024-11-16 16:49:44 -05:00
|
|
|
await this.setupDraftSegment(
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
|
|
|
planeNodePath,
|
2024-05-23 00:53:15 -04:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
origin,
|
|
|
|
segmentName
|
|
|
|
)
|
|
|
|
}
|
2024-11-16 16:49:44 -05:00
|
|
|
|
|
|
|
await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst)
|
2024-03-25 15:20:43 +11:00
|
|
|
},
|
|
|
|
onMove: (args) => {
|
2024-12-14 09:57:33 +11:00
|
|
|
const expressionIndex = Number(sketchEntryNodePath[1][0])
|
|
|
|
const activeSegmentsInCorrectExpression = Object.values(
|
|
|
|
this.activeSegments
|
|
|
|
).filter((seg) => {
|
|
|
|
return seg.userData.pathToNode[1][0] === expressionIndex
|
|
|
|
})
|
|
|
|
const object =
|
|
|
|
activeSegmentsInCorrectExpression[
|
|
|
|
activeSegmentsInCorrectExpression.length - 1
|
|
|
|
]
|
2024-03-25 15:20:43 +11:00
|
|
|
this.onDragSegment({
|
|
|
|
intersection2d: args.intersectionPoint.twoD,
|
2024-12-14 09:57:33 +11:00
|
|
|
object,
|
2024-03-25 15:20:43 +11:00
|
|
|
intersects: args.intersects,
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchNodePaths,
|
|
|
|
sketchEntryNodePath,
|
|
|
|
planeNodePath,
|
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-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: PathToNode,
|
|
|
|
sketchNodePaths: PathToNode[],
|
|
|
|
planeNodePath: PathToNode,
|
2024-04-19 11:56:21 -04:00
|
|
|
forward: [number, number, number],
|
|
|
|
up: [number, number, number],
|
|
|
|
sketchOrigin: [number, number, number],
|
|
|
|
rectangleOrigin: [x: number, y: number]
|
2024-12-14 09:57:33 +11:00
|
|
|
): Promise<SketchDetailsUpdate | Error> => {
|
2024-07-25 20:11:46 -04:00
|
|
|
let _ast = structuredClone(kclManager.ast)
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
2024-06-24 11:45:40 -04:00
|
|
|
_ast,
|
2024-12-14 09:57:33 +11:00
|
|
|
planeNodePath,
|
|
|
|
'VariableDeclarator'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
if (err(varDec)) return varDec
|
|
|
|
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
const varName = findUniqueName(_ast, 'profile')
|
|
|
|
|
|
|
|
// first create just the variable declaration, as that's
|
|
|
|
// all we want the user to see in the editor
|
|
|
|
const tag = findUniqueName(_ast, 'rectangleSegmentA')
|
|
|
|
const newDeclaration = createVariableDeclaration(
|
|
|
|
varName,
|
|
|
|
createCallExpressionStdLib('startProfileAt', [
|
|
|
|
createArrayExpression([
|
|
|
|
createLiteral(roundOff(rectangleOrigin[0])),
|
|
|
|
createLiteral(roundOff(rectangleOrigin[1])),
|
|
|
|
]),
|
|
|
|
createIdentifier(varDec.node.id.name),
|
|
|
|
])
|
|
|
|
)
|
|
|
|
|
|
|
|
const insertIndex = getInsertIndex(sketchNodePaths, planeNodePath, 'end')
|
|
|
|
|
|
|
|
_ast.body.splice(insertIndex, 0, newDeclaration)
|
|
|
|
const { updatedEntryNodePath, updatedSketchNodePaths } =
|
|
|
|
updateSketchNodePathsWithInsertIndex({
|
|
|
|
insertIndex,
|
|
|
|
insertType: 'end',
|
|
|
|
sketchNodePaths,
|
|
|
|
})
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(_ast))
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
// do a quick mock execution to get the program memory up-to-date
|
|
|
|
await kclManager.executeAstMock(_ast)
|
|
|
|
|
|
|
|
const justCreatedNode = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
|
|
|
updatedEntryNodePath,
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
|
|
|
|
if (trap(justCreatedNode)) return Promise.reject(justCreatedNode)
|
|
|
|
const startProfileAt = justCreatedNode.node?.declaration
|
|
|
|
// than add the rest of the profile so we can "animate" it
|
|
|
|
// as draft segments
|
|
|
|
startProfileAt.init = createPipeExpression([
|
|
|
|
startProfileAt?.init,
|
|
|
|
...getRectangleCallExpressions(rectangleOrigin, tag),
|
|
|
|
])
|
|
|
|
|
|
|
|
const code = recast(_ast)
|
|
|
|
const _recastAst = parse(code)
|
|
|
|
if (trap(_recastAst) || !resultIsOk(_recastAst))
|
|
|
|
return Promise.reject(_recastAst)
|
|
|
|
_ast = _recastAst.program
|
|
|
|
|
2024-04-19 11:56:21 -04:00
|
|
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: updatedEntryNodePath,
|
|
|
|
sketchNodePaths: updatedSketchNodePaths,
|
2024-04-19 11:56:21 -04:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: sketchOrigin,
|
|
|
|
maybeModdedAst: _ast,
|
|
|
|
draftExpressionsIndices: { start: 0, end: 3 },
|
|
|
|
})
|
|
|
|
|
|
|
|
sceneInfra.setCallbacks({
|
|
|
|
onMove: async (args) => {
|
|
|
|
// Update the width and height of the draft rectangle
|
2024-12-14 09:57:33 +11:00
|
|
|
|
|
|
|
const nodePathWithCorrectedIndexForTruncatedAst =
|
|
|
|
structuredClone(updatedEntryNodePath)
|
|
|
|
nodePathWithCorrectedIndexForTruncatedAst[1][0] =
|
|
|
|
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) -
|
|
|
|
Number(planeNodePath[1][0]) -
|
|
|
|
1
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
2024-04-19 11:56:21 -04:00
|
|
|
truncatedAst,
|
2024-12-14 09:57:33 +11:00
|
|
|
nodePathWithCorrectedIndexForTruncatedAst,
|
2024-04-19 11:56:21 -04:00
|
|
|
'VariableDeclaration'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (trap(_node)) return Promise.reject(_node)
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node?.declaration.init
|
2024-04-19 11:56:21 -04:00
|
|
|
|
|
|
|
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
|
|
|
|
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
|
|
|
|
|
|
|
if (sketchInit.type === 'PipeExpression') {
|
2024-12-14 09:57:33 +11:00
|
|
|
updateRectangleSketch(sketchInit, x, y, tag)
|
2024-04-19 11:56:21 -04:00
|
|
|
}
|
|
|
|
|
2024-10-09 19:38:40 -04:00
|
|
|
const { execState } = await executeAst({
|
2024-04-19 11:56:21 -04:00
|
|
|
ast: truncatedAst,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
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-14 09:57:33 +11:00
|
|
|
const sketch = sketchFromKclValue(programMemory.get(varName), varName)
|
2024-09-27 15:44:44 -07:00
|
|
|
if (err(sketch)) return Promise.reject(sketch)
|
2024-10-23 12:42:54 -05:00
|
|
|
const sgPaths = sketch.paths
|
2024-04-19 11:56:21 -04:00
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
const varDecIndex = Number(updatedEntryNodePath[1][0])
|
|
|
|
|
|
|
|
this.updateSegment(
|
|
|
|
sketch.start,
|
|
|
|
0,
|
|
|
|
varDecIndex,
|
|
|
|
_ast,
|
|
|
|
orthoFactor,
|
|
|
|
sketch
|
|
|
|
)
|
2024-04-19 11:56:21 -04:00
|
|
|
sgPaths.forEach((seg, index) =>
|
2024-12-14 09:57:33 +11:00
|
|
|
this.updateSegment(seg, index, varDecIndex, _ast, orthoFactor, sketch)
|
2024-04-19 11:56:21 -04:00
|
|
|
)
|
|
|
|
},
|
|
|
|
onClick: async (args) => {
|
2024-10-24 06:26:33 -07:00
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
|
|
|
)
|
|
|
|
if (interaction !== 'none') return
|
2024-04-19 11:56:21 -04:00
|
|
|
// Commit the rectangle to the full AST/code and return to sketch.idle
|
|
|
|
const cornerPoint = args.intersectionPoint?.twoD
|
|
|
|
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
|
|
|
|
|
|
|
const x = roundOff((cornerPoint.x || 0) - rectangleOrigin[0])
|
|
|
|
const y = roundOff((cornerPoint.y || 0) - rectangleOrigin[1])
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
2024-04-19 11:56:21 -04:00
|
|
|
_ast,
|
2024-12-14 09:57:33 +11:00
|
|
|
updatedEntryNodePath,
|
2024-04-19 11:56:21 -04:00
|
|
|
'VariableDeclaration'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
2024-09-13 21:14:14 +10:00
|
|
|
if (trap(_node)) return
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node?.declaration.init
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-11-16 16:49:44 -05:00
|
|
|
if (sketchInit.type !== 'PipeExpression') {
|
|
|
|
return
|
|
|
|
}
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
updateRectangleSketch(sketchInit, x, y, tag)
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-11-16 16:49:44 -05:00
|
|
|
const newCode = recast(_ast)
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(newCode)
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult))
|
|
|
|
return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-11-16 16:49:44 -05:00
|
|
|
// Update the primary AST and unequip the rectangle tool
|
|
|
|
await kclManager.executeAstMock(_ast)
|
2024-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-14 09:57:33 +11:00
|
|
|
sceneInfra.modelingSend({ type: 'Finish rectangle' })
|
2024-04-19 11:56:21 -04:00
|
|
|
},
|
|
|
|
})
|
2024-12-14 09:57:33 +11:00
|
|
|
return { updatedEntryNodePath, updatedSketchNodePaths }
|
2024-04-19 11:56:21 -04:00
|
|
|
}
|
2024-11-18 10:04:09 -05:00
|
|
|
setupDraftCenterRectangle = async (
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: PathToNode,
|
|
|
|
sketchNodePaths: PathToNode[],
|
|
|
|
planeNodePath: PathToNode,
|
2024-11-18 10:04:09 -05:00
|
|
|
forward: [number, number, number],
|
|
|
|
up: [number, number, number],
|
|
|
|
sketchOrigin: [number, number, number],
|
|
|
|
rectangleOrigin: [x: number, y: number]
|
2024-12-14 09:57:33 +11:00
|
|
|
): Promise<SketchDetailsUpdate | Error> => {
|
2024-11-18 10:04:09 -05:00
|
|
|
let _ast = structuredClone(kclManager.ast)
|
2024-12-14 09:57:33 +11:00
|
|
|
|
|
|
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
2024-11-18 10:04:09 -05:00
|
|
|
_ast,
|
2024-12-14 09:57:33 +11:00
|
|
|
planeNodePath,
|
|
|
|
'VariableDeclarator'
|
2024-11-18 10:04:09 -05:00
|
|
|
)
|
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
if (err(varDec)) return varDec
|
|
|
|
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
|
2024-11-18 10:04:09 -05:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
const varName = findUniqueName(_ast, 'profile')
|
|
|
|
// first create just the variable declaration, as that's
|
|
|
|
// all we want the user to see in the editor
|
|
|
|
const tag = findUniqueName(_ast, 'rectangleSegmentA')
|
|
|
|
const newDeclaration = createVariableDeclaration(
|
|
|
|
varName,
|
|
|
|
createCallExpressionStdLib('startProfileAt', [
|
|
|
|
createArrayExpression([
|
|
|
|
createLiteral(roundOff(rectangleOrigin[0])),
|
|
|
|
createLiteral(roundOff(rectangleOrigin[1])),
|
|
|
|
]),
|
|
|
|
createIdentifier(varDec.node.id.name),
|
|
|
|
])
|
|
|
|
)
|
|
|
|
const insertIndex = getInsertIndex(sketchNodePaths, planeNodePath, 'end')
|
|
|
|
|
|
|
|
_ast.body.splice(insertIndex, 0, newDeclaration)
|
|
|
|
const { updatedEntryNodePath, updatedSketchNodePaths } =
|
|
|
|
updateSketchNodePathsWithInsertIndex({
|
|
|
|
insertIndex,
|
|
|
|
insertType: 'end',
|
|
|
|
sketchNodePaths,
|
|
|
|
})
|
2024-11-18 10:04:09 -05:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
let __recastAst = parse(recast(_ast))
|
|
|
|
if (trap(__recastAst) || !resultIsOk(__recastAst))
|
|
|
|
return Promise.reject(__recastAst)
|
|
|
|
_ast = __recastAst.program
|
2024-11-18 10:04:09 -05:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
// do a quick mock execution to get the program memory up-to-date
|
|
|
|
await kclManager.executeAstMock(_ast)
|
|
|
|
|
|
|
|
const justCreatedNode = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
|
|
|
updatedEntryNodePath,
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
|
|
|
|
if (trap(justCreatedNode)) return Promise.reject(justCreatedNode)
|
|
|
|
const startProfileAt = justCreatedNode.node?.declaration
|
|
|
|
// than add the rest of the profile so we can "animate" it
|
|
|
|
// as draft segments
|
|
|
|
startProfileAt.init = createPipeExpression([
|
|
|
|
startProfileAt?.init,
|
|
|
|
...getRectangleCallExpressions(rectangleOrigin, tag),
|
|
|
|
])
|
|
|
|
const code = recast(_ast)
|
|
|
|
__recastAst = parse(code)
|
|
|
|
if (trap(__recastAst) || !resultIsOk(__recastAst))
|
|
|
|
return Promise.reject(__recastAst)
|
|
|
|
_ast = __recastAst.program
|
2024-11-18 10:04:09 -05:00
|
|
|
|
|
|
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: updatedEntryNodePath,
|
|
|
|
sketchNodePaths: updatedSketchNodePaths,
|
2024-11-18 10:04:09 -05:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: sketchOrigin,
|
|
|
|
maybeModdedAst: _ast,
|
|
|
|
draftExpressionsIndices: { start: 0, end: 3 },
|
|
|
|
})
|
|
|
|
|
|
|
|
sceneInfra.setCallbacks({
|
|
|
|
onMove: async (args) => {
|
|
|
|
// Update the width and height of the draft rectangle
|
2024-12-14 09:57:33 +11:00
|
|
|
|
|
|
|
const nodePathWithCorrectedIndexForTruncatedAst =
|
|
|
|
structuredClone(updatedEntryNodePath)
|
|
|
|
nodePathWithCorrectedIndexForTruncatedAst[1][0] =
|
|
|
|
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) -
|
|
|
|
Number(planeNodePath[1][0]) -
|
|
|
|
1
|
2024-11-18 10:04:09 -05:00
|
|
|
|
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
truncatedAst,
|
2024-12-14 09:57:33 +11:00
|
|
|
nodePathWithCorrectedIndexForTruncatedAst,
|
2024-11-18 10:04:09 -05:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (trap(_node)) return Promise.reject(_node)
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node?.declaration.init
|
2024-11-18 10:04:09 -05:00
|
|
|
|
|
|
|
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
|
|
|
|
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
|
|
|
|
|
|
|
if (sketchInit.type === 'PipeExpression') {
|
|
|
|
updateCenterRectangleSketch(
|
|
|
|
sketchInit,
|
|
|
|
x,
|
|
|
|
y,
|
2024-12-14 09:57:33 +11:00
|
|
|
tag,
|
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-14 09:57:33 +11:00
|
|
|
const sketch = sketchFromKclValue(programMemory.get(varName), varName)
|
2024-11-18 10:04:09 -05:00
|
|
|
if (err(sketch)) return Promise.reject(sketch)
|
|
|
|
const sgPaths = sketch.paths
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
const varDecIndex = Number(updatedEntryNodePath[1][0])
|
|
|
|
|
|
|
|
this.updateSegment(
|
|
|
|
sketch.start,
|
|
|
|
0,
|
|
|
|
varDecIndex,
|
|
|
|
_ast,
|
|
|
|
orthoFactor,
|
|
|
|
sketch
|
|
|
|
)
|
2024-11-18 10:04:09 -05:00
|
|
|
sgPaths.forEach((seg, index) =>
|
2024-12-14 09:57:33 +11:00
|
|
|
this.updateSegment(seg, index, varDecIndex, _ast, orthoFactor, sketch)
|
2024-11-18 10:04:09 -05:00
|
|
|
)
|
|
|
|
},
|
|
|
|
onClick: async (args) => {
|
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
|
|
|
)
|
|
|
|
if (interaction !== 'none') return
|
|
|
|
// Commit the rectangle to the full AST/code and return to sketch.idle
|
|
|
|
const cornerPoint = args.intersectionPoint?.twoD
|
|
|
|
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
|
|
|
|
|
|
|
const x = roundOff((cornerPoint.x || 0) - rectangleOrigin[0])
|
|
|
|
const y = roundOff((cornerPoint.y || 0) - rectangleOrigin[1])
|
|
|
|
|
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
2024-12-14 09:57:33 +11:00
|
|
|
updatedEntryNodePath,
|
2024-11-18 10:04:09 -05:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (trap(_node)) return
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node?.declaration.init
|
2024-11-18 10:04:09 -05:00
|
|
|
|
|
|
|
if (sketchInit.type === 'PipeExpression') {
|
|
|
|
updateCenterRectangleSketch(
|
|
|
|
sketchInit,
|
|
|
|
x,
|
|
|
|
y,
|
2024-12-14 09:57:33 +11:00
|
|
|
tag,
|
2024-11-18 10:04:09 -05:00
|
|
|
rectangleOrigin[0],
|
|
|
|
rectangleOrigin[1]
|
|
|
|
)
|
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(_ast))
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult))
|
|
|
|
return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-11-18 10:04:09 -05:00
|
|
|
|
|
|
|
// Update the primary AST and unequip the rectangle tool
|
|
|
|
await kclManager.executeAstMock(_ast)
|
|
|
|
|
2024-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-14 09:57:33 +11:00
|
|
|
sceneInfra.modelingSend({ type: 'Finish center rectangle' })
|
2024-11-18 10:04:09 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
2024-12-14 09:57:33 +11:00
|
|
|
return { updatedEntryNodePath, updatedSketchNodePaths }
|
2024-11-18 10:04:09 -05:00
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
setupDraftCircle = async (
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: PathToNode,
|
|
|
|
sketchNodePaths: PathToNode[],
|
|
|
|
planeNodePath: PathToNode,
|
2024-09-23 22:42:51 +10:00
|
|
|
forward: [number, number, number],
|
|
|
|
up: [number, number, number],
|
|
|
|
sketchOrigin: [number, number, number],
|
|
|
|
circleCenter: [x: number, y: number]
|
2024-12-14 09:57:33 +11:00
|
|
|
): Promise<SketchDetailsUpdate | Error> => {
|
2024-09-23 22:42:51 +10:00
|
|
|
let _ast = structuredClone(kclManager.ast)
|
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
2024-09-23 22:42:51 +10:00
|
|
|
_ast,
|
2024-12-14 09:57:33 +11:00
|
|
|
planeNodePath,
|
|
|
|
'VariableDeclarator'
|
2024-09-23 22:42:51 +10:00
|
|
|
)
|
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
if (err(varDec)) return varDec
|
|
|
|
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
|
|
|
|
|
|
|
|
const varName = findUniqueName(_ast, 'profile')
|
|
|
|
const newExpression = createVariableDeclaration(
|
|
|
|
varName,
|
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-14 09:57:33 +11:00
|
|
|
createIdentifier(varDec.node.id.name),
|
|
|
|
])
|
|
|
|
)
|
|
|
|
|
|
|
|
const insertIndex = getInsertIndex(sketchNodePaths, planeNodePath, 'end')
|
|
|
|
|
|
|
|
_ast.body.splice(insertIndex, 0, newExpression)
|
|
|
|
const { updatedEntryNodePath, updatedSketchNodePaths } =
|
|
|
|
updateSketchNodePathsWithInsertIndex({
|
|
|
|
insertIndex,
|
|
|
|
insertType: 'end',
|
|
|
|
sketchNodePaths,
|
|
|
|
})
|
2024-09-23 22:42:51 +10:00
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(_ast))
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-09-23 22:42:51 +10:00
|
|
|
|
|
|
|
// do a quick mock execution to get the program memory up-to-date
|
|
|
|
await kclManager.executeAstMock(_ast)
|
|
|
|
|
|
|
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: updatedEntryNodePath,
|
|
|
|
sketchNodePaths: updatedSketchNodePaths,
|
2024-09-23 22:42:51 +10:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: sketchOrigin,
|
|
|
|
maybeModdedAst: _ast,
|
|
|
|
draftExpressionsIndices: { start: 0, end: 0 },
|
|
|
|
})
|
|
|
|
|
|
|
|
sceneInfra.setCallbacks({
|
|
|
|
onMove: async (args) => {
|
2024-12-14 09:57:33 +11:00
|
|
|
const nodePathWithCorrectedIndexForTruncatedAst =
|
|
|
|
structuredClone(updatedEntryNodePath)
|
|
|
|
nodePathWithCorrectedIndexForTruncatedAst[1][0] =
|
|
|
|
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) -
|
|
|
|
Number(planeNodePath[1][0]) -
|
|
|
|
1
|
2024-09-23 22:42:51 +10:00
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
truncatedAst,
|
2024-12-14 09:57:33 +11:00
|
|
|
nodePathWithCorrectedIndexForTruncatedAst,
|
2024-09-23 22:42:51 +10:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
let modded = structuredClone(truncatedAst)
|
|
|
|
if (trap(_node)) return
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node.declaration.init
|
2024-09-23 22:42:51 +10:00
|
|
|
|
|
|
|
const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0]
|
|
|
|
const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
|
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
if (sketchInit.type === 'CallExpression') {
|
2024-09-23 22:42:51 +10:00
|
|
|
const moddedResult = changeSketchArguments(
|
|
|
|
modded,
|
|
|
|
kclManager.programMemory,
|
|
|
|
{
|
|
|
|
type: 'path',
|
2024-12-14 09:57:33 +11:00
|
|
|
pathToNode: nodePathWithCorrectedIndexForTruncatedAst,
|
2024-09-23 22:42:51 +10:00
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'arc-segment',
|
|
|
|
center: circleCenter,
|
|
|
|
radius: Math.sqrt(x ** 2 + y ** 2),
|
|
|
|
from: circleCenter,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
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-14 09:57:33 +11:00
|
|
|
const sketch = sketchFromKclValue(programMemory.get(varName), varName)
|
2024-09-27 15:44:44 -07:00
|
|
|
if (err(sketch)) return
|
2024-10-23 12:42:54 -05:00
|
|
|
const sgPaths = sketch.paths
|
2024-09-23 22:42:51 +10:00
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
const varDecIndex = Number(updatedEntryNodePath[1][0])
|
|
|
|
|
|
|
|
this.updateSegment(
|
|
|
|
sketch.start,
|
|
|
|
0,
|
|
|
|
varDecIndex,
|
|
|
|
_ast,
|
|
|
|
orthoFactor,
|
|
|
|
sketch
|
|
|
|
)
|
2024-09-23 22:42:51 +10:00
|
|
|
sgPaths.forEach((seg, index) =>
|
2024-12-14 09:57:33 +11:00
|
|
|
this.updateSegment(seg, index, varDecIndex, _ast, orthoFactor, sketch)
|
2024-09-23 22:42:51 +10:00
|
|
|
)
|
|
|
|
},
|
|
|
|
onClick: async (args) => {
|
2024-10-24 06:26:33 -07:00
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
|
|
|
)
|
|
|
|
if (interaction !== 'none') return
|
2024-09-23 22:42:51 +10:00
|
|
|
// Commit the rectangle to the full AST/code and return to sketch.idle
|
|
|
|
const cornerPoint = args.intersectionPoint?.twoD
|
|
|
|
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
|
|
|
|
|
|
|
const x = roundOff((cornerPoint.x || 0) - circleCenter[0])
|
|
|
|
const y = roundOff((cornerPoint.y || 0) - circleCenter[1])
|
|
|
|
|
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
2024-12-14 09:57:33 +11:00
|
|
|
updatedEntryNodePath || [],
|
2024-09-23 22:42:51 +10:00
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (trap(_node)) return
|
2024-12-07 07:16:04 +13:00
|
|
|
const sketchInit = _node.node?.declaration.init
|
2024-09-23 22:42:51 +10:00
|
|
|
|
|
|
|
let modded = structuredClone(_ast)
|
2024-12-14 09:57:33 +11:00
|
|
|
if (sketchInit.type === 'CallExpression') {
|
2024-09-23 22:42:51 +10:00
|
|
|
const moddedResult = changeSketchArguments(
|
|
|
|
modded,
|
|
|
|
kclManager.programMemory,
|
|
|
|
{
|
|
|
|
type: 'path',
|
2024-12-14 09:57:33 +11:00
|
|
|
pathToNode: updatedEntryNodePath,
|
2024-09-23 22:42:51 +10:00
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'arc-segment',
|
|
|
|
center: circleCenter,
|
|
|
|
radius: Math.sqrt(x ** 2 + y ** 2),
|
|
|
|
from: circleCenter,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
if (err(moddedResult)) return
|
|
|
|
modded = moddedResult.modifiedAst
|
|
|
|
|
2024-11-16 16:49:44 -05:00
|
|
|
const newCode = recast(modded)
|
|
|
|
if (err(newCode)) return
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(newCode)
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult))
|
|
|
|
return Promise.reject(pResult)
|
|
|
|
_ast = pResult.program
|
2024-09-23 22:42:51 +10:00
|
|
|
|
|
|
|
// Update the primary AST and unequip the rectangle tool
|
|
|
|
await kclManager.executeAstMock(_ast)
|
2024-11-16 16:49:44 -05:00
|
|
|
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
|
2024-12-14 09:57:33 +11:00
|
|
|
|
|
|
|
sceneInfra.modelingSend({ type: 'Finish circle' })
|
2024-09-23 22:42:51 +10:00
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
2024-12-14 09:57:33 +11:00
|
|
|
return { updatedEntryNodePath, updatedSketchNodePaths }
|
2024-09-23 22:42:51 +10:00
|
|
|
}
|
2024-04-03 13:22:56 +11:00
|
|
|
setupSketchIdleCallbacks = ({
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
|
|
|
planeNodePath,
|
2024-04-03 13:22:56 +11:00
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
}: {
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: PathToNode
|
|
|
|
sketchNodePaths: PathToNode[]
|
|
|
|
planeNodePath: PathToNode
|
2024-04-03 13:22:56 +11:00
|
|
|
forward: [number, number, number]
|
|
|
|
up: [number, number, number]
|
|
|
|
position?: [number, number, number]
|
|
|
|
}) => {
|
|
|
|
let addingNewSegmentStatus: 'nothing' | 'pending' | 'added' = 'nothing'
|
2024-03-25 15:48:51 +11:00
|
|
|
sceneInfra.setCallbacks({
|
2024-04-03 13:22:56 +11:00
|
|
|
onDragEnd: async () => {
|
|
|
|
if (addingNewSegmentStatus !== 'nothing') {
|
2024-12-14 09:57:33 +11:00
|
|
|
this.tearDownSketch({ removeAxis: false })
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-04-03 13:22:56 +11:00
|
|
|
this.setupSketch({
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
2024-04-03 13:22:56 +11:00
|
|
|
maybeModdedAst: kclManager.ast,
|
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
})
|
|
|
|
// setting up the callbacks again resets value in closures
|
|
|
|
this.setupSketchIdleCallbacks({
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
|
|
|
planeNodePath,
|
2024-04-03 13:22:56 +11:00
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
})
|
2024-11-16 16:49:44 -05:00
|
|
|
await codeManager.writeToFile()
|
2024-04-03 13:22:56 +11:00
|
|
|
}
|
|
|
|
},
|
|
|
|
onDrag: async ({
|
|
|
|
selected,
|
|
|
|
intersectionPoint,
|
|
|
|
mouseEvent,
|
|
|
|
intersects,
|
|
|
|
}) => {
|
2024-03-25 15:48:51 +11:00
|
|
|
if (mouseEvent.which !== 1) return
|
2024-04-03 13:22:56 +11:00
|
|
|
|
|
|
|
const group = getParentGroup(selected, [EXTRA_SEGMENT_HANDLE])
|
|
|
|
if (group?.name === EXTRA_SEGMENT_HANDLE) {
|
|
|
|
const segGroup = getParentGroup(selected)
|
|
|
|
const pathToNode: PathToNode = segGroup?.userData?.pathToNode
|
|
|
|
const pathToNodeIndex = pathToNode.findIndex(
|
|
|
|
(x) => x[1] === 'PipeExpression'
|
|
|
|
)
|
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
const sketch = sketchFromPathToNode({
|
2024-04-03 13:22:56 +11:00
|
|
|
pathToNode,
|
|
|
|
ast: kclManager.ast,
|
|
|
|
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-14 09:57:33 +11:00
|
|
|
this.tearDownSketch({ removeAxis: false })
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-04-03 13:22:56 +11:00
|
|
|
this.setupSketch({
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: pathToNode,
|
|
|
|
sketchNodePaths,
|
2024-04-03 13:22:56 +11:00
|
|
|
maybeModdedAst: kclManager.ast,
|
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
})
|
|
|
|
addingNewSegmentStatus = 'added'
|
|
|
|
} else if (addingNewSegmentStatus === 'added') {
|
|
|
|
const pathToNodeForNewSegment = pathToNode.slice(0, pathToNodeIndex)
|
|
|
|
pathToNodeForNewSegment.push([pipeIndex - 2, 'index'])
|
|
|
|
this.onDragSegment({
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchNodePaths,
|
|
|
|
sketchEntryNodePath: pathToNodeForNewSegment,
|
|
|
|
planeNodePath,
|
2024-04-03 13:22:56 +11:00
|
|
|
object: selected,
|
|
|
|
intersection2d: intersectionPoint.twoD,
|
|
|
|
intersects,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-03-25 15:48:51 +11:00
|
|
|
this.onDragSegment({
|
|
|
|
object: selected,
|
|
|
|
intersection2d: intersectionPoint.twoD,
|
2024-12-14 09:57:33 +11:00
|
|
|
planeNodePath,
|
2024-03-25 15:48:51 +11:00
|
|
|
intersects,
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchNodePaths,
|
|
|
|
sketchEntryNodePath,
|
2024-03-25 15:48:51 +11:00
|
|
|
})
|
|
|
|
},
|
|
|
|
onMove: () => {},
|
|
|
|
onClick: (args) => {
|
2024-10-24 06:26:33 -07:00
|
|
|
// If there is a valid camera interaction that matches, do that instead
|
|
|
|
const interaction = sceneInfra.camControls.getInteractionType(
|
|
|
|
args.mouseEvent
|
|
|
|
)
|
|
|
|
if (interaction !== 'none') return
|
2024-03-25 15:48:51 +11:00
|
|
|
if (args?.mouseEvent.which !== 1) return
|
|
|
|
if (!args || !args.selected) {
|
|
|
|
sceneInfra.modelingSend({
|
|
|
|
type: 'Set selection',
|
|
|
|
data: {
|
|
|
|
selectionType: 'singleCodeCursor',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const { selected } = args
|
|
|
|
const event = getEventForSegmentSelection(selected)
|
|
|
|
if (!event) return
|
|
|
|
sceneInfra.modelingSend(event)
|
|
|
|
},
|
2024-04-04 11:07:51 +11:00
|
|
|
...this.mouseEnterLeaveCallbacks(),
|
2024-03-25 15:48:51 +11:00
|
|
|
})
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
prepareTruncatedMemoryAndAst = (
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchNodePaths: PathToNode[],
|
2024-10-30 16:52:17 -04:00
|
|
|
ast?: Node<Program>,
|
2024-02-11 12:59:00 +11:00
|
|
|
draftSegment?: DraftSegment
|
|
|
|
) =>
|
|
|
|
prepareTruncatedMemoryAndAst(
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchNodePaths,
|
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-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath,
|
|
|
|
sketchNodePaths,
|
|
|
|
planeNodePath,
|
2024-02-11 12:59:00 +11:00
|
|
|
draftInfo,
|
2024-03-04 08:14:37 +11:00
|
|
|
intersects,
|
2024-02-11 12:59:00 +11:00
|
|
|
}: {
|
|
|
|
object: any
|
|
|
|
intersection2d: Vector2
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: PathToNode
|
|
|
|
sketchNodePaths: PathToNode[]
|
|
|
|
planeNodePath: PathToNode
|
2024-03-04 08:14:37 +11:00
|
|
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
2024-02-11 12:59:00 +11:00
|
|
|
draftInfo?: {
|
2024-10-30 16:52:17 -04:00
|
|
|
truncatedAst: Node<Program>
|
2024-02-11 12:59:00 +11:00
|
|
|
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-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 }
|
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
const nodePathWithCorrectedIndexForTruncatedAst =
|
|
|
|
structuredClone(pathToNode)
|
|
|
|
nodePathWithCorrectedIndexForTruncatedAst[1][0] =
|
|
|
|
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) -
|
|
|
|
Number(planeNodePath[1][0]) -
|
|
|
|
1
|
|
|
|
|
2024-10-30 16:52:17 -04:00
|
|
|
const _node = getNodeFromPath<Node<CallExpression>>(
|
2024-02-11 12:59:00 +11:00
|
|
|
modifiedAst,
|
2024-12-14 09:57:33 +11:00
|
|
|
draftInfo ? nodePathWithCorrectedIndexForTruncatedAst : pathToNode,
|
2024-02-11 12:59:00 +11:00
|
|
|
'CallExpression'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (trap(_node)) return
|
|
|
|
const node = _node.node
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
if (node.type !== 'CallExpression') return
|
|
|
|
|
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',
|
2024-12-06 13:57:31 +13:00
|
|
|
sourceRange: [node.start, node.end, true],
|
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-14 09:57:33 +11:00
|
|
|
: this.prepareTruncatedMemoryAndAst(sketchNodePaths || [], modifiedAst)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(info, { suppress: true })) return
|
2024-12-14 09:57:33 +11:00
|
|
|
const { truncatedAst, programMemoryOverride } = 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-12-14 09:57:33 +11:00
|
|
|
const sketchesInfo = getSketchesInfo({
|
|
|
|
sketchNodePaths,
|
|
|
|
ast: truncatedAst,
|
|
|
|
programMemory,
|
|
|
|
})
|
|
|
|
const callBacks: (() => SegmentOverlayPayload | null)[] = []
|
|
|
|
for (const sketchInfo of sketchesInfo) {
|
|
|
|
const { sketch, pathToNode: _pathToNode } = sketchInfo
|
|
|
|
const varDecIndex = Number(_pathToNode[1][0])
|
2024-07-08 17:57:37 -04:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
if (!sketch) return
|
2024-03-02 08:48:30 +11:00
|
|
|
|
2024-12-14 09:57:33 +11:00
|
|
|
const sgPaths = sketch.paths
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
2024-05-24 20:54:42 +10:00
|
|
|
|
2024-04-19 11:56:21 -04:00
|
|
|
this.updateSegment(
|
2024-12-14 09:57:33 +11:00
|
|
|
sketch.start,
|
|
|
|
0,
|
2024-04-19 11:56:21 -04:00
|
|
|
varDecIndex,
|
2024-02-11 12:59:00 +11:00
|
|
|
modifiedAst,
|
2024-04-19 11:56:21 -04:00
|
|
|
orthoFactor,
|
2024-09-27 15:44:44 -07:00
|
|
|
sketch
|
2024-02-11 12:59:00 +11:00
|
|
|
)
|
2024-12-14 09:57:33 +11:00
|
|
|
|
|
|
|
callBacks.push(
|
|
|
|
...sgPaths.map((group, index) =>
|
|
|
|
this.updateSegment(
|
|
|
|
group,
|
|
|
|
index,
|
|
|
|
varDecIndex,
|
|
|
|
modifiedAst,
|
|
|
|
orthoFactor,
|
|
|
|
sketch
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
2024-05-24 20:54:42 +10:00
|
|
|
sceneInfra.overlayCallbacks(callBacks)
|
2024-09-09 18:17:45 -04:00
|
|
|
})().catch(reportRejection)
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
2024-04-19 11:56:21 -04:00
|
|
|
/**
|
|
|
|
* Update the THREEjs sketch entities with new segment data
|
|
|
|
* mapping them back to the AST
|
|
|
|
* @param segment
|
|
|
|
* @param index
|
|
|
|
* @param varDecIndex
|
|
|
|
* @param modifiedAst
|
|
|
|
* @param orthoFactor
|
2024-09-27 15:44:44 -07:00
|
|
|
* @param sketch
|
2024-04-19 11:56:21 -04:00
|
|
|
*/
|
|
|
|
updateSegment = (
|
2024-09-27 15:44:44 -07:00
|
|
|
segment: Path | Sketch['start'],
|
2024-04-19 11:56:21 -04:00
|
|
|
index: number,
|
|
|
|
varDecIndex: number,
|
|
|
|
modifiedAst: Program,
|
|
|
|
orthoFactor: number,
|
2024-09-27 15:44:44 -07:00
|
|
|
sketch: Sketch
|
2024-05-24 20:54:42 +10:00
|
|
|
): (() => SegmentOverlayPayload | null) => {
|
2024-04-19 11:56:21 -04:00
|
|
|
const segPathToNode = getNodePathFromSourceRange(
|
|
|
|
modifiedAst,
|
2024-12-06 13:57:31 +13:00
|
|
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
2024-04-19 11:56:21 -04:00
|
|
|
)
|
2024-10-23 12:42:54 -05:00
|
|
|
const sgPaths = sketch.paths
|
2024-04-19 11:56:21 -04:00
|
|
|
const originalPathToNodeStr = JSON.stringify(segPathToNode)
|
|
|
|
segPathToNode[1][0] = varDecIndex
|
|
|
|
const pathToNodeStr = JSON.stringify(segPathToNode)
|
2024-09-27 15:44:44 -07:00
|
|
|
// more hacks to hopefully be solved by proper pathToNode info in memory/sketch segments
|
2024-04-19 11:56:21 -04:00
|
|
|
const group =
|
|
|
|
this.activeSegments[pathToNodeStr] ||
|
|
|
|
this.activeSegments[originalPathToNodeStr]
|
|
|
|
const type = group?.userData?.type
|
|
|
|
const factor =
|
|
|
|
(sceneInfra.camControls.camera instanceof OrthographicCamera
|
|
|
|
? orthoFactor
|
|
|
|
: perspScale(sceneInfra.camControls.camera, group)) /
|
|
|
|
sceneInfra._baseUnitMultiplier
|
2024-09-23 22:42:51 +10:00
|
|
|
let input: SegmentInputs = {
|
2024-09-13 21:14:14 +10:00
|
|
|
type: 'straight-segment',
|
|
|
|
from: segment.from,
|
|
|
|
to: segment.to,
|
2024-09-23 22:42:51 +10:00
|
|
|
}
|
2024-09-13 21:14:14 +10:00
|
|
|
let update: SegmentUtils['update'] | null = null
|
2024-04-19 11:56:21 -04:00
|
|
|
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
|
2024-09-13 21:14:14 +10:00
|
|
|
update = segmentUtils.tangentialArcTo.update
|
2024-04-19 11:56:21 -04:00
|
|
|
} else if (type === STRAIGHT_SEGMENT) {
|
2024-09-13 21:14:14 +10:00
|
|
|
update = segmentUtils.straight.update
|
2024-09-23 22:42:51 +10:00
|
|
|
} else if (
|
|
|
|
type === CIRCLE_SEGMENT &&
|
|
|
|
'type' in segment &&
|
|
|
|
segment.type === 'Circle'
|
|
|
|
) {
|
|
|
|
update = segmentUtils.circle.update
|
|
|
|
input = {
|
|
|
|
type: 'arc-segment',
|
|
|
|
from: segment.from,
|
|
|
|
center: segment.center,
|
|
|
|
radius: segment.radius,
|
|
|
|
}
|
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-14 09:57:33 +11:00
|
|
|
tearDownSketch({ removeAxis = true }: { removeAxis?: boolean }) {
|
2024-11-22 11:05:04 -05:00
|
|
|
// Remove all draft groups
|
|
|
|
this.draftPointGroups.forEach((draftPointGroup) => {
|
|
|
|
this.scene.remove(draftPointGroup)
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
if (this.axisGroup && removeAxis) this.scene.remove(this.axisGroup)
|
|
|
|
const sketchSegments = this.scene.children.find(
|
|
|
|
({ userData }) => userData?.type === SKETCH_GROUP_SEGMENTS
|
|
|
|
)
|
|
|
|
if (sketchSegments) {
|
2024-07-08 16:41:00 -04:00
|
|
|
// We have to manually remove the CSS2DObjects
|
|
|
|
// as they don't get removed when the group is removed
|
|
|
|
sketchSegments.traverse((object) => {
|
|
|
|
if (object instanceof CSS2DObject) {
|
|
|
|
object.element.remove()
|
|
|
|
object.remove()
|
|
|
|
}
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
this.scene.remove(sketchSegments)
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
sceneInfra.camControls.enableRotate = true
|
2024-02-11 12:59:00 +11:00
|
|
|
this.activeSegments = {}
|
|
|
|
}
|
2024-04-04 11:07:51 +11:00
|
|
|
mouseEnterLeaveCallbacks() {
|
|
|
|
return {
|
|
|
|
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
|
|
|
|
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
|
|
|
|
const obj = selected as Mesh
|
|
|
|
const mat = obj.material as MeshBasicMaterial
|
|
|
|
mat.color.set(obj.userData.baseColor)
|
|
|
|
mat.color.offsetHSL(0, 0, 0.5)
|
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
const parent = getParentGroup(
|
|
|
|
selected,
|
|
|
|
SEGMENT_BODIES_PLUS_PROFILE_START
|
|
|
|
)
|
2024-04-04 11:07:51 +11:00
|
|
|
if (parent?.userData?.pathToNode) {
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(kclManager.ast))
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult))
|
|
|
|
return Promise.reject(pResult)
|
|
|
|
const updatedAst = pResult.program
|
2024-10-30 16:52:17 -04:00
|
|
|
const _node = getNodeFromPath<Node<CallExpression>>(
|
2024-04-04 11:07:51 +11:00
|
|
|
updatedAst,
|
|
|
|
parent.userData.pathToNode,
|
|
|
|
'CallExpression'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (trap(_node, { suppress: true })) return
|
|
|
|
const node = _node.node
|
2024-12-06 13:57:31 +13:00
|
|
|
editorManager.setHighlightRange([[node.start, node.end, true]])
|
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
|
|
|
}
|
|
|
|
|
|
|
|
export type DefaultPlaneStr = 'XY' | 'XZ' | 'YZ' | '-XY' | '-XZ' | '-YZ'
|
|
|
|
|
|
|
|
// calculations/pure-functions/easy to test so no excuse not to
|
|
|
|
|
|
|
|
function prepareTruncatedMemoryAndAst(
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchNodePaths: 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
|
2024-12-14 09:57:33 +11:00
|
|
|
// can I remove the below?
|
2024-06-24 11:45:40 -04:00
|
|
|
variableDeclarationName: string
|
|
|
|
}
|
|
|
|
| Error {
|
2024-12-14 09:57:33 +11:00
|
|
|
const bodyStartIndex = Number(sketchNodePaths?.[0]?.[1]?.[0]) || 0
|
|
|
|
const bodyEndIndex =
|
|
|
|
Number(sketchNodePaths[sketchNodePaths.length - 1]?.[1]?.[0]) ||
|
|
|
|
ast.body.length
|
2024-07-25 20:11:46 -04:00
|
|
|
const _ast = structuredClone(ast)
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-10-30 16:52:17 -04:00
|
|
|
const _node = getNodeFromPath<Node<VariableDeclaration>>(
|
2024-06-24 11:45:40 -04:00
|
|
|
_ast,
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchNodePaths[0] || [],
|
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') {
|
|
|
|
newSegment = createCallExpressionStdLib('line', [
|
|
|
|
createArrayExpression([createLiteral(0), createLiteral(0)]),
|
|
|
|
createPipeSubstitution(),
|
|
|
|
])
|
|
|
|
} else {
|
|
|
|
newSegment = createCallExpressionStdLib('tangentialArcTo', [
|
|
|
|
createArrayExpression([
|
|
|
|
createLiteral(lastSeg.to[0]),
|
|
|
|
createLiteral(lastSeg.to[1]),
|
|
|
|
]),
|
|
|
|
createPipeSubstitution(),
|
|
|
|
])
|
|
|
|
}
|
|
|
|
;(
|
2024-12-14 09:57:33 +11:00
|
|
|
(_ast.body[bodyStartIndex] as VariableDeclaration).declaration
|
2024-02-11 12:59:00 +11:00
|
|
|
.init as PipeExpression
|
|
|
|
).body.push(newSegment)
|
|
|
|
// update source ranges to section we just added.
|
2024-09-27 15:44:44 -07:00
|
|
|
// hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketch segments
|
2024-12-06 13:57:31 +13:00
|
|
|
const pResult = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
|
|
|
|
if (trap(pResult) || !resultIsOk(pResult))
|
|
|
|
return Error('Unexpected compilation error')
|
|
|
|
const updatedSrcRangeAst = pResult.program
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
const lastPipeItem = (
|
2024-12-14 09:57:33 +11:00
|
|
|
(updatedSrcRangeAst.body[bodyStartIndex] as VariableDeclaration)
|
|
|
|
.declaration.init as PipeExpression
|
2024-02-11 12:59:00 +11:00
|
|
|
).body.slice(-1)[0]
|
|
|
|
|
|
|
|
;(
|
2024-12-14 09:57:33 +11:00
|
|
|
(_ast.body[bodyStartIndex] as VariableDeclaration).declaration
|
2024-02-11 12:59:00 +11:00
|
|
|
.init as PipeExpression
|
|
|
|
).body.slice(-1)[0].start = lastPipeItem.start
|
|
|
|
|
|
|
|
_ast.end = lastPipeItem.end
|
2024-12-14 09:57:33 +11:00
|
|
|
const varDec = _ast.body[bodyStartIndex] as Node<VariableDeclaration>
|
2024-02-11 12:59:00 +11:00
|
|
|
varDec.end = lastPipeItem.end
|
2024-12-07 07:16:04 +13:00
|
|
|
const declarator = varDec.declaration
|
2024-02-11 12:59:00 +11:00
|
|
|
declarator.end = lastPipeItem.end
|
2024-10-30 16:52:17 -04:00
|
|
|
const init = declarator.init as Node<PipeExpression>
|
2024-02-11 12:59:00 +11:00
|
|
|
init.end = lastPipeItem.end
|
|
|
|
init.body.slice(-1)[0].end = lastPipeItem.end
|
|
|
|
}
|
2024-10-30 16:52:17 -04:00
|
|
|
const truncatedAst: Node<Program> = {
|
2024-02-11 12:59:00 +11:00
|
|
|
..._ast,
|
2024-12-14 09:57:33 +11:00
|
|
|
body: structuredClone(_ast.body.slice(bodyStartIndex, bodyEndIndex + 1)),
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-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-14 09:57:33 +11:00
|
|
|
for (let i = 0; i < bodyStartIndex; 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') {
|
|
|
|
return result.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-14 09:57:33 +11:00
|
|
|
sketchEntryNodePath: PathToNode
|
2024-03-22 10:23:04 +11:00
|
|
|
): Promise<{
|
|
|
|
quat: Quaternion
|
2024-12-14 09:57:33 +11:00
|
|
|
sketchDetails: Omit<
|
|
|
|
SketchDetails & { faceId?: string },
|
|
|
|
'sketchNodePaths' | 'sketchEntryNodePath' | 'planeNodePath'
|
|
|
|
>
|
2024-03-22 10:23:04 +11:00
|
|
|
}> {
|
2024-09-27 15:44:44 -07:00
|
|
|
const sketch = sketchFromPathToNode({
|
2024-12-14 09:57:33 +11:00
|
|
|
pathToNode: sketchEntryNodePath,
|
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: {
|
|
|
|
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: {
|
|
|
|
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-14 09:57:33 +11:00
|
|
|
|
|
|
|
function getSketchesInfo({
|
|
|
|
sketchNodePaths,
|
|
|
|
ast,
|
|
|
|
programMemory,
|
|
|
|
}: {
|
|
|
|
sketchNodePaths: PathToNode[]
|
|
|
|
ast: Node<Program>
|
|
|
|
programMemory: ProgramMemory
|
|
|
|
}): {
|
|
|
|
sketch: Sketch
|
|
|
|
pathToNode: PathToNode
|
|
|
|
}[] {
|
|
|
|
const sketchesInfo: {
|
|
|
|
sketch: Sketch
|
|
|
|
pathToNode: PathToNode
|
|
|
|
}[] = []
|
|
|
|
for (const path of sketchNodePaths) {
|
|
|
|
const sketch = sketchFromPathToNode({
|
|
|
|
pathToNode: path,
|
|
|
|
ast,
|
|
|
|
programMemory,
|
|
|
|
})
|
|
|
|
if (err(sketch)) continue
|
|
|
|
if (!sketch) continue
|
|
|
|
sketchesInfo.push({
|
|
|
|
sketch,
|
|
|
|
pathToNode: path,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return sketchesInfo
|
|
|
|
}
|