Compare commits
	
		
			4 Commits
		
	
	
		
			nightly-v2
			...
			callbacks-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cf068f325e | |||
| d60d2292f3 | |||
| 7d564cc2ac | |||
| 05a84213dd | 
| @ -19,7 +19,10 @@ import { | ||||
|   PROFILE_START, | ||||
|   getParentGroup, | ||||
| } from './sceneEntities' | ||||
| import { SegmentOverlay, SketchDetails } from 'machines/modelingMachine' | ||||
| import { | ||||
|   SegmentOverlay, | ||||
|   ModelingMachineContext as ModelingContextType, | ||||
| } from 'machines/modelingMachine' | ||||
| import { findUsesOfTagInPipe, getNodeFromPath } from 'lang/queryAst' | ||||
| import { | ||||
|   CallExpression, | ||||
| @ -353,11 +356,12 @@ export const confirmModal = create<ConfirmModalProps, boolean, boolean>( | ||||
|  | ||||
| export async function deleteSegment({ | ||||
|   pathToNode, | ||||
|   sketchDetails, | ||||
|   context, | ||||
| }: { | ||||
|   pathToNode: PathToNode | ||||
|   sketchDetails: SketchDetails | null | ||||
|   context: ModelingContextType | ||||
| }) { | ||||
|   const { sketchDetails } = context | ||||
|   let modifiedAst: Program | Error = kclManager.ast | ||||
|   const dependentRanges = findUsesOfTagInPipe(modifiedAst, pathToNode) | ||||
|  | ||||
| @ -394,13 +398,7 @@ export async function deleteSegment({ | ||||
|   } | ||||
|  | ||||
|   if (!sketchDetails) return | ||||
|   await sceneEntitiesManager.updateAstAndRejigSketch( | ||||
|     pathToNode, | ||||
|     modifiedAst, | ||||
|     sketchDetails.zAxis, | ||||
|     sketchDetails.yAxis, | ||||
|     sketchDetails.origin | ||||
|   ) | ||||
|   await sceneEntitiesManager.updateAstAndRejigSketch(modifiedAst, context) | ||||
|  | ||||
|   // Now 'Set sketchDetails' is called with the modified pathToNode | ||||
| } | ||||
|  | ||||
							
								
								
									
										362
									
								
								src/clientSideScene/sceneCallbacks.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										362
									
								
								src/clientSideScene/sceneCallbacks.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,362 @@ | ||||
| import { getEventForSegmentSelection } from 'lib/selections' | ||||
| import { | ||||
|   editorManager, | ||||
|   kclManager, | ||||
|   sceneEntitiesManager, | ||||
|   sceneInfra, | ||||
| } from 'lib/singletons' | ||||
| import { | ||||
|   Intersection, | ||||
|   Mesh, | ||||
|   MeshBasicMaterial, | ||||
|   Object3D, | ||||
|   Object3DEventMap, | ||||
|   OrthographicCamera, | ||||
|   Points, | ||||
|   Vector2, | ||||
|   Vector3, | ||||
| } from 'three' | ||||
| import { | ||||
|   EXTRA_SEGMENT_HANDLE, | ||||
|   PROFILE_START, | ||||
|   STRAIGHT_SEGMENT, | ||||
|   TANGENTIAL_ARC_TO_SEGMENT, | ||||
|   getParentGroup, | ||||
|   sketchGroupFromPathToNode, | ||||
| } from './sceneEntities' | ||||
| import { trap } from 'lib/trap' | ||||
| import { addNewSketchLn } from 'lang/std/sketch' | ||||
| import { CallExpression, PathToNode, parse, recast } from 'lang/wasm' | ||||
| import { ARROWHEAD, X_AXIS, Y_AXIS } from './sceneInfra' | ||||
| import { getNodeFromPath } from 'lang/queryAst' | ||||
| import { getThemeColorForThreeJs } from 'lib/theme' | ||||
| import { orthoScale, perspScale, quaternionFromUpNForward } from './helpers' | ||||
| import { ModelingMachineContext } from 'machines/modelingMachine' | ||||
| import { addStartProfileAt } from 'lang/modifyAst' | ||||
|  | ||||
| export interface OnMouseEnterLeaveArgs { | ||||
|   selected: Object3D<Object3DEventMap> | ||||
|   dragSelected?: Object3D<Object3DEventMap> | ||||
|   mouseEvent: MouseEvent | ||||
| } | ||||
|  | ||||
| export interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs { | ||||
|   intersectionPoint: { | ||||
|     twoD: Vector2 | ||||
|     threeD: Vector3 | ||||
|   } | ||||
|   intersects: Intersection<Object3D<Object3DEventMap>>[] | ||||
| } | ||||
| export interface OnClickCallbackArgs { | ||||
|   mouseEvent: MouseEvent | ||||
|   intersectionPoint?: { | ||||
|     twoD: Vector2 | ||||
|     threeD: Vector3 | ||||
|   } | ||||
|   intersects: Intersection<Object3D<Object3DEventMap>>[] | ||||
|   selected?: Object3D<Object3DEventMap> | ||||
| } | ||||
|  | ||||
| export interface OnMoveCallbackArgs { | ||||
|   mouseEvent: MouseEvent | ||||
|   intersectionPoint: { | ||||
|     twoD: Vector2 | ||||
|     threeD: Vector3 | ||||
|   } | ||||
|   intersects: Intersection<Object3D<Object3DEventMap>>[] | ||||
|   selected?: Object3D<Object3DEventMap> | ||||
| } | ||||
|  | ||||
| interface Callbacks { | ||||
|   onDragStart: (arg: OnDragCallbackArgs) => void | ||||
|   onDragEnd: (arg: OnDragCallbackArgs) => void | ||||
|   onDrag: (arg: OnDragCallbackArgs) => void | ||||
|   onMove: (arg: OnMoveCallbackArgs) => void | ||||
|   onClick: (arg: OnClickCallbackArgs) => void | ||||
|   onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void | ||||
|   onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void | ||||
| } | ||||
|  | ||||
| type SetCallbacksWithCtx = (context: ModelingMachineContext) => Callbacks | ||||
|  | ||||
| const dummyListenersAll: Callbacks = { | ||||
|   onDragStart: () => {}, | ||||
|   onDragEnd: () => {}, | ||||
|   onDrag: () => {}, | ||||
|   onMove: () => {}, | ||||
|   onClick: () => {}, | ||||
|   onMouseEnter: () => {}, | ||||
|   onMouseLeave: () => {}, | ||||
| } | ||||
|  | ||||
| const onMousEnterLeaveCallbacks = { | ||||
|   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)) | ||||
|       if (trap(updatedAst)) return | ||||
|       const _node = getNodeFromPath<CallExpression>( | ||||
|         updatedAst, | ||||
|         parent.userData.pathToNode, | ||||
|         'CallExpression' | ||||
|       ) | ||||
|       if (trap(_node, { suppress: true })) return | ||||
|       const node = _node.node | ||||
|       editorManager.setHighlightRange([node.start, node.end]) | ||||
|       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) { | ||||
|         sceneEntitiesManager.updateStraightSegment({ | ||||
|           from: parent.userData.from, | ||||
|           to: parent.userData.to, | ||||
|           group: parent, | ||||
|           scale: factor, | ||||
|         }) | ||||
|       } else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) { | ||||
|         sceneEntitiesManager.updateTangentialArcToSegment({ | ||||
|           prevSegment: parent.userData.prevSegment, | ||||
|           from: parent.userData.from, | ||||
|           to: parent.userData.to, | ||||
|           group: parent, | ||||
|           scale: factor, | ||||
|         }) | ||||
|       } | ||||
|       return | ||||
|     } | ||||
|     editorManager.setHighlightRange([0, 0]) | ||||
|   }, | ||||
|   onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => { | ||||
|     editorManager.setHighlightRange([0, 0]) | ||||
|     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) { | ||||
|         sceneEntitiesManager.updateStraightSegment({ | ||||
|           from: parent.userData.from, | ||||
|           to: parent.userData.to, | ||||
|           group: parent, | ||||
|           scale: factor, | ||||
|         }) | ||||
|       } else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) { | ||||
|         sceneEntitiesManager.updateTangentialArcToSegment({ | ||||
|           prevSegment: parent.userData.prevSegment, | ||||
|           from: parent.userData.from, | ||||
|           to: parent.userData.to, | ||||
|           group: parent, | ||||
|           scale: factor, | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
|     const isSelected = parent?.userData?.isSelected | ||||
|     colorSegment( | ||||
|       selected, | ||||
|       isSelected | ||||
|         ? 0x0000ff | ||||
|         : parent?.userData?.baseColor || | ||||
|             getThemeColorForThreeJs(sceneInfra._theme) | ||||
|     ) | ||||
|     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) | ||||
|     } | ||||
|   }, | ||||
| } as const | ||||
|  | ||||
| export const idleCallbacks: SetCallbacksWithCtx = (context) => { | ||||
|   let addingNewSegmentStatus: 'nothing' | 'pending' | 'added' = 'nothing' | ||||
|   return { | ||||
|     onDragStart: () => {}, | ||||
|     onDragEnd: () => {}, | ||||
|     onMove: () => {}, | ||||
|     ...onMousEnterLeaveCallbacks, | ||||
|     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) | ||||
|     }, | ||||
|     onDrag: async ({ selected, intersectionPoint, mouseEvent, intersects }) => { | ||||
|       if (mouseEvent.which !== 1) return | ||||
|  | ||||
|       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, | ||||
|         }) | ||||
|         if (trap(sketchGroup)) return | ||||
|  | ||||
|         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' | ||||
|           if (trap(mod)) return | ||||
|  | ||||
|           await kclManager.executeAstMock(mod.modifiedAst) | ||||
|           await sceneEntitiesManager.tearDownSketch({ removeAxis: false }) | ||||
|           sceneEntitiesManager.setupSketch({ | ||||
|             sketchPathToNode: pathToNode, | ||||
|             maybeModdedAst: kclManager.ast, | ||||
|             up: context.sketchDetails?.yAxis || [0, 1, 0], | ||||
|             forward: context.sketchDetails?.zAxis || [0, 0, 1], | ||||
|             position: context.sketchDetails?.origin || [0, 0, 0], | ||||
|           }) | ||||
|           addingNewSegmentStatus = 'added' | ||||
|         } else if (addingNewSegmentStatus === 'added') { | ||||
|           const pathToNodeForNewSegment = pathToNode.slice(0, pathToNodeIndex) | ||||
|           pathToNodeForNewSegment.push([pipeIndex - 2, 'index']) | ||||
|           sceneEntitiesManager.onDragSegment({ | ||||
|             sketchPathToNode: pathToNodeForNewSegment, | ||||
|             object: selected, | ||||
|             intersection2d: intersectionPoint.twoD, | ||||
|             intersects, | ||||
|           }) | ||||
|         } | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       sceneEntitiesManager.onDragSegment({ | ||||
|         object: selected, | ||||
|         intersection2d: intersectionPoint.twoD, | ||||
|         intersects, | ||||
|         sketchPathToNode: context.sketchDetails?.sketchPathToNode || [], | ||||
|       }) | ||||
|     }, | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const Sketch_LineTool_NoPoints: SetCallbacksWithCtx = ({ | ||||
|   sketchDetails, | ||||
| }) => { | ||||
|   if (!sketchDetails) return dummyListenersAll | ||||
|   sceneEntitiesManager.createIntersectionPlane() | ||||
|   const quaternion = quaternionFromUpNForward( | ||||
|     new Vector3(...sketchDetails.yAxis), | ||||
|     new Vector3(...sketchDetails.zAxis) | ||||
|   ) | ||||
|   sceneEntitiesManager.intersectionPlane && | ||||
|     sceneEntitiesManager.intersectionPlane.setRotationFromQuaternion(quaternion) | ||||
|   sceneEntitiesManager.intersectionPlane && | ||||
|     sceneEntitiesManager.intersectionPlane.position.copy( | ||||
|       new Vector3(...(sketchDetails?.origin || [0, 0, 0])) | ||||
|     ) | ||||
|   return { | ||||
|     ...dummyListenersAll, | ||||
|     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) | ||||
|       sceneEntitiesManager.removeIntersectionPlane() | ||||
|       sceneInfra.modelingSend('Add start point') | ||||
|     }, | ||||
|   } | ||||
| } | ||||
|  | ||||
| function colorSegment(object: any, color: number) { | ||||
|   const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START]) | ||||
|   if (segmentHead) { | ||||
|     segmentHead.traverse((child) => { | ||||
|       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) => { | ||||
|       if (child instanceof Mesh && !child.userData.ignoreColorChange) { | ||||
|         child.material.color.set(color) | ||||
|       } | ||||
|     }) | ||||
|     return | ||||
|   } | ||||
| } | ||||
| @ -22,12 +22,8 @@ import { | ||||
| import { | ||||
|   ARROWHEAD, | ||||
|   AXIS_GROUP, | ||||
|   DEFAULT_PLANES, | ||||
|   DefaultPlane, | ||||
|   defaultPlaneColor, | ||||
|   getSceneScale, | ||||
|   INTERSECTION_PLANE_LAYER, | ||||
|   OnMouseEnterLeaveArgs, | ||||
|   RAYCASTABLE_PLANE, | ||||
|   SKETCH_GROUP_SEGMENTS, | ||||
|   SKETCH_LAYER, | ||||
| @ -82,16 +78,16 @@ import { | ||||
|   createPipeSubstitution, | ||||
|   findUniqueName, | ||||
| } from 'lang/modifyAst' | ||||
| import { | ||||
|   Selections, | ||||
|   getEventForSegmentSelection, | ||||
|   sendSelectEventToEngine, | ||||
| } from 'lib/selections' | ||||
| import { Selections, sendSelectEventToEngine } from 'lib/selections' | ||||
| import { getTangentPointFromPreviousArc } from 'lib/utils2d' | ||||
| import { createGridHelper, orthoScale, perspScale } from './helpers' | ||||
| import { Models } from '@kittycad/lib' | ||||
| import { uuidv4 } from 'lib/utils' | ||||
| import { SegmentOverlayPayload, SketchDetails } from 'machines/modelingMachine' | ||||
| import { | ||||
|   SegmentOverlayPayload, | ||||
|   SketchDetails, | ||||
|   ModelingMachineContext, | ||||
| } from 'machines/modelingMachine' | ||||
| import { | ||||
|   ArtifactMapCommand, | ||||
|   EngineCommandManager, | ||||
| @ -102,6 +98,7 @@ import { | ||||
| } from 'lib/rectangleTool' | ||||
| import { getThemeColorForThreeJs } from 'lib/theme' | ||||
| import { err, trap } from 'lib/trap' | ||||
| import { OnMouseEnterLeaveArgs, idleCallbacks } from './sceneCallbacks' | ||||
|  | ||||
| type DraftSegment = 'line' | 'tangentialArcTo' | ||||
|  | ||||
| @ -499,31 +496,23 @@ export class SceneEntities { | ||||
|       variableDeclarationName, | ||||
|     } | ||||
|   } | ||||
|   updateAstAndRejigSketch = async ( | ||||
|     sketchPathToNode: PathToNode, | ||||
|   updateAstAndRejigSketch: ( | ||||
|     modifiedAst: Program | Error, | ||||
|     forward: [number, number, number], | ||||
|     up: [number, number, number], | ||||
|     origin: [number, number, number] | ||||
|   ) => { | ||||
|     context: ModelingMachineContext | ||||
|   ) => Promise<any> = async (modifiedAst, context) => { | ||||
|     if (err(modifiedAst)) return modifiedAst | ||||
|  | ||||
|     const nextAst = await kclManager.updateAst(modifiedAst, false) | ||||
|     await this.tearDownSketch({ removeAxis: false }) | ||||
|     sceneInfra.resetMouseListeners() | ||||
|     await this.setupSketch({ | ||||
|       sketchPathToNode, | ||||
|       forward, | ||||
|       up, | ||||
|       position: origin, | ||||
|       sketchPathToNode: context.sketchDetails?.sketchPathToNode || [], | ||||
|       forward: context.sketchDetails?.zAxis || [0, 0, 1], | ||||
|       up: context.sketchDetails?.yAxis || [0, 1, 0], | ||||
|       position: context.sketchDetails?.origin || [0, 0, 0], | ||||
|       maybeModdedAst: nextAst.newAst, | ||||
|     }) | ||||
|     this.setupSketchIdleCallbacks({ | ||||
|       forward, | ||||
|       up, | ||||
|       position: origin, | ||||
|       pathToNode: sketchPathToNode, | ||||
|     }) | ||||
|     this.setupSketchIdleCallbacks(context) | ||||
|     return nextAst | ||||
|   } | ||||
|   setUpDraftSegment = async ( | ||||
| @ -838,128 +827,8 @@ export class SceneEntities { | ||||
|       }, | ||||
|     }) | ||||
|   } | ||||
|   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' | ||||
|     sceneInfra.setCallbacks({ | ||||
|       onDragEnd: async () => { | ||||
|         if (addingNewSegmentStatus !== 'nothing') { | ||||
|           await this.tearDownSketch({ removeAxis: false }) | ||||
|           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, | ||||
|           }) | ||||
|         } | ||||
|       }, | ||||
|       onDrag: async ({ | ||||
|         selected, | ||||
|         intersectionPoint, | ||||
|         mouseEvent, | ||||
|         intersects, | ||||
|       }) => { | ||||
|         if (mouseEvent.which !== 1) return | ||||
|  | ||||
|         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, | ||||
|           }) | ||||
|           if (trap(sketchGroup)) return | ||||
|  | ||||
|           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' | ||||
|             if (trap(mod)) return | ||||
|  | ||||
|             await kclManager.executeAstMock(mod.modifiedAst) | ||||
|             await this.tearDownSketch({ removeAxis: false }) | ||||
|             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 | ||||
|         } | ||||
|  | ||||
|         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) | ||||
|       }, | ||||
|       ...this.mouseEnterLeaveCallbacks(), | ||||
|     }) | ||||
|   setupSketchIdleCallbacks = (context: ModelingMachineContext) => { | ||||
|     sceneInfra.setCallbacks(idleCallbacks(context)) | ||||
|   } | ||||
|   prepareTruncatedMemoryAndAst = ( | ||||
|     sketchPathToNode: PathToNode, | ||||
| @ -1429,150 +1298,6 @@ export class SceneEntities { | ||||
|       this._tearDownSketch(0, resolve, reject, { removeAxis }) | ||||
|     }) | ||||
|   } | ||||
|   setupDefaultPlaneHover() { | ||||
|     sceneInfra.setCallbacks({ | ||||
|       onMouseEnter: ({ selected }) => { | ||||
|         if (!(selected instanceof Mesh && selected.parent)) return | ||||
|         if (selected.parent.userData.type !== DEFAULT_PLANES) return | ||||
|         const type: DefaultPlane = selected.userData.type | ||||
|         selected.material.color = defaultPlaneColor(type, 0.5, 1) | ||||
|       }, | ||||
|       onMouseLeave: ({ selected }) => { | ||||
|         if (!(selected instanceof Mesh && selected.parent)) return | ||||
|         if (selected.parent.userData.type !== DEFAULT_PLANES) return | ||||
|         const type: DefaultPlane = selected.userData.type | ||||
|         selected.material.color = defaultPlaneColor(type) | ||||
|       }, | ||||
|       onClick: async (args) => { | ||||
|         const { entity_id } = await sendSelectEventToEngine( | ||||
|           args?.mouseEvent, | ||||
|           document.getElementById('video-stream') as HTMLVideoElement, | ||||
|           sceneInfra._streamDimensions | ||||
|         ) | ||||
|  | ||||
|         let _entity_id = entity_id | ||||
|         if (!_entity_id) return | ||||
|         if ( | ||||
|           engineCommandManager.defaultPlanes?.xy === _entity_id || | ||||
|           engineCommandManager.defaultPlanes?.xz === _entity_id || | ||||
|           engineCommandManager.defaultPlanes?.yz === _entity_id || | ||||
|           engineCommandManager.defaultPlanes?.negXy === _entity_id || | ||||
|           engineCommandManager.defaultPlanes?.negXz === _entity_id || | ||||
|           engineCommandManager.defaultPlanes?.negYz === _entity_id | ||||
|         ) { | ||||
|           const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = { | ||||
|             [engineCommandManager.defaultPlanes.xy]: 'XY', | ||||
|             [engineCommandManager.defaultPlanes.xz]: 'XZ', | ||||
|             [engineCommandManager.defaultPlanes.yz]: 'YZ', | ||||
|             [engineCommandManager.defaultPlanes.negXy]: '-XY', | ||||
|             [engineCommandManager.defaultPlanes.negXz]: '-XZ', | ||||
|             [engineCommandManager.defaultPlanes.negYz]: '-YZ', | ||||
|           } | ||||
|           // TODO can we get this information from rust land when it creates the default planes? | ||||
|           // maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs) | ||||
|           let zAxis: [number, number, number] = [0, 0, 1] | ||||
|           let yAxis: [number, number, number] = [0, 1, 0] | ||||
|  | ||||
|           // get unit vector from camera position to target | ||||
|           const camVector = sceneInfra.camControls.camera.position | ||||
|             .clone() | ||||
|             .sub(sceneInfra.camControls.target) | ||||
|  | ||||
|           if (engineCommandManager.defaultPlanes?.xy === _entity_id) { | ||||
|             zAxis = [0, 0, 1] | ||||
|             yAxis = [0, 1, 0] | ||||
|             if (camVector.z < 0) { | ||||
|               zAxis = [0, 0, -1] | ||||
|               _entity_id = engineCommandManager.defaultPlanes?.negXy || '' | ||||
|             } | ||||
|           } else if (engineCommandManager.defaultPlanes?.yz === _entity_id) { | ||||
|             zAxis = [1, 0, 0] | ||||
|             yAxis = [0, 0, 1] | ||||
|             if (camVector.x < 0) { | ||||
|               zAxis = [-1, 0, 0] | ||||
|               _entity_id = engineCommandManager.defaultPlanes?.negYz || '' | ||||
|             } | ||||
|           } else if (engineCommandManager.defaultPlanes?.xz === _entity_id) { | ||||
|             zAxis = [0, 1, 0] | ||||
|             yAxis = [0, 0, 1] | ||||
|             _entity_id = engineCommandManager.defaultPlanes?.negXz || '' | ||||
|             if (camVector.y < 0) { | ||||
|               zAxis = [0, -1, 0] | ||||
|               _entity_id = engineCommandManager.defaultPlanes?.xz || '' | ||||
|             } | ||||
|           } | ||||
|  | ||||
|           sceneInfra.modelingSend({ | ||||
|             type: 'Select default plane', | ||||
|             data: { | ||||
|               type: 'defaultPlane', | ||||
|               planeId: _entity_id, | ||||
|               plane: defaultPlaneStrMap[_entity_id], | ||||
|               zAxis, | ||||
|               yAxis, | ||||
|             }, | ||||
|           }) | ||||
|           return | ||||
|         } | ||||
|         const artifact = this.engineCommandManager.artifactMap[_entity_id] | ||||
|         // If we clicked on an extrude wall, we climb up the parent Id | ||||
|         // to get the sketch profile's face ID. If we clicked on an endcap, | ||||
|         // we already have it. | ||||
|         const targetId = | ||||
|           'additionalData' in artifact && | ||||
|           artifact.additionalData?.type === 'cap' | ||||
|             ? _entity_id | ||||
|             : artifact.parentId | ||||
|  | ||||
|         // tsc cannot infer that target can have extrusions | ||||
|         // from the commandType (why?) so we need to cast it | ||||
|         const target = this.engineCommandManager.artifactMap?.[ | ||||
|           targetId || '' | ||||
|         ] as ArtifactMapCommand & { extrusions?: string[] } | ||||
|  | ||||
|         // TODO: We get the first extrusion command ID, | ||||
|         // which is fine while backend systems only support one extrusion. | ||||
|         // but we need to more robustly handle resolving to the correct extrusion | ||||
|         // if there are multiple. | ||||
|         const extrusions = | ||||
|           this.engineCommandManager.artifactMap?.[target?.extrusions?.[0] || ''] | ||||
|  | ||||
|         if (artifact?.commandType !== 'solid3d_get_extrusion_face_info') return | ||||
|  | ||||
|         const faceInfo = await getFaceDetails(_entity_id) | ||||
|         if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis) return | ||||
|         const { z_axis, y_axis, origin } = faceInfo | ||||
|         const sketchPathToNode = getNodePathFromSourceRange( | ||||
|           kclManager.ast, | ||||
|           artifact.range | ||||
|         ) | ||||
|  | ||||
|         const extrudePathToNode = extrusions?.range | ||||
|           ? getNodePathFromSourceRange(kclManager.ast, extrusions.range) | ||||
|           : [] | ||||
|  | ||||
|         sceneInfra.modelingSend({ | ||||
|           type: 'Select default plane', | ||||
|           data: { | ||||
|             type: 'extrudeFace', | ||||
|             zAxis: [z_axis.x, z_axis.y, z_axis.z], | ||||
|             yAxis: [y_axis.x, y_axis.y, y_axis.z], | ||||
|             position: [origin.x, origin.y, origin.z].map( | ||||
|               (num) => num / sceneInfra._baseUnitMultiplier | ||||
|             ) as [number, number, number], | ||||
|             sketchPathToNode, | ||||
|             extrudePathToNode, | ||||
|             cap: | ||||
|               artifact?.additionalData?.type === 'cap' | ||||
|                 ? artifact.additionalData.info | ||||
|                 : 'none', | ||||
|             faceId: _entity_id, | ||||
|           }, | ||||
|         }) | ||||
|         return | ||||
|       }, | ||||
|     }) | ||||
|   } | ||||
|   mouseEnterLeaveCallbacks() { | ||||
|     return { | ||||
|       onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => { | ||||
| @ -1863,6 +1588,7 @@ export function sketchGroupFromPathToNode({ | ||||
|   return result as SketchGroup | ||||
| } | ||||
|  | ||||
| // TODO delete | ||||
| function colorSegment(object: any, color: number) { | ||||
|   const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START]) | ||||
|   if (segmentHead) { | ||||
|  | ||||
| @ -1,25 +1,25 @@ | ||||
| import { | ||||
|   AmbientLight, | ||||
|   Color, | ||||
|   GridHelper, | ||||
|   LineBasicMaterial, | ||||
|   OrthographicCamera, | ||||
|   PerspectiveCamera, | ||||
|   Scene, | ||||
|   Vector3, | ||||
|   WebGLRenderer, | ||||
|   Raycaster, | ||||
|   Vector2, | ||||
|   Group, | ||||
|   PlaneGeometry, | ||||
|   MeshBasicMaterial, | ||||
|   Mesh, | ||||
|   DoubleSide, | ||||
|   GridHelper, | ||||
|   Group, | ||||
|   Intersection, | ||||
|   LineBasicMaterial, | ||||
|   Mesh, | ||||
|   MeshBasicMaterial, | ||||
|   Object3D, | ||||
|   Object3DEventMap, | ||||
|   TextureLoader, | ||||
|   OrthographicCamera, | ||||
|   PerspectiveCamera, | ||||
|   PlaneGeometry, | ||||
|   Raycaster, | ||||
|   Scene, | ||||
|   Texture, | ||||
|   TextureLoader, | ||||
|   Vector2, | ||||
|   Vector3, | ||||
|   WebGLRenderer, | ||||
| } from 'three' | ||||
| import { Coords2d, compareVec2Epsilon2 } from 'lang/std/sketch' | ||||
| import { useModelingContext } from 'hooks/useModelingContext' | ||||
| @ -31,6 +31,12 @@ import { EngineCommandManager } from 'lang/std/engineConnection' | ||||
| import { MouseState, SegmentOverlayPayload } from 'machines/modelingMachine' | ||||
| import { getAngle, throttle } from 'lib/utils' | ||||
| import { Themes } from 'lib/theme' | ||||
| import { | ||||
|   OnClickCallbackArgs, | ||||
|   OnDragCallbackArgs, | ||||
|   OnMouseEnterLeaveArgs, | ||||
|   OnMoveCallbackArgs, | ||||
| } from './sceneCallbacks' | ||||
|  | ||||
| type SendType = ReturnType<typeof useModelingContext>['send'] | ||||
|  | ||||
| @ -55,39 +61,6 @@ export const AXIS_GROUP = 'axisGroup' | ||||
| export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments' | ||||
| export const ARROWHEAD = 'arrowhead' | ||||
|  | ||||
| export interface OnMouseEnterLeaveArgs { | ||||
|   selected: Object3D<Object3DEventMap> | ||||
|   dragSelected?: Object3D<Object3DEventMap> | ||||
|   mouseEvent: MouseEvent | ||||
| } | ||||
|  | ||||
| interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs { | ||||
|   intersectionPoint: { | ||||
|     twoD: Vector2 | ||||
|     threeD: Vector3 | ||||
|   } | ||||
|   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. | ||||
| // That is the cameras and switching between them, raycasters for click mouse events and their abstractions (onClick etc), setting up controls. | ||||
| // Anything that added the the scene for the user to interact with is probably in SceneEntities.ts | ||||
| @ -618,59 +591,6 @@ export class SceneInfra { | ||||
|       this.onClickCallback({ mouseEvent, intersects }) | ||||
|     } | ||||
|   } | ||||
|   showDefaultPlanes() { | ||||
|     const addPlane = ( | ||||
|       rotation: { x: number; y: number; z: number }, // | ||||
|       type: DefaultPlane | ||||
|     ): Mesh => { | ||||
|       const planeGeometry = new PlaneGeometry(100, 100) | ||||
|       const planeMaterial = new MeshBasicMaterial({ | ||||
|         color: defaultPlaneColor(type), | ||||
|         transparent: true, | ||||
|         opacity: 0.0, | ||||
|         side: DoubleSide, | ||||
|         depthTest: false, // needed to avoid transparency issues | ||||
|       }) | ||||
|       const plane = new Mesh(planeGeometry, planeMaterial) | ||||
|       plane.rotation.x = rotation.x | ||||
|       plane.rotation.y = rotation.y | ||||
|       plane.rotation.z = rotation.z | ||||
|       plane.userData.type = type | ||||
|       plane.name = type | ||||
|       return plane | ||||
|     } | ||||
|     const planes = [ | ||||
|       addPlane({ x: 0, y: Math.PI / 2, z: 0 }, YZ_PLANE), | ||||
|       addPlane({ x: 0, y: 0, z: 0 }, XY_PLANE), | ||||
|       addPlane({ x: -Math.PI / 2, y: 0, z: 0 }, XZ_PLANE), | ||||
|     ] | ||||
|     const planesGroup = new Group() | ||||
|     planesGroup.userData.type = DEFAULT_PLANES | ||||
|     planesGroup.name = DEFAULT_PLANES | ||||
|     planesGroup.add(...planes) | ||||
|     planesGroup.traverse((child) => { | ||||
|       if (child instanceof Mesh) { | ||||
|         child.layers.enable(SKETCH_LAYER) | ||||
|       } | ||||
|     }) | ||||
|     planesGroup.layers.enable(SKETCH_LAYER) | ||||
|     const sceneScale = getSceneScale( | ||||
|       this.camControls.camera, | ||||
|       this.camControls.target | ||||
|     ) | ||||
|     planesGroup.scale.set( | ||||
|       sceneScale / this._baseUnitMultiplier, | ||||
|       sceneScale / this._baseUnitMultiplier, | ||||
|       sceneScale / this._baseUnitMultiplier | ||||
|     ) | ||||
|     this.scene.add(planesGroup) | ||||
|   } | ||||
|   removeDefaultPlanes() { | ||||
|     const planesGroup = this.scene.children.find( | ||||
|       ({ userData }) => userData.type === DEFAULT_PLANES | ||||
|     ) | ||||
|     if (planesGroup) this.scene.remove(planesGroup) | ||||
|   } | ||||
|   updateOtherSelectionColors = (otherSelections: Axis[]) => { | ||||
|     const axisGroup = this.scene.children.find( | ||||
|       ({ userData }) => userData?.type === AXIS_GROUP | ||||
| @ -728,28 +648,3 @@ function baseUnitTomm(baseUnit: BaseUnit) { | ||||
|       return 914.4 | ||||
|   } | ||||
| } | ||||
|  | ||||
| export type DefaultPlane = | ||||
|   | 'xy-default-plane' | ||||
|   | 'xz-default-plane' | ||||
|   | 'yz-default-plane' | ||||
|  | ||||
| export const XY_PLANE: DefaultPlane = 'xy-default-plane' | ||||
| export const XZ_PLANE: DefaultPlane = 'xz-default-plane' | ||||
| export const YZ_PLANE: DefaultPlane = 'yz-default-plane' | ||||
|  | ||||
| export function defaultPlaneColor( | ||||
|   plane: DefaultPlane, | ||||
|   lowCh = 0.1, | ||||
|   highCh = 0.7 | ||||
| ): Color { | ||||
|   switch (plane) { | ||||
|     case XY_PLANE: | ||||
|       return new Color(highCh, lowCh, lowCh) | ||||
|     case XZ_PLANE: | ||||
|       return new Color(lowCh, lowCh, highCh) | ||||
|     case YZ_PLANE: | ||||
|       return new Color(lowCh, highCh, lowCh) | ||||
|   } | ||||
|   return new Color(lowCh, lowCh, lowCh) | ||||
| } | ||||
|  | ||||
| @ -549,10 +549,8 @@ export const ModelingMachineProvider = ({ | ||||
|             ) as [number, number, number], | ||||
|           } | ||||
|         }, | ||||
|         'Get horizontal info': async ({ | ||||
|           selectionRanges, | ||||
|           sketchDetails, | ||||
|         }): Promise<SetSelections> => { | ||||
|         'Get horizontal info': async (context): Promise<SetSelections> => { | ||||
|           const { selectionRanges, sketchDetails } = context | ||||
|           const { modifiedAst, pathToNodeMap } = | ||||
|             await applyConstraintHorzVertDistance({ | ||||
|               constraint: 'setHorzDistance', | ||||
| @ -566,11 +564,8 @@ export const ModelingMachineProvider = ({ | ||||
|             pathToNodeMap | ||||
|           ) | ||||
|           const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch( | ||||
|             updatedPathToNode, | ||||
|             _modifiedAst, | ||||
|             sketchDetails.zAxis, | ||||
|             sketchDetails.yAxis, | ||||
|             sketchDetails.origin | ||||
|             context | ||||
|           ) | ||||
|           if (err(updatedAst)) return Promise.reject(updatedAst) | ||||
|           const selection = updateSelections( | ||||
| @ -585,10 +580,8 @@ export const ModelingMachineProvider = ({ | ||||
|             updatedPathToNode, | ||||
|           } | ||||
|         }, | ||||
|         'Get vertical info': async ({ | ||||
|           selectionRanges, | ||||
|           sketchDetails, | ||||
|         }): Promise<SetSelections> => { | ||||
|         'Get vertical info': async (context): Promise<SetSelections> => { | ||||
|           const { selectionRanges, sketchDetails } = context | ||||
|           const { modifiedAst, pathToNodeMap } = | ||||
|             await applyConstraintHorzVertDistance({ | ||||
|               constraint: 'setVertDistance', | ||||
| @ -602,11 +595,8 @@ export const ModelingMachineProvider = ({ | ||||
|             pathToNodeMap | ||||
|           ) | ||||
|           const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch( | ||||
|             updatedPathToNode, | ||||
|             _modifiedAst, | ||||
|             sketchDetails.zAxis, | ||||
|             sketchDetails.yAxis, | ||||
|             sketchDetails.origin | ||||
|             context | ||||
|           ) | ||||
|           if (err(updatedAst)) return Promise.reject(updatedAst) | ||||
|           const selection = updateSelections( | ||||
| @ -621,10 +611,8 @@ export const ModelingMachineProvider = ({ | ||||
|             updatedPathToNode, | ||||
|           } | ||||
|         }, | ||||
|         'Get angle info': async ({ | ||||
|           selectionRanges, | ||||
|           sketchDetails, | ||||
|         }): Promise<SetSelections> => { | ||||
|         'Get angle info': async (context): Promise<SetSelections> => { | ||||
|           const { selectionRanges, sketchDetails } = context | ||||
|           const info = angleBetweenInfo({ | ||||
|             selectionRanges, | ||||
|           }) | ||||
| @ -647,11 +635,8 @@ export const ModelingMachineProvider = ({ | ||||
|             pathToNodeMap | ||||
|           ) | ||||
|           const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch( | ||||
|             updatedPathToNode, | ||||
|             _modifiedAst, | ||||
|             sketchDetails.zAxis, | ||||
|             sketchDetails.yAxis, | ||||
|             sketchDetails.origin | ||||
|             context | ||||
|           ) | ||||
|           if (err(updatedAst)) return Promise.reject(updatedAst) | ||||
|           const selection = updateSelections( | ||||
| @ -666,10 +651,8 @@ export const ModelingMachineProvider = ({ | ||||
|             updatedPathToNode, | ||||
|           } | ||||
|         }, | ||||
|         'Get length info': async ({ | ||||
|           selectionRanges, | ||||
|           sketchDetails, | ||||
|         }): Promise<SetSelections> => { | ||||
|         'Get length info': async (context): Promise<SetSelections> => { | ||||
|           const { selectionRanges, sketchDetails } = context | ||||
|           const { modifiedAst, pathToNodeMap } = | ||||
|             await applyConstraintAngleLength({ selectionRanges }) | ||||
|           const _modifiedAst = parse(recast(modifiedAst)) | ||||
| @ -680,11 +663,8 @@ export const ModelingMachineProvider = ({ | ||||
|             pathToNodeMap | ||||
|           ) | ||||
|           const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch( | ||||
|             updatedPathToNode, | ||||
|             _modifiedAst, | ||||
|             sketchDetails.zAxis, | ||||
|             sketchDetails.yAxis, | ||||
|             sketchDetails.origin | ||||
|             context | ||||
|           ) | ||||
|           if (err(updatedAst)) return Promise.reject(updatedAst) | ||||
|           const selection = updateSelections( | ||||
| @ -699,10 +679,10 @@ export const ModelingMachineProvider = ({ | ||||
|             updatedPathToNode, | ||||
|           } | ||||
|         }, | ||||
|         'Get perpendicular distance info': async ({ | ||||
|           selectionRanges, | ||||
|           sketchDetails, | ||||
|         }): Promise<SetSelections> => { | ||||
|         'Get perpendicular distance info': async ( | ||||
|           context | ||||
|         ): Promise<SetSelections> => { | ||||
|           const { selectionRanges, sketchDetails } = context | ||||
|           const { modifiedAst, pathToNodeMap } = await applyConstraintIntersect( | ||||
|             { | ||||
|               selectionRanges, | ||||
| @ -716,11 +696,8 @@ export const ModelingMachineProvider = ({ | ||||
|             pathToNodeMap | ||||
|           ) | ||||
|           const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch( | ||||
|             updatedPathToNode, | ||||
|             _modifiedAst, | ||||
|             sketchDetails.zAxis, | ||||
|             sketchDetails.yAxis, | ||||
|             sketchDetails.origin | ||||
|             context | ||||
|           ) | ||||
|           if (err(updatedAst)) return Promise.reject(updatedAst) | ||||
|           const selection = updateSelections( | ||||
| @ -735,10 +712,8 @@ export const ModelingMachineProvider = ({ | ||||
|             updatedPathToNode, | ||||
|           } | ||||
|         }, | ||||
|         'Get ABS X info': async ({ | ||||
|           selectionRanges, | ||||
|           sketchDetails, | ||||
|         }): Promise<SetSelections> => { | ||||
|         'Get ABS X info': async (context): Promise<SetSelections> => { | ||||
|           const { selectionRanges, sketchDetails } = context | ||||
|           const { modifiedAst, pathToNodeMap } = | ||||
|             await applyConstraintAbsDistance({ | ||||
|               constraint: 'xAbs', | ||||
| @ -752,11 +727,8 @@ export const ModelingMachineProvider = ({ | ||||
|             pathToNodeMap | ||||
|           ) | ||||
|           const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch( | ||||
|             updatedPathToNode, | ||||
|             _modifiedAst, | ||||
|             sketchDetails.zAxis, | ||||
|             sketchDetails.yAxis, | ||||
|             sketchDetails.origin | ||||
|             context | ||||
|           ) | ||||
|           if (err(updatedAst)) return Promise.reject(updatedAst) | ||||
|           const selection = updateSelections( | ||||
| @ -771,10 +743,8 @@ export const ModelingMachineProvider = ({ | ||||
|             updatedPathToNode, | ||||
|           } | ||||
|         }, | ||||
|         'Get ABS Y info': async ({ | ||||
|           selectionRanges, | ||||
|           sketchDetails, | ||||
|         }): Promise<SetSelections> => { | ||||
|         'Get ABS Y info': async (context): Promise<SetSelections> => { | ||||
|           const { selectionRanges, sketchDetails } = context | ||||
|           const { modifiedAst, pathToNodeMap } = | ||||
|             await applyConstraintAbsDistance({ | ||||
|               constraint: 'yAbs', | ||||
| @ -788,11 +758,8 @@ export const ModelingMachineProvider = ({ | ||||
|             pathToNodeMap | ||||
|           ) | ||||
|           const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch( | ||||
|             updatedPathToNode, | ||||
|             _modifiedAst, | ||||
|             sketchDetails.zAxis, | ||||
|             sketchDetails.yAxis, | ||||
|             sketchDetails.origin | ||||
|             context | ||||
|           ) | ||||
|           if (err(updatedAst)) return Promise.reject(updatedAst) | ||||
|           const selection = updateSelections( | ||||
| @ -808,9 +775,10 @@ export const ModelingMachineProvider = ({ | ||||
|           } | ||||
|         }, | ||||
|         'Get convert to variable info': async ( | ||||
|           { sketchDetails, selectionRanges }, | ||||
|           context, | ||||
|           { data } | ||||
|         ): Promise<SetSelections> => { | ||||
|           const { selectionRanges, sketchDetails } = context | ||||
|           if (!sketchDetails) | ||||
|             return Promise.reject(new Error('No sketch details')) | ||||
|           const { variableName } = await getVarNameModal({ | ||||
| @ -834,11 +802,8 @@ export const ModelingMachineProvider = ({ | ||||
|             return Promise.reject(new Error('No path to replaced node')) | ||||
|  | ||||
|           const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch( | ||||
|             pathToReplacedNode || [], | ||||
|             parsed, | ||||
|             sketchDetails.zAxis, | ||||
|             sketchDetails.yAxis, | ||||
|             sketchDetails.origin | ||||
|             context | ||||
|           ) | ||||
|           if (err(updatedAst)) return Promise.reject(updatedAst) | ||||
|           const selection = updateSelections( | ||||
|  | ||||
| @ -109,7 +109,6 @@ export const Stream = () => { | ||||
|       }, | ||||
|     }) | ||||
|     if (state.matches('Sketch')) return | ||||
|     if (state.matches('Sketch no face')) return | ||||
|  | ||||
|     if (!context.store?.didDragInStream && butName(e).left) { | ||||
|       sendSelectEventToEngine( | ||||
|  | ||||
| @ -4,7 +4,7 @@ import { useModelingContext } from './useModelingContext' | ||||
| import { getEventForSelectWithPoint } from 'lib/selections' | ||||
|  | ||||
| export function useEngineConnectionSubscriptions() { | ||||
|   const { send, context } = useModelingContext() | ||||
|   const { send, state } = useModelingContext() | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (!engineCommandManager) return | ||||
| @ -29,9 +29,7 @@ export function useEngineConnectionSubscriptions() { | ||||
|     const unSubClick = engineCommandManager.subscribeTo({ | ||||
|       event: 'select_with_point', | ||||
|       callback: async (engineEvent) => { | ||||
|         const event = await getEventForSelectWithPoint(engineEvent, { | ||||
|           sketchEnginePathId: context.sketchEnginePathId, | ||||
|         }) | ||||
|         const event = await getEventForSelectWithPoint(engineEvent, state) | ||||
|         event && send(event) | ||||
|       }, | ||||
|     }) | ||||
| @ -39,5 +37,5 @@ export function useEngineConnectionSubscriptions() { | ||||
|       unSubHover() | ||||
|       unSubClick() | ||||
|     } | ||||
|   }, [engineCommandManager, context?.sketchEnginePathId]) | ||||
|   }, [engineCommandManager, state]) | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,7 @@ import { | ||||
|   engineCommandManager, | ||||
|   kclManager, | ||||
|   sceneEntitiesManager, | ||||
|   sceneInfra, | ||||
| } from 'lib/singletons' | ||||
| import { CallExpression, SourceRange, Value, parse, recast } from 'lang/wasm' | ||||
| import { ModelingMachineEvent } from 'machines/modelingMachine' | ||||
| @ -15,6 +16,7 @@ import { Program } from 'lang/wasm' | ||||
| import { | ||||
|   doesPipeHaveCallExp, | ||||
|   getNodeFromPath, | ||||
|   getNodePathFromSourceRange, | ||||
|   hasSketchPipeBeenExtruded, | ||||
|   isSingleCursorInPipe, | ||||
| } from 'lang/queryAst' | ||||
| @ -24,11 +26,15 @@ import { | ||||
|   TANGENTIAL_ARC_TO_SEGMENT, | ||||
|   getParentGroup, | ||||
|   PROFILE_START, | ||||
|   getFaceDetails, | ||||
|   DefaultPlaneStr, | ||||
| } from 'clientSideScene/sceneEntities' | ||||
| import { Mesh, Object3D, Object3DEventMap } from 'three' | ||||
| import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra' | ||||
| import { PathToNodeMap } from 'lang/std/sketchcombos' | ||||
| import { err } from 'lib/trap' | ||||
| import { useModelingContext } from 'hooks/useModelingContext' | ||||
| import { ArtifactMapCommand } from 'lang/std/engineConnection' | ||||
|  | ||||
| export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b' | ||||
| export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01' | ||||
| @ -62,8 +68,12 @@ export async function getEventForSelectWithPoint( | ||||
|     Models['OkModelingCmdResponse_type'], | ||||
|     { type: 'select_with_point' } | ||||
|   >, | ||||
|   { sketchEnginePathId }: { sketchEnginePathId?: string } | ||||
|   state: ReturnType<typeof useModelingContext>['state'] | ||||
| ): Promise<ModelingMachineEvent | null> { | ||||
|   if (state.matches('Sketch no face')) | ||||
|     return handleSelectionInSketchNoFace(data?.entity_id || '') | ||||
|  | ||||
|   // assumes XState state is `idle` from this point | ||||
|   if (!data?.entity_id) { | ||||
|     return { | ||||
|       type: 'Set selection', | ||||
| @ -143,6 +153,128 @@ export async function getEventForSelectWithPoint( | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function handleSelectionInSketchNoFace( | ||||
|   entity_id: string | ||||
| ): Promise<ModelingMachineEvent | null> { | ||||
|   let _entity_id = entity_id | ||||
|   if (!_entity_id) return Promise.resolve(null) | ||||
|   if ( | ||||
|     engineCommandManager.defaultPlanes?.xy === _entity_id || | ||||
|     engineCommandManager.defaultPlanes?.xz === _entity_id || | ||||
|     engineCommandManager.defaultPlanes?.yz === _entity_id || | ||||
|     engineCommandManager.defaultPlanes?.negXy === _entity_id || | ||||
|     engineCommandManager.defaultPlanes?.negXz === _entity_id || | ||||
|     engineCommandManager.defaultPlanes?.negYz === _entity_id | ||||
|   ) { | ||||
|     const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = { | ||||
|       [engineCommandManager.defaultPlanes.xy]: 'XY', | ||||
|       [engineCommandManager.defaultPlanes.xz]: 'XZ', | ||||
|       [engineCommandManager.defaultPlanes.yz]: 'YZ', | ||||
|       [engineCommandManager.defaultPlanes.negXy]: '-XY', | ||||
|       [engineCommandManager.defaultPlanes.negXz]: '-XZ', | ||||
|       [engineCommandManager.defaultPlanes.negYz]: '-YZ', | ||||
|     } | ||||
|     // TODO can we get this information from rust land when it creates the default planes? | ||||
|     // maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs) | ||||
|     let zAxis: [number, number, number] = [0, 0, 1] | ||||
|     let yAxis: [number, number, number] = [0, 1, 0] | ||||
|  | ||||
|     // get unit vector from camera position to target | ||||
|     const camVector = sceneInfra.camControls.camera.position | ||||
|       .clone() | ||||
|       .sub(sceneInfra.camControls.target) | ||||
|  | ||||
|     if (engineCommandManager.defaultPlanes?.xy === _entity_id) { | ||||
|       zAxis = [0, 0, 1] | ||||
|       yAxis = [0, 1, 0] | ||||
|       if (camVector.z < 0) { | ||||
|         zAxis = [0, 0, -1] | ||||
|         _entity_id = engineCommandManager.defaultPlanes?.negXy || '' | ||||
|       } | ||||
|     } else if (engineCommandManager.defaultPlanes?.yz === _entity_id) { | ||||
|       zAxis = [1, 0, 0] | ||||
|       yAxis = [0, 0, 1] | ||||
|       if (camVector.x < 0) { | ||||
|         zAxis = [-1, 0, 0] | ||||
|         _entity_id = engineCommandManager.defaultPlanes?.negYz || '' | ||||
|       } | ||||
|     } else if (engineCommandManager.defaultPlanes?.xz === _entity_id) { | ||||
|       zAxis = [0, 1, 0] | ||||
|       yAxis = [0, 0, 1] | ||||
|       _entity_id = engineCommandManager.defaultPlanes?.negXz || '' | ||||
|       if (camVector.y < 0) { | ||||
|         zAxis = [0, -1, 0] | ||||
|         _entity_id = engineCommandManager.defaultPlanes?.xz || '' | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|       type: 'Select default plane', | ||||
|       data: { | ||||
|         type: 'defaultPlane', | ||||
|         planeId: _entity_id, | ||||
|         plane: defaultPlaneStrMap[_entity_id], | ||||
|         zAxis, | ||||
|         yAxis, | ||||
|       }, | ||||
|     } | ||||
|   } | ||||
|   const artifact = engineCommandManager.artifactMap[_entity_id] | ||||
|   // If we clicked on an extrude wall, we climb up the parent Id | ||||
|   // to get the sketch profile's face ID. If we clicked on an endcap, | ||||
|   // we already have it. | ||||
|   const targetId = | ||||
|     'additionalData' in artifact && artifact.additionalData?.type === 'cap' | ||||
|       ? _entity_id | ||||
|       : artifact.parentId | ||||
|  | ||||
|   // tsc cannot infer that target can have extrusions | ||||
|   // from the commandType (why?) so we need to cast it | ||||
|   const target = engineCommandManager.artifactMap?.[ | ||||
|     targetId || '' | ||||
|   ] as ArtifactMapCommand & { extrusions?: string[] } | ||||
|  | ||||
|   // TODO: We get the first extrusion command ID, | ||||
|   // which is fine while backend systems only support one extrusion. | ||||
|   // but we need to more robustly handle resolving to the correct extrusion | ||||
|   // if there are multiple. | ||||
|   const extrusions = | ||||
|     engineCommandManager.artifactMap?.[target?.extrusions?.[0] || ''] | ||||
|  | ||||
|   if (artifact?.commandType !== 'solid3d_get_extrusion_face_info') return null | ||||
|  | ||||
|   const faceInfo = await getFaceDetails(_entity_id) | ||||
|   if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis) return null | ||||
|   const { z_axis, y_axis, origin } = faceInfo | ||||
|   const sketchPathToNode = getNodePathFromSourceRange( | ||||
|     kclManager.ast, | ||||
|     artifact.range | ||||
|   ) | ||||
|  | ||||
|   const extrudePathToNode = extrusions?.range | ||||
|     ? getNodePathFromSourceRange(kclManager.ast, extrusions.range) | ||||
|     : [] | ||||
|  | ||||
|   return { | ||||
|     type: 'Select default plane', | ||||
|     data: { | ||||
|       type: 'extrudeFace', | ||||
|       zAxis: [z_axis.x, z_axis.y, z_axis.z], | ||||
|       yAxis: [y_axis.x, y_axis.y, y_axis.z], | ||||
|       position: [origin.x, origin.y, origin.z].map( | ||||
|         (num) => num / sceneInfra._baseUnitMultiplier | ||||
|       ) as [number, number, number], | ||||
|       sketchPathToNode, | ||||
|       extrudePathToNode, | ||||
|       cap: | ||||
|         artifact?.additionalData?.type === 'cap' | ||||
|           ? artifact.additionalData.info | ||||
|           : 'none', | ||||
|       faceId: _entity_id, | ||||
|     }, | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function getEventForSegmentSelection( | ||||
|   obj: Object3D<Object3DEventMap> | ||||
| ): ModelingMachineEvent | null { | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user
	