Refactor mouse event args (#1613)

* refactor mouse event interfaces

Importantly returning multiple intersections from raycastRing, but other clean up

* refactor enter exit args interface

* type tweak
This commit is contained in:
Kurt Hutten
2024-03-03 16:23:16 +11:00
committed by GitHub
parent fedffbb384
commit 223b5952aa
3 changed files with 169 additions and 151 deletions

View File

@ -364,17 +364,18 @@ class SceneEntities {
this.scene.add(group) this.scene.add(group)
if (!draftSegment) { if (!draftSegment) {
sceneInfra.setCallbacks({ sceneInfra.setCallbacks({
onDrag: (args) => { onDrag: ({ selected, intersectionPoint, mouseEvent }) => {
if (args.event.which !== 1) return if (mouseEvent.which !== 1) return
this.onDragSegment({ this.onDragSegment({
...args, object: selected,
intersection2d: intersectionPoint.twoD,
sketchPathToNode, sketchPathToNode,
}) })
}, },
onMove: () => {}, onMove: () => {},
onClick: (args) => { onClick: (args) => {
if (args?.event.which !== 1) return if (args?.mouseEvent.which !== 1) return
if (!args || !args.object) { if (!args || !args.selected) {
sceneInfra.modelingSend({ sceneInfra.modelingSend({
type: 'Set selection', type: 'Set selection',
data: { data: {
@ -383,22 +384,22 @@ class SceneEntities {
}) })
return return
} }
const { object } = args const { selected } = args
const event = getEventForSegmentSelection(object) const event = getEventForSegmentSelection(selected)
if (!event) return if (!event) return
sceneInfra.modelingSend(event) sceneInfra.modelingSend(event)
}, },
onMouseEnter: ({ object }) => { onMouseEnter: ({ selected }) => {
// TODO change the color of the segment to yellow? // TODO change the color of the segment to yellow?
// Give a few pixels grace around each of the segments // Give a few pixels grace around each of the segments
// for hover. // for hover.
if ([X_AXIS, Y_AXIS].includes(object?.userData?.type)) { if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = object as Mesh const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor) mat.color.set(obj.userData.baseColor)
mat.color.offsetHSL(0, 0, 0.5) mat.color.offsetHSL(0, 0, 0.5)
} }
const parent = getParentGroup(object, [ const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT, STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START, PROFILE_START,
@ -412,22 +413,22 @@ class SceneEntities {
).node ).node
sceneInfra.highlightCallback([node.start, node.end]) sceneInfra.highlightCallback([node.start, node.end])
const yellow = 0xffff00 const yellow = 0xffff00
colorSegment(object, yellow) colorSegment(selected, yellow)
return return
} }
sceneInfra.highlightCallback([0, 0]) sceneInfra.highlightCallback([0, 0])
}, },
onMouseLeave: ({ object }) => { onMouseLeave: ({ selected }) => {
sceneInfra.highlightCallback([0, 0]) sceneInfra.highlightCallback([0, 0])
const parent = getParentGroup(object, [ const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT, STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START, PROFILE_START,
]) ])
const isSelected = parent?.userData?.isSelected const isSelected = parent?.userData?.isSelected
colorSegment(object, isSelected ? 0x0000ff : 0xffffff) colorSegment(selected, isSelected ? 0x0000ff : 0xffffff)
if ([X_AXIS, Y_AXIS].includes(object?.userData?.type)) { if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = object as Mesh const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor) mat.color.set(obj.userData.baseColor)
if (obj.userData.isSelected) mat.color.offsetHSL(0, 0, 0.2) if (obj.userData.isSelected) mat.color.offsetHSL(0, 0, 0.2)
@ -439,14 +440,14 @@ class SceneEntities {
onDrag: () => {}, onDrag: () => {},
onClick: async (args) => { onClick: async (args) => {
if (!args) return if (!args) return
if (args.event.which !== 1) return if (args.mouseEvent.which !== 1) return
const { intersection2d } = args const { intersectionPoint } = args
if (!intersection2d) return if (!intersectionPoint?.twoD) return
const firstSeg = sketchGroup.value[0] const firstSeg = sketchGroup.value[0]
const isClosingSketch = compareVec2Epsilon2( const isClosingSketch = compareVec2Epsilon2(
firstSeg.from, firstSeg.from,
[intersection2d.x, intersection2d.y], [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
0.5 0.5
) )
let modifiedAst let modifiedAst
@ -462,7 +463,7 @@ class SceneEntities {
modifiedAst = addNewSketchLn({ modifiedAst = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
to: [intersection2d.x, intersection2d.y], to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
from: [lastSegment.to[0], lastSegment.to[1]], from: [lastSegment.to[0], lastSegment.to[1]],
fnName: fnName:
lastSegment.type === 'TangentialArcTo' lastSegment.type === 'TangentialArcTo'
@ -478,7 +479,7 @@ class SceneEntities {
}, },
onMove: (args) => { onMove: (args) => {
this.onDragSegment({ this.onDragSegment({
...args, intersection2d: args.intersectionPoint.twoD,
object: Object.values(this.activeSegments).slice(-1)[0], object: Object.values(this.activeSegments).slice(-1)[0],
sketchPathToNode, sketchPathToNode,
draftInfo: { draftInfo: {
@ -525,15 +526,11 @@ class SceneEntities {
) )
onDragSegment({ onDragSegment({
object, object,
event,
intersectPoint,
intersection2d, intersection2d,
sketchPathToNode, sketchPathToNode,
draftInfo, draftInfo,
}: { }: {
object: any object: any
event: any
intersectPoint: Vector3
intersection2d: Vector2 intersection2d: Vector2
sketchPathToNode: PathToNode sketchPathToNode: PathToNode
draftInfo?: { draftInfo?: {
@ -851,22 +848,24 @@ class SceneEntities {
} }
setupDefaultPlaneHover() { setupDefaultPlaneHover() {
sceneInfra.setCallbacks({ sceneInfra.setCallbacks({
onMouseEnter: ({ object }) => { onMouseEnter: ({ selected }) => {
if (object.parent.userData.type !== DEFAULT_PLANES) return if (!(selected instanceof Mesh && selected.parent)) return
const type: DefaultPlane = object.userData.type if (selected.parent.userData.type !== DEFAULT_PLANES) return
object.material.color = defaultPlaneColor(type, 0.5, 1) const type: DefaultPlane = selected.userData.type
selected.material.color = defaultPlaneColor(type, 0.5, 1)
}, },
onMouseLeave: ({ object }) => { onMouseLeave: ({ selected }) => {
if (object.parent.userData.type !== DEFAULT_PLANES) return if (!(selected instanceof Mesh && selected.parent)) return
const type: DefaultPlane = object.userData.type if (selected.parent.userData.type !== DEFAULT_PLANES) return
object.material.color = defaultPlaneColor(type) const type: DefaultPlane = selected.userData.type
selected.material.color = defaultPlaneColor(type)
}, },
onClick: (args) => { onClick: (args) => {
if (!args || !args.object) return if (!args || !args.intersects?.[0]) return
if (args.event.which !== 1) return if (args.mouseEvent.which !== 1) return
const { intersection } = args const { intersects } = args
const type = intersection.object.name || '' const type = intersects?.[0].object.name || ''
const posNorm = Number(intersection.normal?.z) > 0 const posNorm = Number(intersects?.[0]?.normal?.z) > 0
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY' let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1] let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
if (type === YZ_PLANE) { if (type === YZ_PLANE) {

View File

@ -48,31 +48,36 @@ export const AXIS_GROUP = 'axisGroup'
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments' export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
export const ARROWHEAD = 'arrowhead' export const ARROWHEAD = 'arrowhead'
interface BaseCallbackArgs2 { interface OnMouseEnterLeaveArgs {
object: any selected: Object3D<Object3DEventMap>
event: any mouseEvent: MouseEvent
}
interface BaseCallbackArgs {
event: any
}
interface OnDragCallbackArgs extends BaseCallbackArgs {
object: any
intersection2d: Vector2
intersectPoint: Vector3
intersection: Intersection<Object3D<Object3DEventMap>>
}
interface OnClickCallbackArgs extends BaseCallbackArgs {
intersection2d?: Vector2
intersectPoint: Vector3
intersection: Intersection<Object3D<Object3DEventMap>>
object?: any
} }
interface onMoveCallbackArgs { interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
event: any intersectionPoint: {
intersection2d: Vector2 twoD: Vector2
intersectPoint: Vector3 threeD: Vector3
intersection: Intersection<Object3D<Object3DEventMap>> }
intersects: Intersection<Object3D<Object3DEventMap>>[]
}
interface OnClickCallbackArgs {
mouseEvent: MouseEvent
intersectionPoint?: {
twoD: Vector2
threeD: Vector3
}
intersects: Intersection<Object3D<Object3DEventMap>>[]
selected?: Object3D<Object3DEventMap>
}
interface OnMoveCallbackArgs {
mouseEvent: MouseEvent
intersectionPoint: {
twoD: Vector2
threeD: Vector3
}
intersects: Intersection<Object3D<Object3DEventMap>>[]
selected?: Object3D<Object3DEventMap>
} }
// This singleton class is responsible for all of the under the hood setup for the client side scene. // This singleton class is responsible for all of the under the hood setup for the client side scene.
@ -90,16 +95,16 @@ class SceneInfra {
_baseUnit: BaseUnit = 'mm' _baseUnit: BaseUnit = 'mm'
_baseUnitMultiplier = 1 _baseUnitMultiplier = 1
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {} onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
onMoveCallback: (arg: onMoveCallbackArgs) => void = () => {} onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {} onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
onMouseEnter: (arg: BaseCallbackArgs2) => void = () => {} onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
onMouseLeave: (arg: BaseCallbackArgs2) => void = () => {} onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
setCallbacks = (callbacks: { setCallbacks = (callbacks: {
onDrag?: (arg: OnDragCallbackArgs) => void onDrag?: (arg: OnDragCallbackArgs) => void
onMove?: (arg: onMoveCallbackArgs) => void onMove?: (arg: OnMoveCallbackArgs) => void
onClick?: (arg?: OnClickCallbackArgs) => void onClick?: (arg?: OnClickCallbackArgs) => void
onMouseEnter?: (arg: BaseCallbackArgs2) => void onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
onMouseLeave?: (arg: BaseCallbackArgs2) => void onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
}) => { }) => {
this.onDragCallback = callbacks.onDrag || this.onDragCallback this.onDragCallback = callbacks.onDrag || this.onDragCallback
this.onMoveCallback = callbacks.onMove || this.onMoveCallback this.onMoveCallback = callbacks.onMove || this.onMoveCallback
@ -142,10 +147,9 @@ class SceneInfra {
currentMouseVector = new Vector2() currentMouseVector = new Vector2()
selected: { selected: {
mouseDownVector: Vector2 mouseDownVector: Vector2
object: any object: Object3D<Object3DEventMap>
hasBeenDragged: boolean hasBeenDragged: boolean
} | null = null } | null = null
selectedObject: null | any = null
mouseDownVector: null | Vector2 = null mouseDownVector: null | Vector2 = null
constructor() { constructor() {
@ -242,8 +246,8 @@ class SceneInfra {
// Dispose of any other resources like geometries, materials, textures // Dispose of any other resources like geometries, materials, textures
} }
getPlaneIntersectPoint = (): { getPlaneIntersectPoint = (): {
intersection2d?: Vector2 twoD?: Vector2
intersectPoint: Vector3 threeD?: Vector3
intersection: Intersection<Object3D<Object3DEventMap>> intersection: Intersection<Object3D<Object3DEventMap>>
} | null => { } | null => {
this.planeRaycaster.setFromCamera( this.planeRaycaster.setFromCamera(
@ -254,23 +258,11 @@ class SceneInfra {
this.scene.children, this.scene.children,
true true
) )
if ( const recastablePlaneIntersect = planeIntersects.find(
planeIntersects.length > 0 && (intersect) => intersect.object.name === RAYCASTABLE_PLANE
planeIntersects[0].object.userData.type !== RAYCASTABLE_PLANE
) {
const intersect = planeIntersects[0]
return {
intersectPoint: intersect.point,
intersection: intersect,
}
}
if (
!(
planeIntersects.length > 0 &&
planeIntersects[0].object.userData.type === RAYCASTABLE_PLANE
)
) )
return null if (!planeIntersects.length) return null
if (!recastablePlaneIntersect) return { intersection: planeIntersects[0] }
const planePosition = planeIntersects[0].object.position const planePosition = planeIntersects[0].object.position
const inversePlaneQuaternion = planeIntersects[0].object.quaternion const inversePlaneQuaternion = planeIntersects[0].object.quaternion
.clone() .clone()
@ -285,19 +277,21 @@ class SceneInfra {
} }
return { return {
intersection2d: new Vector2( twoD: new Vector2(
transformedPoint.x / this._baseUnitMultiplier, transformedPoint.x / this._baseUnitMultiplier,
transformedPoint.y / this._baseUnitMultiplier transformedPoint.y / this._baseUnitMultiplier
), // z should be 0 ), // z should be 0
intersectPoint: intersectPoint.divideScalar(this._baseUnitMultiplier), threeD: intersectPoint.divideScalar(this._baseUnitMultiplier),
intersection: planeIntersects[0], intersection: planeIntersects[0],
} }
} }
onMouseMove = (event: MouseEvent) => { onMouseMove = (mouseEvent: MouseEvent) => {
this.currentMouseVector.x = (event.clientX / window.innerWidth) * 2 - 1 this.currentMouseVector.x = (mouseEvent.clientX / window.innerWidth) * 2 - 1
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1 this.currentMouseVector.y =
-(mouseEvent.clientY / window.innerHeight) * 2 + 1
const planeIntersectPoint = this.getPlaneIntersectPoint() const planeIntersectPoint = this.getPlaneIntersectPoint()
const intersects = this.raycastRing()
if (this.selected) { if (this.selected) {
const hasBeenDragged = !compareVec2Epsilon2( const hasBeenDragged = !compareVec2Epsilon2(
@ -313,47 +307,56 @@ class SceneInfra {
if ( if (
hasBeenDragged && hasBeenDragged &&
planeIntersectPoint && planeIntersectPoint &&
planeIntersectPoint.intersection2d planeIntersectPoint.twoD &&
planeIntersectPoint.threeD
) { ) {
// // console.log('onDrag', this.selected) // // console.log('onDrag', this.selected)
this.onDragCallback({ this.onDragCallback({
object: this.selected.object, mouseEvent,
event, intersectionPoint: {
intersection2d: planeIntersectPoint.intersection2d, twoD: planeIntersectPoint.twoD,
...planeIntersectPoint, threeD: planeIntersectPoint.threeD,
},
intersects,
selected: this.selected.object,
}) })
} }
} else if (planeIntersectPoint && planeIntersectPoint.intersection2d) { } else if (
planeIntersectPoint &&
planeIntersectPoint.twoD &&
planeIntersectPoint.threeD
) {
this.onMoveCallback({ this.onMoveCallback({
event, mouseEvent,
intersection2d: planeIntersectPoint.intersection2d, intersectionPoint: {
...planeIntersectPoint, twoD: planeIntersectPoint.twoD,
threeD: planeIntersectPoint.threeD,
},
intersects,
}) })
} }
const intersect = this.raycastRing() if (intersects[0]) {
const firstIntersectObject = intersects[0].object
if (intersect) {
const firstIntersectObject = intersect.object
if (this.hoveredObject !== firstIntersectObject) { if (this.hoveredObject !== firstIntersectObject) {
if (this.hoveredObject) { if (this.hoveredObject) {
this.onMouseLeave({ this.onMouseLeave({
object: this.hoveredObject, selected: this.hoveredObject,
event, mouseEvent: mouseEvent,
}) })
} }
this.hoveredObject = firstIntersectObject this.hoveredObject = firstIntersectObject
this.onMouseEnter({ this.onMouseEnter({
object: this.hoveredObject, selected: this.hoveredObject,
event, mouseEvent: mouseEvent,
}) })
} }
} else { } else {
if (this.hoveredObject) { if (this.hoveredObject) {
this.onMouseLeave({ this.onMouseLeave({
object: this.hoveredObject, selected: this.hoveredObject,
event, mouseEvent: mouseEvent,
}) })
this.hoveredObject = null this.hoveredObject = null
} }
@ -363,41 +366,38 @@ class SceneInfra {
raycastRing = ( raycastRing = (
pixelRadius = 8, pixelRadius = 8,
rayRingCount = 32 rayRingCount = 32
): Intersection<Object3D<Object3DEventMap>> | undefined => { ): Intersection<Object3D<Object3DEventMap>>[] => {
const mouseDownVector = this.currentMouseVector.clone() const mouseDownVector = this.currentMouseVector.clone()
let closestIntersection: const intersectionsMap = new Map<
| Intersection<Object3D<Object3DEventMap>> Object3D,
| undefined = undefined Intersection<Object3D<Object3DEventMap>>
let closestDistance = Infinity >()
const updateClosestIntersection = ( const updateIntersectionsMap = (
intersections: Intersection<Object3D<Object3DEventMap>>[] intersections: Intersection<Object3D<Object3DEventMap>>[]
) => { ) => {
let intersection = null intersections.forEach((intersection) => {
for (let i = 0; i < intersections.length; i++) { if (intersection.object.type !== 'GridHelper') {
if (intersections[i].object.type !== 'GridHelper') { const existingIntersection = intersectionsMap.get(intersection.object)
intersection = intersections[i] if (
break !existingIntersection ||
existingIntersection.distance > intersection.distance
) {
intersectionsMap.set(intersection.object, intersection)
}
} }
} })
if (!intersection) return
if (intersection.distance < closestDistance) {
closestDistance = intersection.distance
closestIntersection = intersection
}
} }
// Check the center point // Check the center point
this.raycaster.setFromCamera(mouseDownVector, this.camControls.camera) this.raycaster.setFromCamera(mouseDownVector, this.camControls.camera)
updateClosestIntersection( updateIntersectionsMap(
this.raycaster.intersectObjects(this.scene.children, true) this.raycaster.intersectObjects(this.scene.children, true)
) )
// Check the ring points // Check the ring points
for (let i = 0; i < rayRingCount; i++) { for (let i = 0; i < rayRingCount; i++) {
const angle = (i / rayRingCount) * Math.PI * 2 const angle = (i / rayRingCount) * Math.PI * 2
const offsetX = ((pixelRadius * Math.cos(angle)) / window.innerWidth) * 2 const offsetX = ((pixelRadius * Math.cos(angle)) / window.innerWidth) * 2
const offsetY = ((pixelRadius * Math.sin(angle)) / window.innerHeight) * 2 const offsetY = ((pixelRadius * Math.sin(angle)) / window.innerHeight) * 2
const ringVector = new Vector2( const ringVector = new Vector2(
@ -405,11 +405,15 @@ class SceneInfra {
mouseDownVector.y - offsetY mouseDownVector.y - offsetY
) )
this.raycaster.setFromCamera(ringVector, this.camControls.camera) this.raycaster.setFromCamera(ringVector, this.camControls.camera)
updateClosestIntersection( updateIntersectionsMap(
this.raycaster.intersectObjects(this.scene.children, true) this.raycaster.intersectObjects(this.scene.children, true)
) )
} }
return closestIntersection
// Convert the map values to an array and sort by distance
return Array.from(intersectionsMap.values()).sort(
(a, b) => a.distance - b.distance
)
} }
onMouseDown = (event: MouseEvent) => { onMouseDown = (event: MouseEvent) => {
@ -417,45 +421,60 @@ class SceneInfra {
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1 this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1
const mouseDownVector = this.currentMouseVector.clone() const mouseDownVector = this.currentMouseVector.clone()
const intersect = this.raycastRing() const intersect = this.raycastRing()[0]
if (intersect) { if (intersect) {
const intersectParent = intersect?.object?.parent as Group const intersectParent = intersect?.object?.parent as Group
this.selected = intersectParent.isGroup this.selected = intersectParent.isGroup
? { ? {
mouseDownVector, mouseDownVector,
object: intersect?.object, object: intersect.object,
hasBeenDragged: false, hasBeenDragged: false,
} }
: null : null
} }
} }
onMouseUp = (event: MouseEvent) => { onMouseUp = (mouseEvent: MouseEvent) => {
this.currentMouseVector.x = (event.clientX / window.innerWidth) * 2 - 1 this.currentMouseVector.x = (mouseEvent.clientX / window.innerWidth) * 2 - 1
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1 this.currentMouseVector.y =
-(mouseEvent.clientY / window.innerHeight) * 2 + 1
const planeIntersectPoint = this.getPlaneIntersectPoint() const planeIntersectPoint = this.getPlaneIntersectPoint()
const intersects = this.raycastRing()
if (this.selected) { if (this.selected) {
if (this.selected.hasBeenDragged) { if (this.selected.hasBeenDragged) {
// this is where we could fire a onDragEnd event // this is where we could fire a onDragEnd event
// console.log('onDragEnd', this.selected) // console.log('onDragEnd', this.selected)
} else if (planeIntersectPoint) { } else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
// fire onClick event as there was no drags // fire onClick event as there was no drags
this.onClickCallback({ this.onClickCallback({
object: this.selected?.object, mouseEvent,
event, intersectionPoint: {
...planeIntersectPoint, twoD: planeIntersectPoint.twoD,
threeD: planeIntersectPoint.threeD,
},
intersects,
selected: this.selected.object,
})
} else if (planeIntersectPoint) {
this.onClickCallback({
mouseEvent,
intersects,
}) })
} else { } else {
this.onClickCallback() this.onClickCallback()
} }
// Clear the selected state whether it was dragged or not // Clear the selected state whether it was dragged or not
this.selected = null this.selected = null
} else if (planeIntersectPoint) { } else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
this.onClickCallback({ this.onClickCallback({
event, mouseEvent,
...planeIntersectPoint, intersectionPoint: {
twoD: planeIntersectPoint.twoD,
threeD: planeIntersectPoint.threeD,
},
intersects,
}) })
} else { } else {
this.onClickCallback() this.onClickCallback()

View File

@ -829,13 +829,13 @@ export const modelingMachine = createMachine(
sceneInfra.setCallbacks({ sceneInfra.setCallbacks({
onClick: async (args) => { onClick: async (args) => {
if (!args) return if (!args) return
if (args.event.which !== 1) return if (args.mouseEvent.which !== 1) return
const { intersection2d } = args const { intersectionPoint } = args
if (!intersection2d || !sketchPathToNode) return if (!intersectionPoint?.twoD || !sketchPathToNode) return
const { modifiedAst } = addStartProfileAt( const { modifiedAst } = addStartProfileAt(
kclManager.ast, kclManager.ast,
sketchPathToNode, sketchPathToNode,
[intersection2d.x, intersection2d.y] [intersectionPoint.twoD.x, intersectionPoint.twoD.y]
) )
await kclManager.updateAst(modifiedAst, false) await kclManager.updateAst(modifiedAst, false)
sceneEntitiesManager.removeIntersectionPlane() sceneEntitiesManager.removeIntersectionPlane()