2024-02-11 12:59:00 +11:00
|
|
|
import {
|
|
|
|
BoxGeometry,
|
|
|
|
DoubleSide,
|
|
|
|
ExtrudeGeometry,
|
|
|
|
Group,
|
2024-03-04 08:14:37 +11:00
|
|
|
Intersection,
|
2024-02-11 12:59:00 +11:00
|
|
|
LineCurve3,
|
|
|
|
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,
|
|
|
|
Shape,
|
|
|
|
Vector2,
|
|
|
|
Vector3,
|
|
|
|
} from 'three'
|
|
|
|
import {
|
|
|
|
ARROWHEAD,
|
|
|
|
AXIS_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,
|
2024-07-08 16:41:00 -04:00
|
|
|
SEGMENT_LENGTH_LABEL,
|
|
|
|
SEGMENT_LENGTH_LABEL_TEXT,
|
2024-02-11 12:59:00 +11:00
|
|
|
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,
|
|
|
|
getTangentialArcToInfo,
|
|
|
|
parse,
|
|
|
|
Path,
|
|
|
|
PathToNode,
|
|
|
|
PipeExpression,
|
|
|
|
Program,
|
|
|
|
ProgramMemory,
|
|
|
|
recast,
|
|
|
|
SketchGroup,
|
2024-07-08 17:57:37 -04:00
|
|
|
ExtrudeGroup,
|
2024-02-11 12:59:00 +11:00
|
|
|
VariableDeclaration,
|
|
|
|
VariableDeclarator,
|
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
|
|
|
sketchGroupFromKclValue,
|
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-07-02 17:16:27 +10:00
|
|
|
import { executeAst } from 'lang/langHelpers'
|
2024-02-11 12:59:00 +11:00
|
|
|
import {
|
|
|
|
createArcGeometry,
|
|
|
|
dashedStraight,
|
2024-03-02 08:48:30 +11:00
|
|
|
profileStart,
|
2024-02-11 12:59:00 +11:00
|
|
|
straightSegment,
|
|
|
|
tangentialArcToSegment,
|
|
|
|
} 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-08-22 16:08:49 -04:00
|
|
|
import {
|
|
|
|
isArray,
|
|
|
|
isOverlap,
|
|
|
|
normaliseAngle,
|
|
|
|
roundOff,
|
|
|
|
throttle,
|
|
|
|
} from 'lib/utils'
|
2024-02-11 12:59:00 +11:00
|
|
|
import {
|
2024-07-30 14:16:53 -04:00
|
|
|
addStartProfileAt,
|
2024-02-11 12:59:00 +11:00
|
|
|
createArrayExpression,
|
|
|
|
createCallExpressionStdLib,
|
|
|
|
createLiteral,
|
2024-04-19 11:56:21 -04:00
|
|
|
createPipeExpression,
|
2024-02-11 12:59:00 +11:00
|
|
|
createPipeSubstitution,
|
2024-04-19 11:56:21 -04:00
|
|
|
findUniqueName,
|
2024-02-11 12:59:00 +11:00
|
|
|
} from 'lang/modifyAst'
|
2024-08-03 18:08:51 +10:00
|
|
|
import { Selections, getEventForSegmentSelection } from 'lib/selections'
|
2024-02-11 12:59:00 +11:00
|
|
|
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
|
|
|
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-05-24 20:54:42 +10:00
|
|
|
import { SegmentOverlayPayload, SketchDetails } from 'machines/modelingMachine'
|
2024-07-25 19:03:56 +10:00
|
|
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
2024-04-19 11:56:21 -04:00
|
|
|
import {
|
|
|
|
getRectangleCallExpressions,
|
|
|
|
updateRectangleSketch,
|
|
|
|
} from 'lib/rectangleTool'
|
2024-09-10 13:30:39 -04:00
|
|
|
import { getThemeColorForThreeJs, Themes } from 'lib/theme'
|
2024-09-09 18:17:45 -04: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-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'
|
|
|
|
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-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
|
|
|
|
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-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-02-11 12:59:00 +11:00
|
|
|
if (
|
|
|
|
segment.userData.from &&
|
|
|
|
segment.userData.to &&
|
|
|
|
segment.userData.type === STRAIGHT_SEGMENT
|
|
|
|
) {
|
2024-05-24 20:54:42 +10:00
|
|
|
callbacks.push(
|
|
|
|
this.updateStraightSegment({
|
|
|
|
from: segment.userData.from,
|
|
|
|
to: segment.userData.to,
|
|
|
|
group: segment,
|
|
|
|
scale: factor,
|
|
|
|
})
|
|
|
|
)
|
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-05-24 20:54:42 +10:00
|
|
|
callbacks.push(
|
|
|
|
this.updateTangentialArcToSegment({
|
|
|
|
prevSegment: segment.userData.prevSegment,
|
|
|
|
from: segment.userData.from,
|
|
|
|
to: segment.userData.to,
|
|
|
|
group: segment,
|
|
|
|
scale: factor,
|
|
|
|
})
|
|
|
|
)
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
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)) {
|
2024-08-03 07:05:35 +10:00
|
|
|
// this.removeIntersectionPlane()
|
2024-02-26 19:53:44 +11:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
removeIntersectionPlane() {
|
|
|
|
const intersectionPlane = this.scene.getObjectByName(RAYCASTABLE_PLANE)
|
|
|
|
if (intersectionPlane) this.scene.remove(intersectionPlane)
|
|
|
|
}
|
|
|
|
|
2024-07-30 14:16:53 -04:00
|
|
|
setupNoPointsListener({
|
|
|
|
sketchDetails,
|
|
|
|
afterClick,
|
|
|
|
}: {
|
|
|
|
sketchDetails: SketchDetails
|
|
|
|
afterClick: (args: OnClickCallbackArgs) => void
|
|
|
|
}) {
|
|
|
|
// Create a THREEjs plane to raycast clicks onto
|
|
|
|
this.createIntersectionPlane()
|
|
|
|
const quaternion = quaternionFromUpNForward(
|
|
|
|
new Vector3(...sketchDetails.yAxis),
|
|
|
|
new Vector3(...sketchDetails.zAxis)
|
|
|
|
)
|
|
|
|
|
|
|
|
// Position the click raycast plane
|
|
|
|
if (this.intersectionPlane) {
|
|
|
|
this.intersectionPlane.setRotationFromQuaternion(quaternion)
|
|
|
|
this.intersectionPlane.position.copy(
|
|
|
|
new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
sceneInfra.setCallbacks({
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
2024-07-30 14:16:53 -04:00
|
|
|
onClick: async (args) => {
|
|
|
|
if (!args) return
|
|
|
|
if (args.mouseEvent.which !== 1) return
|
|
|
|
const { intersectionPoint } = args
|
|
|
|
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) return
|
|
|
|
const addStartProfileAtRes = addStartProfileAt(
|
|
|
|
kclManager.ast,
|
|
|
|
sketchDetails.sketchPathToNode,
|
|
|
|
[intersectionPoint.twoD.x, intersectionPoint.twoD.y]
|
|
|
|
)
|
|
|
|
|
|
|
|
if (trap(addStartProfileAtRes)) return
|
|
|
|
const { modifiedAst } = addStartProfileAtRes
|
|
|
|
|
|
|
|
await kclManager.updateAst(modifiedAst, false)
|
|
|
|
this.removeIntersectionPlane()
|
|
|
|
|
|
|
|
// Now perform the caller-specified action
|
|
|
|
afterClick(args)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
async setupSketch({
|
|
|
|
sketchPathToNode,
|
2024-03-22 10:23:04 +11:00
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position,
|
2024-03-25 15:20:43 +11:00
|
|
|
maybeModdedAst,
|
|
|
|
draftExpressionsIndices,
|
2024-06-22 04:49:31 -04:00
|
|
|
selectionRanges,
|
2024-02-11 12:59:00 +11:00
|
|
|
}: {
|
|
|
|
sketchPathToNode: PathToNode
|
2024-03-25 15:20:43 +11:00
|
|
|
maybeModdedAst: Program
|
|
|
|
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<{
|
|
|
|
truncatedAst: Program
|
|
|
|
programMemoryOverride: ProgramMemory
|
|
|
|
sketchGroup: SketchGroup
|
|
|
|
variableDeclarationName: string
|
|
|
|
}> {
|
2024-02-11 12:59:00 +11:00
|
|
|
this.createIntersectionPlane()
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const prepared = this.prepareTruncatedMemoryAndAst(
|
|
|
|
sketchPathToNode || [],
|
|
|
|
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-02-11 12:59:00 +11:00
|
|
|
const { programMemory } = await executeAst({
|
|
|
|
ast: truncatedAst,
|
|
|
|
useFakeExecutor: true,
|
2024-03-22 16:55:30 +11:00
|
|
|
engineCommandManager: this.engineCommandManager,
|
2024-02-11 12:59:00 +11:00
|
|
|
programMemoryOverride,
|
|
|
|
})
|
|
|
|
const sketchGroup = sketchGroupFromPathToNode({
|
|
|
|
pathToNode: sketchPathToNode,
|
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
|
|
|
if (err(sketchGroup)) return Promise.reject(sketchGroup)
|
2024-08-07 15:15:22 -04:00
|
|
|
if (!sketchGroup) return Promise.reject('sketchGroup not found')
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-08-22 16:08:49 -04:00
|
|
|
if (!isArray(sketchGroup?.value))
|
2024-03-25 15:20:43 +11:00
|
|
|
return {
|
|
|
|
truncatedAst,
|
|
|
|
programMemoryOverride,
|
|
|
|
sketchGroup,
|
|
|
|
variableDeclarationName,
|
|
|
|
}
|
2024-02-14 05:35:05 +11:00
|
|
|
this.sceneProgramMemory = programMemory
|
2024-02-11 12:59:00 +11:00
|
|
|
const group = new Group()
|
2024-03-22 10:23:04 +11:00
|
|
|
position && group.position.set(...position)
|
2024-02-11 12:59:00 +11:00
|
|
|
group.userData = {
|
|
|
|
type: SKETCH_GROUP_SEGMENTS,
|
|
|
|
pathToNode: sketchPathToNode,
|
|
|
|
}
|
|
|
|
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-02-26 19:53:44 +11:00
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
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, dummy)) /
|
|
|
|
sceneInfra._baseUnitMultiplier
|
2024-03-02 08:48:30 +11:00
|
|
|
|
2024-03-04 08:14:37 +11:00
|
|
|
const segPathToNode = getNodePathFromSourceRange(
|
2024-04-19 11:56:21 -04:00
|
|
|
maybeModdedAst,
|
2024-03-02 08:48:30 +11:00
|
|
|
sketchGroup.start.__geoMeta.sourceRange
|
|
|
|
)
|
|
|
|
const _profileStart = profileStart({
|
|
|
|
from: sketchGroup.start.from,
|
|
|
|
id: sketchGroup.start.__geoMeta.id,
|
|
|
|
pathToNode: segPathToNode,
|
|
|
|
scale: factor,
|
2024-05-09 08:38:42 -04:00
|
|
|
theme: sceneInfra._theme,
|
2024-03-02 08:48:30 +11:00
|
|
|
})
|
|
|
|
_profileStart.layers.set(SKETCH_LAYER)
|
|
|
|
_profileStart.traverse((child) => {
|
|
|
|
child.layers.set(SKETCH_LAYER)
|
|
|
|
})
|
|
|
|
group.add(_profileStart)
|
|
|
|
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
|
2024-05-24 20:54:42 +10:00
|
|
|
const callbacks: (() => SegmentOverlayPayload | null)[] = []
|
2024-02-11 12:59:00 +11:00
|
|
|
sketchGroup.value.forEach((segment, index) => {
|
|
|
|
let segPathToNode = getNodePathFromSourceRange(
|
2024-04-19 11:56:21 -04:00
|
|
|
maybeModdedAst,
|
2024-02-11 12:59:00 +11:00
|
|
|
segment.__geoMeta.sourceRange
|
|
|
|
)
|
2024-03-25 15:20:43 +11:00
|
|
|
if (
|
|
|
|
draftExpressionsIndices &&
|
|
|
|
(sketchGroup.value[index - 1] || sketchGroup.start)
|
|
|
|
) {
|
2024-03-04 08:14:37 +11:00
|
|
|
const previousSegment =
|
|
|
|
sketchGroup.value[index - 1] || sketchGroup.start
|
|
|
|
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
2024-04-19 11:56:21 -04:00
|
|
|
maybeModdedAst,
|
2024-03-04 08:14:37 +11:00
|
|
|
previousSegment.__geoMeta.sourceRange
|
|
|
|
)
|
|
|
|
const bodyIndex = previousSegmentPathToNode[1][0]
|
|
|
|
segPathToNode = getNodePathFromSourceRange(
|
|
|
|
truncatedAst,
|
|
|
|
segment.__geoMeta.sourceRange
|
|
|
|
)
|
|
|
|
segPathToNode[1][0] = bodyIndex
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
const isDraftSegment =
|
2024-03-25 15:20:43 +11:00
|
|
|
draftExpressionsIndices &&
|
|
|
|
index <= draftExpressionsIndices.end &&
|
|
|
|
index >= draftExpressionsIndices.start
|
2024-06-22 04:49:31 -04:00
|
|
|
const isSelected = selectionRanges?.codeBasedSelections.some(
|
|
|
|
(selection) => {
|
|
|
|
return isOverlap(selection.range, segment.__geoMeta.sourceRange)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2024-07-08 16:41:00 -04:00
|
|
|
let seg: Group
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node1 = getNodeFromPath<CallExpression>(
|
2024-04-19 11:56:21 -04:00
|
|
|
maybeModdedAst,
|
2024-03-04 14:18:08 +11:00
|
|
|
segPathToNode,
|
|
|
|
'CallExpression'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(_node1)) return
|
|
|
|
const callExpName = _node1.node?.callee?.name
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
if (segment.type === 'TangentialArcTo') {
|
|
|
|
seg = tangentialArcToSegment({
|
|
|
|
prevSegment: sketchGroup.value[index - 1],
|
|
|
|
from: segment.from,
|
|
|
|
to: segment.to,
|
|
|
|
id: segment.__geoMeta.id,
|
|
|
|
pathToNode: segPathToNode,
|
|
|
|
isDraftSegment,
|
|
|
|
scale: factor,
|
2024-04-03 13:22:56 +11:00
|
|
|
texture: sceneInfra.extraSegmentTexture,
|
2024-05-09 08:38:42 -04:00
|
|
|
theme: sceneInfra._theme,
|
2024-06-22 04:49:31 -04:00
|
|
|
isSelected,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
2024-05-24 20:54:42 +10:00
|
|
|
callbacks.push(
|
|
|
|
this.updateTangentialArcToSegment({
|
|
|
|
prevSegment: sketchGroup.value[index - 1],
|
|
|
|
from: segment.from,
|
|
|
|
to: segment.to,
|
|
|
|
group: seg,
|
|
|
|
scale: factor,
|
|
|
|
})
|
|
|
|
)
|
2024-02-11 12:59:00 +11:00
|
|
|
} else {
|
|
|
|
seg = straightSegment({
|
|
|
|
from: segment.from,
|
|
|
|
to: segment.to,
|
|
|
|
id: segment.__geoMeta.id,
|
|
|
|
pathToNode: segPathToNode,
|
|
|
|
isDraftSegment,
|
|
|
|
scale: factor,
|
2024-03-04 14:18:08 +11:00
|
|
|
callExpName,
|
2024-04-03 13:22:56 +11:00
|
|
|
texture: sceneInfra.extraSegmentTexture,
|
2024-05-09 08:38:42 -04:00
|
|
|
theme: sceneInfra._theme,
|
2024-06-22 04:49:31 -04:00
|
|
|
isSelected,
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
2024-05-24 20:54:42 +10:00
|
|
|
callbacks.push(
|
|
|
|
this.updateStraightSegment({
|
|
|
|
from: segment.from,
|
|
|
|
to: segment.to,
|
|
|
|
group: seg,
|
|
|
|
scale: factor,
|
|
|
|
})
|
|
|
|
)
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
seg.layers.set(SKETCH_LAYER)
|
|
|
|
seg.traverse((child) => {
|
|
|
|
child.layers.set(SKETCH_LAYER)
|
|
|
|
})
|
|
|
|
|
|
|
|
group.add(seg)
|
|
|
|
this.activeSegments[JSON.stringify(segPathToNode)] = seg
|
|
|
|
})
|
|
|
|
|
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,
|
|
|
|
sketchGroup,
|
|
|
|
variableDeclarationName,
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
updateAstAndRejigSketch = async (
|
|
|
|
sketchPathToNode: PathToNode,
|
2024-06-24 11:45:40 -04:00
|
|
|
modifiedAst: 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-06-24 11:45:40 -04:00
|
|
|
if (err(modifiedAst)) return modifiedAst
|
|
|
|
|
|
|
|
const nextAst = await kclManager.updateAst(modifiedAst, false)
|
2024-02-11 12:59:00 +11:00
|
|
|
await this.tearDownSketch({ removeAxis: false })
|
2024-04-03 13:22:56 +11:00
|
|
|
sceneInfra.resetMouseListeners()
|
2024-03-25 15:20:43 +11:00
|
|
|
await this.setupSketch({
|
|
|
|
sketchPathToNode,
|
|
|
|
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,
|
|
|
|
pathToNode: sketchPathToNode,
|
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
return nextAst
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-03-25 15:20:43 +11:00
|
|
|
setUpDraftSegment = async (
|
2024-03-22 10:23:04 +11:00
|
|
|
sketchPathToNode: PathToNode,
|
|
|
|
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,
|
|
|
|
sketchPathToNode || [],
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (trap(_node1)) return Promise.reject(_node1)
|
2024-03-25 15:20:43 +11:00
|
|
|
const variableDeclarationName =
|
2024-06-24 11:45:40 -04:00
|
|
|
_node1.node?.declarations?.[0]?.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
|
|
|
const sg = sketchGroupFromKclValue(
|
|
|
|
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
|
|
|
)
|
|
|
|
if (err(sg)) return sg
|
|
|
|
const lastSeg = sg.value?.slice(-1)[0] || sg.start
|
2024-03-25 15:20:43 +11:00
|
|
|
|
|
|
|
const index = sg.value.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,
|
|
|
|
to: [lastSeg.to[0], lastSeg.to[1]],
|
|
|
|
from: [lastSeg.to[0], lastSeg.to[1]],
|
|
|
|
fnName: segmentName,
|
|
|
|
pathToNode: sketchPathToNode,
|
2024-04-03 13:22:56 +11:00
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(mod)) return Promise.reject(mod)
|
2024-04-03 13:22:56 +11:00
|
|
|
const modifiedAst = parse(recast(mod.modifiedAst))
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
2024-03-25 15:20:43 +11:00
|
|
|
|
|
|
|
const draftExpressionsIndices = { start: index, end: index }
|
|
|
|
|
|
|
|
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
|
2024-04-03 13:22:56 +11:00
|
|
|
sceneInfra.resetMouseListeners()
|
2024-06-29 18:10:07 -07:00
|
|
|
|
2024-03-25 15:20:43 +11:00
|
|
|
const { truncatedAst, programMemoryOverride, sketchGroup } =
|
|
|
|
await this.setupSketch({
|
|
|
|
sketchPathToNode,
|
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: origin,
|
|
|
|
maybeModdedAst: modifiedAst,
|
|
|
|
draftExpressionsIndices,
|
|
|
|
})
|
|
|
|
sceneInfra.setCallbacks({
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
2024-03-25 15:20:43 +11:00
|
|
|
onClick: async (args) => {
|
|
|
|
if (!args) return
|
|
|
|
if (args.mouseEvent.which !== 1) return
|
|
|
|
const { intersectionPoint } = args
|
|
|
|
let intersection2d = intersectionPoint?.twoD
|
|
|
|
const profileStart = args.intersects
|
|
|
|
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
|
|
|
|
.find((a) => a?.name === PROFILE_START)
|
|
|
|
|
|
|
|
let modifiedAst
|
|
|
|
if (profileStart) {
|
2024-05-23 00:53:15 -04:00
|
|
|
const lastSegment = sketchGroup.value.slice(-1)[0]
|
|
|
|
modifiedAst = addCallExpressionsToPipe({
|
2024-03-25 15:20:43 +11:00
|
|
|
node: kclManager.ast,
|
|
|
|
programMemory: kclManager.programMemory,
|
|
|
|
pathToNode: sketchPathToNode,
|
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,
|
|
|
|
pathToNode: sketchPathToNode,
|
2024-03-25 15:20:43 +11:00
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
2024-03-25 15:20:43 +11:00
|
|
|
} else if (intersection2d) {
|
|
|
|
const lastSegment = sketchGroup.value.slice(-1)[0]
|
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,
|
|
|
|
to: [intersection2d.x, intersection2d.y],
|
|
|
|
from: [lastSegment.to[0], lastSegment.to[1]],
|
|
|
|
fnName:
|
|
|
|
lastSegment.type === 'TangentialArcTo'
|
|
|
|
? 'tangentialArcTo'
|
|
|
|
: 'line',
|
|
|
|
pathToNode: sketchPathToNode,
|
2024-06-24 11:45:40 -04:00
|
|
|
})
|
|
|
|
if (trap(tmp)) return Promise.reject(tmp)
|
|
|
|
modifiedAst = tmp.modifiedAst
|
|
|
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
2024-03-25 15:20:43 +11:00
|
|
|
} else {
|
|
|
|
// return early as we didn't modify the ast
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-17 20:18:07 -07:00
|
|
|
await kclManager.executeAstMock(modifiedAst)
|
2024-05-23 00:53:15 -04:00
|
|
|
if (profileStart) {
|
|
|
|
sceneInfra.modelingSend({ type: 'CancelSketch' })
|
|
|
|
} else {
|
2024-09-09 18:17:45 -04:00
|
|
|
await this.setUpDraftSegment(
|
2024-05-23 00:53:15 -04:00
|
|
|
sketchPathToNode,
|
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
origin,
|
|
|
|
segmentName
|
|
|
|
)
|
|
|
|
}
|
2024-03-25 15:20:43 +11:00
|
|
|
},
|
|
|
|
onMove: (args) => {
|
|
|
|
this.onDragSegment({
|
|
|
|
intersection2d: args.intersectionPoint.twoD,
|
|
|
|
object: Object.values(this.activeSegments).slice(-1)[0],
|
|
|
|
intersects: args.intersects,
|
|
|
|
sketchPathToNode,
|
|
|
|
draftInfo: {
|
|
|
|
truncatedAst,
|
|
|
|
programMemoryOverride,
|
|
|
|
variableDeclarationName,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
},
|
2024-04-04 11:07:51 +11:00
|
|
|
...this.mouseEnterLeaveCallbacks(),
|
2024-03-25 15:20:43 +11:00
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-04-19 11:56:21 -04:00
|
|
|
setupDraftRectangle = async (
|
|
|
|
sketchPathToNode: PathToNode,
|
|
|
|
forward: [number, number, number],
|
|
|
|
up: [number, number, number],
|
|
|
|
sketchOrigin: [number, number, number],
|
|
|
|
rectangleOrigin: [x: number, y: number]
|
|
|
|
) => {
|
2024-07-25 20:11:46 -04:00
|
|
|
let _ast = structuredClone(kclManager.ast)
|
2024-04-19 11:56:21 -04:00
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node1 = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
|
|
|
sketchPathToNode || [],
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (trap(_node1)) return Promise.reject(_node1)
|
2024-04-19 11:56:21 -04:00
|
|
|
const variableDeclarationName =
|
2024-06-24 11:45:40 -04:00
|
|
|
_node1.node?.declarations?.[0]?.id?.name || ''
|
2024-07-30 14:16:53 -04:00
|
|
|
const startSketchOn = _node1.node?.declarations
|
|
|
|
const startSketchOnInit = startSketchOn?.[0]?.init
|
2024-04-19 11:56:21 -04:00
|
|
|
|
|
|
|
const tags: [string, string, string] = [
|
|
|
|
findUniqueName(_ast, 'rectangleSegmentA'),
|
|
|
|
findUniqueName(_ast, 'rectangleSegmentB'),
|
|
|
|
findUniqueName(_ast, 'rectangleSegmentC'),
|
|
|
|
]
|
|
|
|
|
|
|
|
startSketchOn[0].init = createPipeExpression([
|
|
|
|
startSketchOnInit,
|
|
|
|
...getRectangleCallExpressions(rectangleOrigin, tags),
|
|
|
|
])
|
|
|
|
|
2024-07-25 20:11:46 -04:00
|
|
|
let _recastAst = parse(recast(_ast))
|
|
|
|
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
|
|
|
_ast = _recastAst
|
2024-04-19 11:56:21 -04:00
|
|
|
|
|
|
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
|
|
|
sketchPathToNode,
|
|
|
|
forward,
|
|
|
|
up,
|
|
|
|
position: sketchOrigin,
|
|
|
|
maybeModdedAst: _ast,
|
|
|
|
draftExpressionsIndices: { start: 0, end: 3 },
|
|
|
|
})
|
|
|
|
|
|
|
|
sceneInfra.setCallbacks({
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
2024-04-19 11:56:21 -04:00
|
|
|
onMove: async (args) => {
|
|
|
|
// Update the width and height of the draft rectangle
|
2024-07-25 20:11:46 -04:00
|
|
|
const pathToNodeTwo = structuredClone(sketchPathToNode)
|
2024-04-19 11:56:21 -04:00
|
|
|
pathToNodeTwo[1][0] = 0
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
2024-04-19 11:56:21 -04:00
|
|
|
truncatedAst,
|
|
|
|
pathToNodeTwo || [],
|
|
|
|
'VariableDeclaration'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (trap(_node)) return Promise.reject(_node)
|
|
|
|
const sketchInit = _node.node?.declarations?.[0]?.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') {
|
|
|
|
updateRectangleSketch(sketchInit, x, y, tags[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
const { programMemory } = await executeAst({
|
|
|
|
ast: truncatedAst,
|
|
|
|
useFakeExecutor: true,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
|
|
|
programMemoryOverride,
|
|
|
|
})
|
|
|
|
this.sceneProgramMemory = programMemory
|
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
|
|
|
const sketchGroup = sketchGroupFromKclValue(
|
|
|
|
programMemory.get(variableDeclarationName),
|
2024-04-19 11:56:21 -04: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
|
|
|
)
|
|
|
|
if (err(sketchGroup)) return sketchGroup
|
2024-04-19 11:56:21 -04:00
|
|
|
const sgPaths = sketchGroup.value
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
|
|
|
this.updateSegment(
|
|
|
|
sketchGroup.start,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
_ast,
|
|
|
|
orthoFactor,
|
|
|
|
sketchGroup
|
|
|
|
)
|
|
|
|
sgPaths.forEach((seg, index) =>
|
|
|
|
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup)
|
|
|
|
)
|
|
|
|
},
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
2024-04-19 11:56:21 -04:00
|
|
|
onClick: async (args) => {
|
|
|
|
// 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,
|
|
|
|
sketchPathToNode || [],
|
|
|
|
'VariableDeclaration'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (trap(_node)) return Promise.reject(_node)
|
|
|
|
const sketchInit = _node.node?.declarations?.[0]?.init
|
2024-04-19 11:56:21 -04:00
|
|
|
|
|
|
|
if (sketchInit.type === 'PipeExpression') {
|
|
|
|
updateRectangleSketch(sketchInit, x, y, tags[0])
|
|
|
|
|
2024-07-25 20:11:46 -04:00
|
|
|
let _recastAst = parse(recast(_ast))
|
|
|
|
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
|
|
|
_ast = _recastAst
|
2024-04-19 11:56:21 -04:00
|
|
|
|
|
|
|
// Update the primary AST and unequip the rectangle tool
|
|
|
|
await kclManager.executeAstMock(_ast)
|
2024-07-05 13:40:16 +10:00
|
|
|
sceneInfra.modelingSend({ type: 'Finish rectangle' })
|
2024-04-19 11:56:21 -04:00
|
|
|
|
|
|
|
const { programMemory } = await executeAst({
|
|
|
|
ast: _ast,
|
|
|
|
useFakeExecutor: true,
|
|
|
|
engineCommandManager: this.engineCommandManager,
|
|
|
|
programMemoryOverride,
|
|
|
|
})
|
|
|
|
|
|
|
|
// Prepare to update the THREEjs scene
|
|
|
|
this.sceneProgramMemory = programMemory
|
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
|
|
|
const sketchGroup = sketchGroupFromKclValue(
|
|
|
|
programMemory.get(variableDeclarationName),
|
2024-04-19 11:56:21 -04: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
|
|
|
)
|
|
|
|
if (err(sketchGroup)) return sketchGroup
|
2024-04-19 11:56:21 -04:00
|
|
|
const sgPaths = sketchGroup.value
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
|
|
|
// Update the starting segment of the THREEjs scene
|
|
|
|
this.updateSegment(
|
|
|
|
sketchGroup.start,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
_ast,
|
|
|
|
orthoFactor,
|
|
|
|
sketchGroup
|
|
|
|
)
|
|
|
|
// Update the rest of the segments of the THREEjs scene
|
|
|
|
sgPaths.forEach((seg, index) =>
|
|
|
|
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
2024-04-03 13:22:56 +11:00
|
|
|
setupSketchIdleCallbacks = ({
|
|
|
|
pathToNode,
|
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
}: {
|
|
|
|
pathToNode: PathToNode
|
|
|
|
forward: [number, number, number]
|
|
|
|
up: [number, number, number]
|
|
|
|
position?: [number, number, number]
|
|
|
|
}) => {
|
|
|
|
let addingNewSegmentStatus: 'nothing' | 'pending' | 'added' = 'nothing'
|
2024-03-25 15:48:51 +11:00
|
|
|
sceneInfra.setCallbacks({
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
2024-04-03 13:22:56 +11:00
|
|
|
onDragEnd: async () => {
|
|
|
|
if (addingNewSegmentStatus !== 'nothing') {
|
|
|
|
await this.tearDownSketch({ removeAxis: false })
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-04-03 13:22:56 +11:00
|
|
|
this.setupSketch({
|
|
|
|
sketchPathToNode: pathToNode,
|
|
|
|
maybeModdedAst: kclManager.ast,
|
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
})
|
|
|
|
// setting up the callbacks again resets value in closures
|
|
|
|
this.setupSketchIdleCallbacks({
|
|
|
|
pathToNode,
|
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
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'
|
|
|
|
)
|
|
|
|
|
|
|
|
const sketchGroup = sketchGroupFromPathToNode({
|
|
|
|
pathToNode,
|
|
|
|
ast: kclManager.ast,
|
|
|
|
programMemory: kclManager.programMemory,
|
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(sketchGroup)) return
|
2024-08-07 15:15:22 -04:00
|
|
|
if (!sketchGroup) {
|
|
|
|
trap(new Error('sketchGroup not found'))
|
|
|
|
return
|
|
|
|
}
|
2024-04-03 13:22:56 +11:00
|
|
|
|
|
|
|
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
|
|
|
|
if (addingNewSegmentStatus === 'nothing') {
|
|
|
|
const prevSegment = sketchGroup.value[pipeIndex - 2]
|
|
|
|
const mod = addNewSketchLn({
|
|
|
|
node: kclManager.ast,
|
|
|
|
programMemory: kclManager.programMemory,
|
|
|
|
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
|
|
|
|
from: [prevSegment.from[0], prevSegment.from[1]],
|
|
|
|
// TODO assuming it's always a straight segments being added
|
|
|
|
// as this is easiest, and we'll need to add "tabbing" behavior
|
|
|
|
// to support other segment types
|
|
|
|
fnName: 'line',
|
|
|
|
pathToNode: pathToNode,
|
|
|
|
spliceBetween: true,
|
|
|
|
})
|
|
|
|
addingNewSegmentStatus = 'pending'
|
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-04-03 13:22:56 +11:00
|
|
|
await this.tearDownSketch({ removeAxis: false })
|
2024-09-09 18:17:45 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-04-03 13:22:56 +11:00
|
|
|
this.setupSketch({
|
|
|
|
sketchPathToNode: pathToNode,
|
|
|
|
maybeModdedAst: kclManager.ast,
|
|
|
|
up,
|
|
|
|
forward,
|
|
|
|
position,
|
|
|
|
})
|
|
|
|
addingNewSegmentStatus = 'added'
|
|
|
|
} else if (addingNewSegmentStatus === 'added') {
|
|
|
|
const pathToNodeForNewSegment = pathToNode.slice(0, pathToNodeIndex)
|
|
|
|
pathToNodeForNewSegment.push([pipeIndex - 2, 'index'])
|
|
|
|
this.onDragSegment({
|
|
|
|
sketchPathToNode: pathToNodeForNewSegment,
|
|
|
|
object: selected,
|
|
|
|
intersection2d: intersectionPoint.twoD,
|
|
|
|
intersects,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-03-25 15:48:51 +11:00
|
|
|
this.onDragSegment({
|
|
|
|
object: selected,
|
|
|
|
intersection2d: intersectionPoint.twoD,
|
|
|
|
intersects,
|
|
|
|
sketchPathToNode: pathToNode,
|
|
|
|
})
|
|
|
|
},
|
|
|
|
onMove: () => {},
|
|
|
|
onClick: (args) => {
|
|
|
|
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 = (
|
|
|
|
sketchPathToNode: PathToNode,
|
|
|
|
ast?: Program,
|
|
|
|
draftSegment?: DraftSegment
|
|
|
|
) =>
|
|
|
|
prepareTruncatedMemoryAndAst(
|
|
|
|
sketchPathToNode,
|
|
|
|
ast || kclManager.ast,
|
|
|
|
kclManager.programMemory,
|
|
|
|
draftSegment
|
|
|
|
)
|
|
|
|
onDragSegment({
|
|
|
|
object,
|
2024-03-04 08:14:37 +11:00
|
|
|
intersection2d: _intersection2d,
|
2024-02-11 12:59:00 +11:00
|
|
|
sketchPathToNode,
|
|
|
|
draftInfo,
|
2024-03-04 08:14:37 +11:00
|
|
|
intersects,
|
2024-02-11 12:59:00 +11:00
|
|
|
}: {
|
|
|
|
object: any
|
|
|
|
intersection2d: Vector2
|
|
|
|
sketchPathToNode: PathToNode
|
2024-03-04 08:14:37 +11:00
|
|
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
2024-02-11 12:59:00 +11:00
|
|
|
draftInfo?: {
|
|
|
|
truncatedAst: Program
|
|
|
|
programMemoryOverride: ProgramMemory
|
|
|
|
variableDeclarationName: string
|
|
|
|
}
|
|
|
|
}) {
|
2024-03-04 08:14:37 +11:00
|
|
|
const profileStart =
|
|
|
|
draftInfo &&
|
|
|
|
intersects
|
|
|
|
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
|
|
|
|
.find((a) => a?.name === PROFILE_START)
|
|
|
|
const intersection2d = profileStart
|
|
|
|
? new Vector2(profileStart.position.x, profileStart.position.y)
|
|
|
|
: _intersection2d
|
|
|
|
|
2024-03-02 08:48:30 +11:00
|
|
|
const group = getParentGroup(object, [
|
|
|
|
STRAIGHT_SEGMENT,
|
|
|
|
TANGENTIAL_ARC_TO_SEGMENT,
|
|
|
|
PROFILE_START,
|
|
|
|
])
|
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
|
|
|
if (draftInfo) {
|
|
|
|
pathToNode[1][0] = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
const from: [number, number] = [
|
|
|
|
group.userData.from[0],
|
|
|
|
group.userData.from[1],
|
|
|
|
]
|
|
|
|
const to: [number, number] = [intersection2d.x, intersection2d.y]
|
|
|
|
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node = getNodeFromPath<CallExpression>(
|
2024-02-11 12:59:00 +11:00
|
|
|
modifiedAst,
|
|
|
|
pathToNode,
|
|
|
|
'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:
|
|
|
|
| {
|
|
|
|
modifiedAst: Program
|
|
|
|
pathToNode: PathToNode
|
|
|
|
}
|
|
|
|
| Error
|
2024-03-02 08:48:30 +11:00
|
|
|
if (group.name === PROFILE_START) {
|
|
|
|
modded = updateStartProfileAtArgs({
|
|
|
|
node: modifiedAst,
|
|
|
|
pathToNode,
|
|
|
|
to,
|
|
|
|
from,
|
|
|
|
previousProgramMemory: kclManager.programMemory,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
modded = changeSketchArguments(
|
|
|
|
modifiedAst,
|
|
|
|
kclManager.programMemory,
|
|
|
|
[node.start, node.end],
|
|
|
|
to,
|
|
|
|
from
|
|
|
|
)
|
|
|
|
}
|
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
|
|
|
|
: this.prepareTruncatedMemoryAndAst(pathToNode || [])
|
|
|
|
if (trap(info, { suppress: true })) return
|
2024-02-11 12:59:00 +11:00
|
|
|
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
2024-06-24 11:45:40 -04:00
|
|
|
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-02-11 12:59:00 +11:00
|
|
|
const { programMemory } = await executeAst({
|
|
|
|
ast: truncatedAst,
|
|
|
|
useFakeExecutor: true,
|
2024-03-22 16:55:30 +11:00
|
|
|
engineCommandManager: this.engineCommandManager,
|
2024-02-11 12:59:00 +11:00
|
|
|
programMemoryOverride,
|
|
|
|
})
|
|
|
|
this.sceneProgramMemory = programMemory
|
2024-07-08 17:57:37 -04:00
|
|
|
|
2024-07-22 19:43:40 -04:00
|
|
|
const maybeSketchGroup = programMemory.get(variableDeclarationName)
|
2024-07-08 17:57:37 -04:00
|
|
|
let sketchGroup = undefined
|
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
|
|
|
const sg = sketchGroupFromKclValue(
|
|
|
|
maybeSketchGroup,
|
|
|
|
variableDeclarationName
|
|
|
|
)
|
|
|
|
if (!err(sg)) {
|
|
|
|
sketchGroup = sg
|
2024-07-08 17:57:37 -04:00
|
|
|
} else if ((maybeSketchGroup as ExtrudeGroup).sketchGroup) {
|
|
|
|
sketchGroup = (maybeSketchGroup as ExtrudeGroup).sketchGroup
|
|
|
|
}
|
|
|
|
if (!sketchGroup) return
|
|
|
|
|
2024-03-02 08:48:30 +11:00
|
|
|
const sgPaths = sketchGroup.value
|
2024-02-26 19:53:44 +11:00
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
2024-03-02 08:48:30 +11:00
|
|
|
|
2024-04-19 11:56:21 -04:00
|
|
|
this.updateSegment(
|
|
|
|
sketchGroup.start,
|
|
|
|
0,
|
|
|
|
varDecIndex,
|
|
|
|
modifiedAst,
|
|
|
|
orthoFactor,
|
|
|
|
sketchGroup
|
|
|
|
)
|
2024-05-24 20:54:42 +10:00
|
|
|
|
|
|
|
const callBacks = sgPaths.map((group, index) =>
|
2024-04-19 11:56:21 -04:00
|
|
|
this.updateSegment(
|
|
|
|
group,
|
|
|
|
index,
|
|
|
|
varDecIndex,
|
2024-02-11 12:59:00 +11:00
|
|
|
modifiedAst,
|
2024-04-19 11:56:21 -04:00
|
|
|
orthoFactor,
|
|
|
|
sketchGroup
|
2024-02-11 12:59:00 +11:00
|
|
|
)
|
2024-04-19 11:56:21 -04:00
|
|
|
)
|
2024-05-24 20:54:42 +10:00
|
|
|
sceneInfra.overlayCallbacks(callBacks)
|
2024-09-09 18:17:45 -04:00
|
|
|
})().catch(reportRejection)
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
|
2024-04-19 11:56:21 -04:00
|
|
|
/**
|
|
|
|
* Update the THREEjs sketch entities with new segment data
|
|
|
|
* mapping them back to the AST
|
|
|
|
* @param segment
|
|
|
|
* @param index
|
|
|
|
* @param varDecIndex
|
|
|
|
* @param modifiedAst
|
|
|
|
* @param orthoFactor
|
|
|
|
* @param sketchGroup
|
|
|
|
*/
|
|
|
|
updateSegment = (
|
|
|
|
segment: Path | SketchGroup['start'],
|
|
|
|
index: number,
|
|
|
|
varDecIndex: number,
|
|
|
|
modifiedAst: Program,
|
|
|
|
orthoFactor: number,
|
|
|
|
sketchGroup: SketchGroup
|
2024-05-24 20:54:42 +10:00
|
|
|
): (() => SegmentOverlayPayload | null) => {
|
2024-04-19 11:56:21 -04:00
|
|
|
const segPathToNode = getNodePathFromSourceRange(
|
|
|
|
modifiedAst,
|
|
|
|
segment.__geoMeta.sourceRange
|
|
|
|
)
|
|
|
|
const sgPaths = sketchGroup.value
|
|
|
|
const originalPathToNodeStr = JSON.stringify(segPathToNode)
|
|
|
|
segPathToNode[1][0] = varDecIndex
|
|
|
|
const pathToNodeStr = JSON.stringify(segPathToNode)
|
|
|
|
// more hacks to hopefully be solved by proper pathToNode info in memory/sketchGroup segments
|
|
|
|
const group =
|
|
|
|
this.activeSegments[pathToNodeStr] ||
|
|
|
|
this.activeSegments[originalPathToNodeStr]
|
|
|
|
// const prevSegment = sketchGroup.slice(index - 1)[0]
|
|
|
|
const type = group?.userData?.type
|
|
|
|
const factor =
|
|
|
|
(sceneInfra.camControls.camera instanceof OrthographicCamera
|
|
|
|
? orthoFactor
|
|
|
|
: perspScale(sceneInfra.camControls.camera, group)) /
|
|
|
|
sceneInfra._baseUnitMultiplier
|
|
|
|
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
|
2024-05-24 20:54:42 +10:00
|
|
|
return this.updateTangentialArcToSegment({
|
2024-04-19 11:56:21 -04:00
|
|
|
prevSegment: sgPaths[index - 1],
|
|
|
|
from: segment.from,
|
|
|
|
to: segment.to,
|
|
|
|
group: group,
|
|
|
|
scale: factor,
|
|
|
|
})
|
|
|
|
} else if (type === STRAIGHT_SEGMENT) {
|
2024-05-24 20:54:42 +10:00
|
|
|
return this.updateStraightSegment({
|
2024-04-19 11:56:21 -04:00
|
|
|
from: segment.from,
|
|
|
|
to: segment.to,
|
|
|
|
group,
|
|
|
|
scale: factor,
|
|
|
|
})
|
|
|
|
} else if (type === PROFILE_START) {
|
|
|
|
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-02-11 12:59:00 +11:00
|
|
|
updateTangentialArcToSegment({
|
|
|
|
prevSegment,
|
|
|
|
from,
|
|
|
|
to,
|
|
|
|
group,
|
|
|
|
scale = 1,
|
|
|
|
}: {
|
|
|
|
prevSegment: SketchGroup['value'][number]
|
|
|
|
from: [number, number]
|
|
|
|
to: [number, number]
|
|
|
|
group: Group
|
|
|
|
scale?: number
|
2024-05-24 20:54:42 +10:00
|
|
|
}): () => SegmentOverlayPayload | null {
|
2024-02-11 12:59:00 +11:00
|
|
|
group.userData.from = from
|
|
|
|
group.userData.to = to
|
|
|
|
group.userData.prevSegment = prevSegment
|
2024-03-02 08:48:30 +11:00
|
|
|
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
2024-04-03 13:22:56 +11:00
|
|
|
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
const previousPoint =
|
|
|
|
prevSegment?.type === 'TangentialArcTo'
|
|
|
|
? getTangentPointFromPreviousArc(
|
|
|
|
prevSegment.center,
|
|
|
|
prevSegment.ccw,
|
|
|
|
prevSegment.to
|
|
|
|
)
|
|
|
|
: prevSegment.from
|
|
|
|
|
|
|
|
const arcInfo = getTangentialArcToInfo({
|
|
|
|
arcStartPoint: from,
|
|
|
|
arcEndPoint: to,
|
|
|
|
tanPreviousPoint: previousPoint,
|
|
|
|
obtuse: true,
|
|
|
|
})
|
|
|
|
|
2024-04-03 13:22:56 +11:00
|
|
|
const pxLength = arcInfo.arcLength / scale
|
2024-04-04 11:07:51 +11:00
|
|
|
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
|
|
|
|
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
|
|
|
|
|
|
|
|
const hoveredParent =
|
|
|
|
sceneInfra.hoveredObject &&
|
|
|
|
getParentGroup(sceneInfra.hoveredObject, [TANGENTIAL_ARC_TO_SEGMENT])
|
|
|
|
let isHandlesVisible = !shouldHideIdle
|
|
|
|
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
|
|
|
|
isHandlesVisible = !shouldHideHover
|
|
|
|
}
|
2024-04-03 13:22:56 +11:00
|
|
|
|
|
|
|
if (arrowGroup) {
|
|
|
|
arrowGroup.position.set(to[0], to[1], 0)
|
|
|
|
|
|
|
|
const arrowheadAngle =
|
|
|
|
arcInfo.endAngle + (Math.PI / 2) * (arcInfo.ccw ? 1 : -1)
|
|
|
|
arrowGroup.quaternion.setFromUnitVectors(
|
|
|
|
new Vector3(0, 1, 0),
|
|
|
|
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
|
|
|
|
)
|
|
|
|
arrowGroup.scale.set(scale, scale, scale)
|
2024-04-04 11:07:51 +11:00
|
|
|
arrowGroup.visible = isHandlesVisible
|
2024-04-03 13:22:56 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
if (extraSegmentGroup) {
|
|
|
|
const circumferenceInPx = (2 * Math.PI * arcInfo.radius) / scale
|
|
|
|
const extraSegmentAngleDelta =
|
|
|
|
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
|
|
|
|
const extraSegmentAngle =
|
|
|
|
arcInfo.startAngle + (arcInfo.ccw ? 1 : -1) * extraSegmentAngleDelta
|
|
|
|
const extraSegmentOffset = new Vector2(
|
|
|
|
Math.cos(extraSegmentAngle) * arcInfo.radius,
|
|
|
|
Math.sin(extraSegmentAngle) * arcInfo.radius
|
|
|
|
)
|
|
|
|
extraSegmentGroup.position.set(
|
|
|
|
arcInfo.center[0] + extraSegmentOffset.x,
|
|
|
|
arcInfo.center[1] + extraSegmentOffset.y,
|
|
|
|
0
|
|
|
|
)
|
|
|
|
extraSegmentGroup.scale.set(scale, scale, scale)
|
2024-04-04 11:07:51 +11:00
|
|
|
extraSegmentGroup.visible = isHandlesVisible
|
2024-04-03 13:22:56 +11:00
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
const tangentialArcToSegmentBody = group.children.find(
|
|
|
|
(child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY
|
|
|
|
) as Mesh
|
|
|
|
|
|
|
|
if (tangentialArcToSegmentBody) {
|
|
|
|
const newGeo = createArcGeometry({ ...arcInfo, scale })
|
|
|
|
tangentialArcToSegmentBody.geometry = newGeo
|
|
|
|
}
|
|
|
|
const tangentialArcToSegmentBodyDashed = group.children.find(
|
|
|
|
(child) => child.userData.type === TANGENTIAL_ARC_TO__SEGMENT_DASH
|
|
|
|
) as Mesh
|
|
|
|
if (tangentialArcToSegmentBodyDashed) {
|
|
|
|
// consider throttling the whole updateTangentialArcToSegment
|
|
|
|
// if there are more perf considerations going forward
|
|
|
|
this.throttledUpdateDashedArcGeo({
|
|
|
|
...arcInfo,
|
|
|
|
mesh: tangentialArcToSegmentBodyDashed,
|
|
|
|
isDashed: true,
|
|
|
|
scale,
|
|
|
|
})
|
|
|
|
}
|
2024-05-24 20:54:42 +10:00
|
|
|
const angle = normaliseAngle(
|
|
|
|
(arcInfo.endAngle * 180) / Math.PI + (arcInfo.ccw ? 90 : -90)
|
|
|
|
)
|
|
|
|
return () =>
|
|
|
|
sceneInfra.updateOverlayDetails({
|
|
|
|
arrowGroup,
|
|
|
|
group,
|
|
|
|
isHandlesVisible,
|
|
|
|
from,
|
|
|
|
to,
|
|
|
|
angle,
|
|
|
|
})
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
|
|
|
throttledUpdateDashedArcGeo = throttle(
|
|
|
|
(
|
|
|
|
args: Parameters<typeof createArcGeometry>[0] & {
|
|
|
|
mesh: Mesh
|
|
|
|
scale: number
|
|
|
|
}
|
|
|
|
) => (args.mesh.geometry = createArcGeometry(args)),
|
|
|
|
1000 / 30
|
|
|
|
)
|
|
|
|
updateStraightSegment({
|
|
|
|
from,
|
|
|
|
to,
|
|
|
|
group,
|
|
|
|
scale = 1,
|
|
|
|
}: {
|
|
|
|
from: [number, number]
|
|
|
|
to: [number, number]
|
|
|
|
group: Group
|
|
|
|
scale?: number
|
2024-05-24 20:54:42 +10:00
|
|
|
}): () => SegmentOverlayPayload | null {
|
2024-02-11 12:59:00 +11:00
|
|
|
group.userData.from = from
|
|
|
|
group.userData.to = to
|
|
|
|
const shape = new Shape()
|
2024-04-03 13:22:56 +11:00
|
|
|
shape.moveTo(0, (-SEGMENT_WIDTH_PX / 2) * scale) // The width of the line in px (2.4px in this case)
|
|
|
|
shape.lineTo(0, (SEGMENT_WIDTH_PX / 2) * scale)
|
2024-03-02 08:48:30 +11:00
|
|
|
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
2024-07-08 16:41:00 -04:00
|
|
|
const labelGroup = group.getObjectByName(SEGMENT_LENGTH_LABEL) as Group
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-04-03 13:22:56 +11:00
|
|
|
const length = Math.sqrt(
|
|
|
|
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
|
|
|
|
)
|
|
|
|
|
|
|
|
const pxLength = length / scale
|
2024-04-04 11:07:51 +11:00
|
|
|
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
|
|
|
|
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
|
|
|
|
|
|
|
|
const hoveredParent =
|
|
|
|
sceneInfra.hoveredObject &&
|
|
|
|
getParentGroup(sceneInfra.hoveredObject, [STRAIGHT_SEGMENT])
|
|
|
|
let isHandlesVisible = !shouldHideIdle
|
|
|
|
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
|
|
|
|
isHandlesVisible = !shouldHideHover
|
|
|
|
}
|
2024-04-03 13:22:56 +11:00
|
|
|
|
2024-03-04 14:18:08 +11:00
|
|
|
if (arrowGroup) {
|
|
|
|
arrowGroup.position.set(to[0], to[1], 0)
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-03-04 14:18:08 +11:00
|
|
|
const dir = new Vector3()
|
|
|
|
.subVectors(
|
|
|
|
new Vector3(to[0], to[1], 0),
|
|
|
|
new Vector3(from[0], from[1], 0)
|
|
|
|
)
|
|
|
|
.normalize()
|
|
|
|
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
|
|
|
arrowGroup.scale.set(scale, scale, scale)
|
2024-04-04 11:07:51 +11:00
|
|
|
arrowGroup.visible = isHandlesVisible
|
2024-04-03 13:22:56 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
|
|
|
if (extraSegmentGroup) {
|
|
|
|
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
|
|
|
|
.normalize()
|
|
|
|
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
|
|
|
|
extraSegmentGroup.position.set(
|
|
|
|
from[0] + offsetFromBase.x,
|
|
|
|
from[1] + offsetFromBase.y,
|
|
|
|
0
|
|
|
|
)
|
|
|
|
extraSegmentGroup.scale.set(scale, scale, scale)
|
2024-04-04 11:07:51 +11:00
|
|
|
extraSegmentGroup.visible = isHandlesVisible
|
2024-03-04 14:18:08 +11:00
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-07-08 16:41:00 -04:00
|
|
|
if (labelGroup) {
|
|
|
|
const labelWrapper = labelGroup.getObjectByName(
|
|
|
|
SEGMENT_LENGTH_LABEL_TEXT
|
|
|
|
) as CSS2DObject
|
|
|
|
const labelWrapperElem = labelWrapper.element as HTMLDivElement
|
|
|
|
const label = labelWrapperElem.children[0] as HTMLParagraphElement
|
2024-09-10 16:22:16 -05:00
|
|
|
label.innerText = `${roundOff(length)}`
|
2024-07-08 16:41:00 -04:00
|
|
|
label.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
|
2024-09-10 16:22:16 -05:00
|
|
|
const slope = (to[1] - from[1]) / (to[0] - from[0])
|
|
|
|
let slopeAngle = ((Math.atan(slope) * 180) / Math.PI) * -1
|
|
|
|
label.style.setProperty('--degree', `${slopeAngle}deg`)
|
|
|
|
label.style.setProperty('--x', `0px`)
|
|
|
|
label.style.setProperty('--y', `0px`)
|
|
|
|
labelWrapper.position.set((from[0] + to[0]) / 2, (from[1] + to[1]) / 2, 0)
|
2024-07-08 16:41:00 -04:00
|
|
|
labelGroup.visible = isHandlesVisible
|
|
|
|
}
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
const straightSegmentBody = group.children.find(
|
|
|
|
(child) => child.userData.type === STRAIGHT_SEGMENT_BODY
|
|
|
|
) as Mesh
|
|
|
|
if (straightSegmentBody) {
|
|
|
|
const line = new LineCurve3(
|
|
|
|
new Vector3(from[0], from[1], 0),
|
|
|
|
new Vector3(to[0], to[1], 0)
|
|
|
|
)
|
|
|
|
straightSegmentBody.geometry = new ExtrudeGeometry(shape, {
|
|
|
|
steps: 2,
|
|
|
|
bevelEnabled: false,
|
|
|
|
extrudePath: line,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
const straightSegmentBodyDashed = group.children.find(
|
|
|
|
(child) => child.userData.type === STRAIGHT_SEGMENT_DASH
|
|
|
|
) as Mesh
|
|
|
|
if (straightSegmentBodyDashed) {
|
|
|
|
straightSegmentBodyDashed.geometry = dashedStraight(
|
|
|
|
from,
|
|
|
|
to,
|
|
|
|
shape,
|
|
|
|
scale
|
|
|
|
)
|
|
|
|
}
|
2024-05-24 20:54:42 +10:00
|
|
|
return () =>
|
|
|
|
sceneInfra.updateOverlayDetails({
|
|
|
|
arrowGroup,
|
|
|
|
group,
|
|
|
|
isHandlesVisible,
|
|
|
|
from,
|
|
|
|
to,
|
|
|
|
})
|
2024-02-11 12:59:00 +11: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)
|
|
|
|
}
|
|
|
|
private _tearDownSketch(
|
|
|
|
callDepth = 0,
|
|
|
|
resolve: (val: unknown) => void,
|
|
|
|
reject: () => void,
|
|
|
|
{ removeAxis = true }: { removeAxis?: boolean }
|
|
|
|
) {
|
|
|
|
if (this.intersectionPlane) this.scene.remove(this.intersectionPlane)
|
|
|
|
if (this.axisGroup && removeAxis) this.scene.remove(this.axisGroup)
|
|
|
|
const sketchSegments = this.scene.children.find(
|
|
|
|
({ userData }) => userData?.type === SKETCH_GROUP_SEGMENTS
|
|
|
|
)
|
|
|
|
let shouldResolve = false
|
|
|
|
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)
|
|
|
|
shouldResolve = true
|
|
|
|
} else {
|
|
|
|
const delay = 100
|
|
|
|
const maxTimeRetries = 3000 // 3 seconds
|
|
|
|
const maxCalls = maxTimeRetries / delay
|
|
|
|
if (callDepth < maxCalls) {
|
|
|
|
setTimeout(() => {
|
|
|
|
this._tearDownSketch(callDepth + 1, resolve, reject, { removeAxis })
|
|
|
|
}, delay)
|
|
|
|
} else {
|
|
|
|
reject()
|
|
|
|
}
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
sceneInfra.camControls.enableRotate = true
|
2024-02-11 12:59:00 +11:00
|
|
|
this.activeSegments = {}
|
|
|
|
// maybe should reset onMove etc handlers
|
|
|
|
if (shouldResolve) resolve(true)
|
|
|
|
}
|
|
|
|
async tearDownSketch({
|
|
|
|
removeAxis = true,
|
|
|
|
}: {
|
|
|
|
removeAxis?: boolean
|
|
|
|
} = {}) {
|
|
|
|
// I think promisifying this is mostly a side effect of not having
|
|
|
|
// "setupSketch" correctly capture a promise when it's done
|
|
|
|
// so we're effectively waiting for to be finished setting up the scene just to tear it down
|
|
|
|
// TODO is to fix that
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
this._tearDownSketch(0, resolve, reject, { removeAxis })
|
|
|
|
})
|
|
|
|
}
|
2024-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)
|
|
|
|
}
|
|
|
|
const parent = getParentGroup(selected, [
|
|
|
|
STRAIGHT_SEGMENT,
|
|
|
|
TANGENTIAL_ARC_TO_SEGMENT,
|
|
|
|
PROFILE_START,
|
|
|
|
])
|
|
|
|
if (parent?.userData?.pathToNode) {
|
|
|
|
const updatedAst = parse(recast(kclManager.ast))
|
2024-06-24 11:45:40 -04:00
|
|
|
if (trap(updatedAst)) return
|
|
|
|
const _node = getNodeFromPath<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-08-03 18:08:51 +10:00
|
|
|
editorManager.setHighlightRange([[node.start, node.end]])
|
2024-04-04 11:07:51 +11:00
|
|
|
const yellow = 0xffff00
|
|
|
|
colorSegment(selected, yellow)
|
|
|
|
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
|
|
|
if (extraSegmentGroup) {
|
|
|
|
extraSegmentGroup.traverse((child) => {
|
|
|
|
if (child instanceof Points || child instanceof Mesh) {
|
|
|
|
child.material.opacity = dragSelected ? 0 : 1
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
|
|
|
const factor =
|
|
|
|
(sceneInfra.camControls.camera instanceof OrthographicCamera
|
|
|
|
? orthoFactor
|
|
|
|
: perspScale(sceneInfra.camControls.camera, parent)) /
|
|
|
|
sceneInfra._baseUnitMultiplier
|
|
|
|
if (parent.name === STRAIGHT_SEGMENT) {
|
|
|
|
this.updateStraightSegment({
|
|
|
|
from: parent.userData.from,
|
|
|
|
to: parent.userData.to,
|
|
|
|
group: parent,
|
|
|
|
scale: factor,
|
|
|
|
})
|
|
|
|
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
|
|
|
|
this.updateTangentialArcToSegment({
|
|
|
|
prevSegment: parent.userData.prevSegment,
|
|
|
|
from: parent.userData.from,
|
|
|
|
to: parent.userData.to,
|
|
|
|
group: parent,
|
|
|
|
scale: factor,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2024-08-03 18:08:51 +10:00
|
|
|
editorManager.setHighlightRange([[0, 0]])
|
2024-04-04 11:07:51 +11:00
|
|
|
},
|
|
|
|
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
|
2024-08-03 18:08:51 +10:00
|
|
|
editorManager.setHighlightRange([[0, 0]])
|
2024-04-04 11:07:51 +11:00
|
|
|
const parent = getParentGroup(selected, [
|
|
|
|
STRAIGHT_SEGMENT,
|
|
|
|
TANGENTIAL_ARC_TO_SEGMENT,
|
|
|
|
PROFILE_START,
|
|
|
|
])
|
|
|
|
if (parent) {
|
|
|
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
|
|
|
|
|
|
const factor =
|
|
|
|
(sceneInfra.camControls.camera instanceof OrthographicCamera
|
|
|
|
? orthoFactor
|
|
|
|
: perspScale(sceneInfra.camControls.camera, parent)) /
|
|
|
|
sceneInfra._baseUnitMultiplier
|
|
|
|
if (parent.name === STRAIGHT_SEGMENT) {
|
|
|
|
this.updateStraightSegment({
|
|
|
|
from: parent.userData.from,
|
|
|
|
to: parent.userData.to,
|
|
|
|
group: parent,
|
|
|
|
scale: factor,
|
|
|
|
})
|
|
|
|
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
|
|
|
|
this.updateTangentialArcToSegment({
|
|
|
|
prevSegment: parent.userData.prevSegment,
|
|
|
|
from: parent.userData.from,
|
|
|
|
to: parent.userData.to,
|
|
|
|
group: parent,
|
|
|
|
scale: factor,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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(
|
|
|
|
sketchPathToNode: PathToNode,
|
|
|
|
ast: Program,
|
|
|
|
programMemory: ProgramMemory,
|
|
|
|
draftSegment?: DraftSegment
|
2024-06-24 11:45:40 -04:00
|
|
|
):
|
|
|
|
| {
|
|
|
|
truncatedAst: Program
|
|
|
|
programMemoryOverride: ProgramMemory
|
|
|
|
variableDeclarationName: string
|
|
|
|
}
|
|
|
|
| Error {
|
2024-02-11 12:59:00 +11:00
|
|
|
const bodyIndex = Number(sketchPathToNode?.[1]?.[0]) || 0
|
2024-07-25 20:11:46 -04:00
|
|
|
const _ast = structuredClone(ast)
|
2024-02-11 12:59:00 +11:00
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node = getNodeFromPath<VariableDeclaration>(
|
|
|
|
_ast,
|
|
|
|
sketchPathToNode || [],
|
|
|
|
'VariableDeclaration'
|
|
|
|
)
|
|
|
|
if (err(_node)) return _node
|
|
|
|
const variableDeclarationName = _node.node?.declarations?.[0]?.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
|
|
|
const sg = sketchGroupFromKclValue(
|
|
|
|
programMemory.get(variableDeclarationName),
|
|
|
|
variableDeclarationName
|
|
|
|
)
|
|
|
|
if (err(sg)) return sg
|
|
|
|
const lastSeg = sg?.value.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(),
|
|
|
|
])
|
|
|
|
}
|
|
|
|
;(
|
|
|
|
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
|
|
|
|
.init as PipeExpression
|
|
|
|
).body.push(newSegment)
|
|
|
|
// update source ranges to section we just added.
|
|
|
|
// hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketchGroup segments
|
|
|
|
const updatedSrcRangeAst = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
|
2024-06-24 11:45:40 -04:00
|
|
|
if (err(updatedSrcRangeAst)) return updatedSrcRangeAst
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
const lastPipeItem = (
|
|
|
|
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration)
|
|
|
|
.declarations[0].init as PipeExpression
|
|
|
|
).body.slice(-1)[0]
|
|
|
|
|
|
|
|
;(
|
|
|
|
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
|
|
|
|
.init as PipeExpression
|
|
|
|
).body.slice(-1)[0].start = lastPipeItem.start
|
|
|
|
|
|
|
|
_ast.end = lastPipeItem.end
|
|
|
|
const varDec = _ast.body[bodyIndex] as VariableDeclaration
|
|
|
|
varDec.end = lastPipeItem.end
|
|
|
|
const declarator = varDec.declarations[0]
|
|
|
|
declarator.end = lastPipeItem.end
|
|
|
|
const init = declarator.init as PipeExpression
|
|
|
|
init.end = lastPipeItem.end
|
|
|
|
init.body.slice(-1)[0].end = lastPipeItem.end
|
|
|
|
}
|
|
|
|
const truncatedAst: Program = {
|
|
|
|
..._ast,
|
2024-07-25 20:11:46 -04:00
|
|
|
body: [structuredClone(_ast.body[bodyIndex])],
|
2024-02-11 12:59:00 +11:00
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-06-24 22:39:04 -07:00
|
|
|
// Grab all the TagDeclarators and TagIdentifiers from memory.
|
|
|
|
let start = _node.node.start
|
2024-07-22 19:43:40 -04:00
|
|
|
const programMemoryOverride = programMemory.filterVariables(true, (value) => {
|
2024-06-24 22:39:04 -07:00
|
|
|
if (
|
2024-07-22 19:43:40 -04:00
|
|
|
!('__meta' in value) ||
|
2024-06-24 22:39:04 -07:00
|
|
|
value.__meta === undefined ||
|
|
|
|
value.__meta.length === 0 ||
|
|
|
|
value.__meta[0].sourceRange === undefined
|
|
|
|
) {
|
2024-07-22 19:43:40 -04:00
|
|
|
return false
|
2024-06-24 22:39:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (value.__meta[0].sourceRange[0] >= start) {
|
|
|
|
// We only want things before our start point.
|
2024-07-22 19:43:40 -04:00
|
|
|
return false
|
2024-06-24 22:39:04 -07:00
|
|
|
}
|
|
|
|
|
2024-07-22 19:43:40 -04:00
|
|
|
return value.type === 'TagIdentifier'
|
|
|
|
})
|
|
|
|
if (err(programMemoryOverride)) return programMemoryOverride
|
2024-06-24 22:39:04 -07:00
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
for (let i = 0; i < bodyIndex; i++) {
|
|
|
|
const node = _ast.body[i]
|
|
|
|
if (node.type !== 'VariableDeclaration') {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
const name = node.declarations[0].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,
|
|
|
|
stopAt: string[] = [STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT]
|
|
|
|
): Group | null {
|
|
|
|
if (stopAt.includes(object?.userData?.type)) {
|
|
|
|
return object
|
|
|
|
} else if (object?.parent) {
|
|
|
|
return getParentGroup(object.parent, stopAt)
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
export function sketchGroupFromPathToNode({
|
|
|
|
pathToNode,
|
|
|
|
ast,
|
|
|
|
programMemory,
|
|
|
|
}: {
|
|
|
|
pathToNode: PathToNode
|
|
|
|
ast: Program
|
|
|
|
programMemory: ProgramMemory
|
2024-08-07 15:15:22 -04:00
|
|
|
}): SketchGroup | 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-06-21 23:50:30 -07:00
|
|
|
if (result?.type === 'ExtrudeGroup') {
|
|
|
|
return result.sketchGroup
|
|
|
|
}
|
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
|
|
|
const sg = sketchGroupFromKclValue(result, varDec?.id?.name)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
const straightSegmentBody = getParentGroup(object, [
|
|
|
|
STRAIGHT_SEGMENT,
|
|
|
|
TANGENTIAL_ARC_TO_SEGMENT,
|
|
|
|
])
|
|
|
|
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-02-11 12:59:00 +11:00
|
|
|
const sketchGroup = sketchGroupFromPathToNode({
|
|
|
|
pathToNode: sketchPathToNode,
|
|
|
|
ast: kclManager.ast,
|
|
|
|
programMemory: kclManager.programMemory,
|
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
if (err(sketchGroup)) return sketchGroup
|
2024-06-21 19:54:18 -07:00
|
|
|
const zAxis = sketchGroup?.on.zAxis || sketchNormalBackUp
|
2024-08-22 16:08:49 -04:00
|
|
|
if (!zAxis) return Error('SketchGroup 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(
|
|
|
|
sketchPathToNode: PathToNode
|
|
|
|
): Promise<{
|
|
|
|
quat: Quaternion
|
2024-04-22 20:14:06 +10:00
|
|
|
sketchDetails: SketchDetails & { faceId?: string }
|
2024-03-22 10:23:04 +11:00
|
|
|
}> {
|
|
|
|
const sketchGroup = sketchGroupFromPathToNode({
|
|
|
|
pathToNode: sketchPathToNode,
|
|
|
|
ast: kclManager.ast,
|
|
|
|
programMemory: kclManager.programMemory,
|
|
|
|
})
|
2024-06-24 11:45:40 -04:00
|
|
|
if (err(sketchGroup)) return Promise.reject(sketchGroup)
|
2024-08-07 15:15:22 -04:00
|
|
|
if (!sketchGroup) return Promise.reject('sketchGroup not found')
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-03-22 10:23:04 +11:00
|
|
|
if (sketchGroup.on.type === 'plane') {
|
2024-06-21 19:54:18 -07:00
|
|
|
const zAxis = sketchGroup?.on.zAxis
|
2024-03-22 10:23:04 +11:00
|
|
|
return {
|
|
|
|
quat: getQuaternionFromZAxis(massageFormats(zAxis)),
|
|
|
|
sketchDetails: {
|
|
|
|
sketchPathToNode,
|
|
|
|
zAxis: [zAxis.x, zAxis.y, zAxis.z],
|
2024-06-21 19:54:18 -07:00
|
|
|
yAxis: [
|
|
|
|
sketchGroup.on.yAxis.x,
|
|
|
|
sketchGroup.on.yAxis.y,
|
|
|
|
sketchGroup.on.yAxis.z,
|
|
|
|
],
|
2024-03-22 10:23:04 +11:00
|
|
|
origin: [0, 0, 0],
|
2024-04-22 20:14:06 +10:00
|
|
|
faceId: sketchGroup.on.id,
|
2024-03-22 10:23:04 +11:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-03-22 10:23:04 +11:00
|
|
|
if (sketchGroup.on.type === 'face') {
|
2024-06-21 19:54:18 -07:00
|
|
|
const faceInfo = await getFaceDetails(sketchGroup.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: {
|
|
|
|
sketchPathToNode,
|
|
|
|
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-06-21 19:54:18 -07:00
|
|
|
faceId: sketchGroup.on.id,
|
2024-03-22 10:23:04 +11:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
return Promise.reject(
|
2024-03-22 10:23:04 +11:00
|
|
|
'sketchGroup.on.type not recognized, has a new type been added?'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|