Compare commits
	
		
			6 Commits
		
	
	
		
			jtran/fix-
			...
			franknoiro
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b95272cf03 | |||
| fa973e7b89 | |||
| ee77af74c1 | |||
| 0e85da7e36 | |||
| 8831046bdd | |||
| 01975a5447 | 
| @ -98,6 +98,7 @@ import { | ||||
| import { getThemeColorForThreeJs } from 'lib/theme' | ||||
| import { err, trap } from 'lib/trap' | ||||
| import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' | ||||
| import { addCircleToSketchAst } from 'lib/circleTool' | ||||
|  | ||||
| type DraftSegment = 'line' | 'tangentialArcTo' | ||||
|  | ||||
| @ -714,11 +715,8 @@ export class SceneEntities { | ||||
|     }) | ||||
|   } | ||||
|   setupDraftRectangle = async ( | ||||
|     sketchPathToNode: PathToNode, | ||||
|     forward: [number, number, number], | ||||
|     up: [number, number, number], | ||||
|     sketchOrigin: [number, number, number], | ||||
|     rectangleOrigin: [x: number, y: number] | ||||
|     rectangleOrigin: [number, number], | ||||
|     { sketchPathToNode, origin, zAxis, yAxis }: SketchDetails | ||||
|   ) => { | ||||
|     let _ast = structuredClone(kclManager.ast) | ||||
|  | ||||
| @ -750,9 +748,9 @@ export class SceneEntities { | ||||
|  | ||||
|     const { programMemoryOverride, truncatedAst } = await this.setupSketch({ | ||||
|       sketchPathToNode, | ||||
|       forward, | ||||
|       up, | ||||
|       position: sketchOrigin, | ||||
|       forward: yAxis, | ||||
|       up: zAxis, | ||||
|       position: origin, | ||||
|       maybeModdedAst: _ast, | ||||
|       draftExpressionsIndices: { start: 0, end: 3 }, | ||||
|     }) | ||||
| @ -862,6 +860,52 @@ export class SceneEntities { | ||||
|       }, | ||||
|     }) | ||||
|   } | ||||
|   setupCircleOriginListener = () => { | ||||
|     sceneInfra.setCallbacks({ | ||||
|       onClick: (args) => { | ||||
|         const twoD = args.intersectionPoint?.twoD | ||||
|         if (!twoD) { | ||||
|           console.warn(`This click didn't have a 2D intersection`, args) | ||||
|           return | ||||
|         } | ||||
|         sceneInfra.modelingSend({ | ||||
|           type: 'Add circle origin', | ||||
|           data: [twoD.x, twoD.y], | ||||
|         }) | ||||
|       }, | ||||
|     }) | ||||
|   } | ||||
|   setupDraftCircle = async ( | ||||
|     circleOrigin: [number, number], | ||||
|     { sketchPathToNode, origin, zAxis, yAxis }: SketchDetails | ||||
|   ) => { | ||||
|     const astWithCircle = await addCircleToSketchAst({ | ||||
|       sourceAst: kclManager.ast, | ||||
|       sketchPathToNode, | ||||
|       circleOrigin, | ||||
|     }) | ||||
|  | ||||
|     const { programMemoryOverride, truncatedAst } = await this.setupSketch({ | ||||
|       sketchPathToNode, | ||||
|       forward: yAxis, | ||||
|       up: zAxis, | ||||
|       position: origin, | ||||
|       maybeModdedAst: astWithCircle, | ||||
|       draftExpressionsIndices: { start: 0, end: 3 }, | ||||
|     }) | ||||
|  | ||||
|     // TODO: Move this into the onclick handler to get the real shit | ||||
|     // Update the primary AST and unequip the rectangle tool | ||||
|     await kclManager.executeAstMock(astWithCircle) | ||||
|     sceneInfra.modelingSend({ type: 'CancelSketch' }) | ||||
|  | ||||
|     const { programMemory } = await executeAst({ | ||||
|       ast: astWithCircle, | ||||
|       useFakeExecutor: true, | ||||
|       engineCommandManager: this.engineCommandManager, | ||||
|       programMemoryOverride, | ||||
|     }) | ||||
|   } | ||||
|   setupSketchIdleCallbacks = ({ | ||||
|     pathToNode, | ||||
|     up, | ||||
| @ -1176,6 +1220,9 @@ export class SceneEntities { | ||||
|         ? orthoFactor | ||||
|         : perspScale(sceneInfra.camControls.camera, group)) / | ||||
|       sceneInfra._baseUnitMultiplier | ||||
|  | ||||
|     console.log('segment type', type) | ||||
|  | ||||
|     if (type === TANGENTIAL_ARC_TO_SEGMENT) { | ||||
|       return this.updateTangentialArcToSegment({ | ||||
|         prevSegment: sgPaths[index - 1], | ||||
|  | ||||
							
								
								
									
										92
									
								
								src/lib/circleTool.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/lib/circleTool.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,92 @@ | ||||
| import { | ||||
|   createArrayExpression, | ||||
|   createCallExpressionStdLib, | ||||
|   createLiteral, | ||||
|   createPipeExpression, | ||||
|   createPipeSubstitution, | ||||
|   createTagDeclarator, | ||||
|   findUniqueName, | ||||
| } from 'lang/modifyAst' | ||||
| import { roundOff } from './utils' | ||||
| import { | ||||
|   PathToNode, | ||||
|   Program, | ||||
|   VariableDeclaration, | ||||
|   parse, | ||||
|   recast, | ||||
| } from 'lang/wasm' | ||||
| import { getNodeFromPath } from 'lang/queryAst' | ||||
| import { trap } from './trap' | ||||
|  | ||||
| /** | ||||
|  * Hide away the working with the AST | ||||
|  */ | ||||
| export async function addCircleToSketchAst({ | ||||
|   sourceAst, | ||||
|   sketchPathToNode, | ||||
|   circleOrigin, | ||||
| }: { | ||||
|   sourceAst: Program | ||||
|   sketchPathToNode?: PathToNode | ||||
|   circleOrigin?: [number, number] | ||||
| }) { | ||||
|   let _ast = JSON.parse(JSON.stringify(sourceAst)) | ||||
|  | ||||
|   const _node1 = getNodeFromPath<VariableDeclaration>( | ||||
|     _ast, | ||||
|     sketchPathToNode || [], | ||||
|     'VariableDeclaration' | ||||
|   ) | ||||
|   if (trap(_node1)) return Promise.reject(_node1) | ||||
|  | ||||
|   const tag = findUniqueName(_ast, 'circle') | ||||
|  | ||||
|   const _node2 = getNodeFromPath<VariableDeclaration>( | ||||
|     _ast, | ||||
|     sketchPathToNode || [], | ||||
|     'VariableDeclaration' | ||||
|   ) | ||||
|   if (trap(_node2)) return Promise.reject(_node2) | ||||
|   const startSketchOn = _node2.node?.declarations | ||||
|  | ||||
|   const startSketchOnInit = startSketchOn?.[0]?.init | ||||
|   startSketchOn[0].init = createPipeExpression([ | ||||
|     startSketchOnInit, | ||||
|     ...getCircleCallExpressions({ | ||||
|       center: circleOrigin, | ||||
|       tag, | ||||
|     }), | ||||
|   ]) | ||||
|  | ||||
|   const maybeModdedAst = parse(recast(_ast)) | ||||
|   if (trap(maybeModdedAst)) return Promise.reject(maybeModdedAst) | ||||
|   return Promise.resolve(maybeModdedAst) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Returns AST expressions for this KCL code: | ||||
|  * const yo = startSketchOn('XY') | ||||
|  *   |> startProfileAt([0, 0], %) | ||||
|  *   |> circle([0, 0], 0, %) <- this line | ||||
|  */ | ||||
| export function getCircleCallExpressions({ | ||||
|   center = [0, 0], | ||||
|   radius = 10, | ||||
|   tag, | ||||
| }: { | ||||
|   center?: [number, number] | ||||
|   radius?: number | ||||
|   tag: string | ||||
| }) { | ||||
|   return [ | ||||
|     createCallExpressionStdLib('circle', [ | ||||
|       createArrayExpression([ | ||||
|         createLiteral(roundOff(center[0])), | ||||
|         createLiteral(roundOff(center[1])), | ||||
|       ]), | ||||
|       createLiteral(roundOff(radius)), | ||||
|       createPipeSubstitution(), | ||||
|       createTagDeclarator(tag), | ||||
|     ]), | ||||
|   ] | ||||
| } | ||||
| @ -293,7 +293,8 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = { | ||||
|         status: 'available', | ||||
|         disabled: (state) => | ||||
|           state.matches('Sketch no face') || | ||||
|           state.matches('Sketch.Rectangle tool.Awaiting second corner'), | ||||
|           state.matches('Sketch.Rectangle tool.Awaiting second corner') || | ||||
|           state.matches('Sketch.Circle tool.Awaiting perimeter click'), | ||||
|         title: 'Line', | ||||
|         hotkey: (state) => | ||||
|           state.matches('Sketch.Line tool') ? ['Esc', 'L'] : 'L', | ||||
| @ -355,9 +356,20 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = { | ||||
|       [ | ||||
|         { | ||||
|           id: 'circle-center', | ||||
|           onClick: () => console.error('Center circle not yet implemented'), | ||||
|           onClick: ({ modelingStateMatches, modelingSend }) => | ||||
|             modelingSend({ | ||||
|               type: 'change tool', | ||||
|               data: { | ||||
|                 tool: !modelingStateMatches('Sketch.Circle tool') | ||||
|                   ? 'circle' | ||||
|                   : 'none', | ||||
|               }, | ||||
|             }), | ||||
|           icon: 'circle', | ||||
|           status: 'unavailable', | ||||
|           status: 'available', | ||||
|           disabled: (state) => | ||||
|             !canRectangleTool(state.context) && | ||||
|             !state.matches('Sketch.Circle tool'), | ||||
|           title: 'Center circle', | ||||
|           showTitle: false, | ||||
|           description: 'Start drawing a circle from its center', | ||||
| @ -367,6 +379,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = { | ||||
|               url: 'https://github.com/KittyCAD/modeling-app/issues/1501', | ||||
|             }, | ||||
|           ], | ||||
|           hotkey: (state) => | ||||
|             state.matches('Sketch.Circle tool') ? ['Esc', 'C'] : 'C', | ||||
|           isActive: (state) => state.matches('Sketch.Circle tool'), | ||||
|         }, | ||||
|         { | ||||
|           id: 'circle-three-points', | ||||
|  | ||||
| @ -141,7 +141,12 @@ interface Store { | ||||
|   openPanes: SidebarType[] | ||||
| } | ||||
|  | ||||
| export type SketchTool = 'line' | 'tangentialArc' | 'rectangle' | 'none' | ||||
| export type SketchTool = | ||||
|   | 'line' | ||||
|   | 'tangentialArc' | ||||
|   | 'rectangle' | ||||
|   | 'circle' | ||||
|   | 'none' | ||||
|  | ||||
| export type ModelingMachineEvent = | ||||
|   | { | ||||
| @ -209,6 +214,10 @@ export type ModelingMachineEvent = | ||||
|       type: 'Add rectangle origin' | ||||
|       data: [x: number, y: number] | ||||
|     } | ||||
|   | { | ||||
|       type: 'Add circle origin' | ||||
|       data: [x: number, y: number] | ||||
|     } | ||||
|   | { | ||||
|       type: 'done.invoke.animate-to-face' | 'done.invoke.animate-to-sketch' | ||||
|       data: SketchDetails | ||||
| @ -591,6 +600,8 @@ export const modelingMachine = createMachine( | ||||
|                   Cancel: '#Modeling.Sketch.undo startSketchOn', | ||||
|                 }, | ||||
|               }, | ||||
|  | ||||
|               'new state 1': {}, | ||||
|             }, | ||||
|  | ||||
|             initial: 'Init', | ||||
| @ -796,8 +807,31 @@ export const modelingMachine = createMachine( | ||||
|                 target: 'Tangential arc to', | ||||
|                 cond: 'next is tangential arc', | ||||
|               }, | ||||
|               { | ||||
|                 target: 'Circle tool', | ||||
|                 cond: 'next is circle', | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
|  | ||||
|           'Circle tool': { | ||||
|             entry: 'listen for circle origin', | ||||
|  | ||||
|             states: { | ||||
|               'Awaiting origin': { | ||||
|                 on: { | ||||
|                   'Add circle origin': { | ||||
|                     target: 'Awaiting perimeter click', | ||||
|                     actions: 'set up draft circle', | ||||
|                   }, | ||||
|                 }, | ||||
|               }, | ||||
|  | ||||
|               'Awaiting perimeter click': {}, | ||||
|             }, | ||||
|  | ||||
|             initial: 'Awaiting origin', | ||||
|           }, | ||||
|         }, | ||||
|  | ||||
|         initial: 'Init', | ||||
| @ -1041,6 +1075,14 @@ export const modelingMachine = createMachine( | ||||
|         if ((state?.event as any).data.tool !== 'rectangle') return false | ||||
|         return canRectangleTool({ sketchDetails }) | ||||
|       }, | ||||
|       'next is circle': ({ sketchDetails }, _, { state }) => { | ||||
|         if ((state?.event as any).data.tool !== 'circle') return false | ||||
|         // TODO: Both this an rectangle can currently only be used | ||||
|         // if the sketch is empty. They share limited implementations, | ||||
|         // so I'm using the same cond until we have | ||||
|         // multi-profile sketch support in. | ||||
|         return canRectangleTool({ sketchDetails }) | ||||
|       }, | ||||
|       'next is line': (_, __, { state }) => | ||||
|         (state?.event as any).data.tool === 'line', | ||||
|       'next is none': (_, __, { state }) => | ||||
| @ -1281,13 +1323,18 @@ export const modelingMachine = createMachine( | ||||
|       }, | ||||
|       'set up draft rectangle': ({ sketchDetails }, { data }) => { | ||||
|         if (!sketchDetails || !data) return | ||||
|         sceneEntitiesManager.setupDraftRectangle( | ||||
|           sketchDetails.sketchPathToNode, | ||||
|           sketchDetails.zAxis, | ||||
|           sketchDetails.yAxis, | ||||
|           sketchDetails.origin, | ||||
|           data | ||||
|         ) | ||||
|  | ||||
|         sceneEntitiesManager.setupDraftRectangle(data, sketchDetails) | ||||
|       }, | ||||
|       'listen for circle origin': ({ sketchDetails }) => { | ||||
|         if (!sketchDetails) return | ||||
|         sceneEntitiesManager.setupCircleOriginListener() | ||||
|       }, | ||||
|       'set up draft circle': ({ sketchDetails }, { data }) => { | ||||
|         if (!sketchDetails || !data) return | ||||
|         console.log('setting up draft circle', data) | ||||
|  | ||||
|         sceneEntitiesManager.setupDraftCircle(data, sketchDetails) | ||||
|       }, | ||||
|       'set up draft line without teardown': ({ sketchDetails }) => { | ||||
|         if (!sketchDetails) return | ||||
| @ -1658,7 +1705,10 @@ export function isEditingExistingSketch({ | ||||
|     (item) => | ||||
|       item.type === 'CallExpression' && item.callee.name === 'startProfileAt' | ||||
|   ) | ||||
|   return hasStartProfileAt && pipeExpression.body.length > 2 | ||||
|   const hasCircle = pipeExpression.body.some( | ||||
|     (item) => item.type === 'CallExpression' && item.callee.name === 'circle' | ||||
|   ) | ||||
|   return (hasStartProfileAt && pipeExpression.body.length > 2) || hasCircle | ||||
| } | ||||
|  | ||||
| export function canRectangleTool({ | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	