Nadro/adhoc/center rectangle (#4480)
* fix: big commit, doing this to save work then do a PR cleanup; center rectangle * fix: making a center function for each scenario * fix: reverting the update rectangle code since I have a center rectangle one * fix: does not allow seletcing circle or rectangle tool while selecting a face * chore: adding comment to better read the HTML * fix: cleaning up for PR * fix: pushing broken code for someone to checkout * fix: fixed the typescript issues, removed the as keyword for my center rectangle expressions * fix: removing comment * fix: removed as for type narrowing checks
This commit is contained in:
		@ -141,6 +141,7 @@ export function Toolbar({
 | 
			
		||||
      >
 | 
			
		||||
        {/* A menu item will either be a vertical line break, a button with a dropdown, or a single button */}
 | 
			
		||||
        {currentModeItems.map((maybeIconConfig, i) => {
 | 
			
		||||
          // Vertical Line Break
 | 
			
		||||
          if (maybeIconConfig === 'break') {
 | 
			
		||||
            return (
 | 
			
		||||
              <div
 | 
			
		||||
@ -149,6 +150,7 @@ export function Toolbar({
 | 
			
		||||
              />
 | 
			
		||||
            )
 | 
			
		||||
          } else if (Array.isArray(maybeIconConfig)) {
 | 
			
		||||
            // A button with a dropdown
 | 
			
		||||
            return (
 | 
			
		||||
              <ActionButtonDropdown
 | 
			
		||||
                Element="button"
 | 
			
		||||
@ -215,6 +217,7 @@ export function Toolbar({
 | 
			
		||||
          }
 | 
			
		||||
          const itemConfig = maybeIconConfig
 | 
			
		||||
 | 
			
		||||
          // A single button
 | 
			
		||||
          return (
 | 
			
		||||
            <div className="relative" key={itemConfig.id}>
 | 
			
		||||
              <ActionButton
 | 
			
		||||
 | 
			
		||||
@ -89,6 +89,7 @@ import { EngineCommandManager } from 'lang/std/engineConnection'
 | 
			
		||||
import {
 | 
			
		||||
  getRectangleCallExpressions,
 | 
			
		||||
  updateRectangleSketch,
 | 
			
		||||
  updateCenterRectangleSketch,
 | 
			
		||||
} from 'lib/rectangleTool'
 | 
			
		||||
import { getThemeColorForThreeJs, Themes } from 'lib/theme'
 | 
			
		||||
import { err, reportRejection, trap } from 'lib/trap'
 | 
			
		||||
@ -1043,6 +1044,169 @@ export class SceneEntities {
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  setupDraftCenterRectangle = async (
 | 
			
		||||
    sketchPathToNode: PathToNode,
 | 
			
		||||
    forward: [number, number, number],
 | 
			
		||||
    up: [number, number, number],
 | 
			
		||||
    sketchOrigin: [number, number, number],
 | 
			
		||||
    rectangleOrigin: [x: number, y: number]
 | 
			
		||||
  ) => {
 | 
			
		||||
    let _ast = structuredClone(kclManager.ast)
 | 
			
		||||
    const _node1 = getNodeFromPath<VariableDeclaration>(
 | 
			
		||||
      _ast,
 | 
			
		||||
      sketchPathToNode || [],
 | 
			
		||||
      'VariableDeclaration'
 | 
			
		||||
    )
 | 
			
		||||
    if (trap(_node1)) return Promise.reject(_node1)
 | 
			
		||||
 | 
			
		||||
    // startSketchOn already exists
 | 
			
		||||
    const variableDeclarationName =
 | 
			
		||||
      _node1.node?.declarations?.[0]?.id?.name || ''
 | 
			
		||||
    const startSketchOn = _node1.node?.declarations
 | 
			
		||||
    const startSketchOnInit = startSketchOn?.[0]?.init
 | 
			
		||||
 | 
			
		||||
    const tags: [string, string, string] = [
 | 
			
		||||
      findUniqueName(_ast, 'rectangleSegmentA'),
 | 
			
		||||
      findUniqueName(_ast, 'rectangleSegmentB'),
 | 
			
		||||
      findUniqueName(_ast, 'rectangleSegmentC'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    startSketchOn[0].init = createPipeExpression([
 | 
			
		||||
      startSketchOnInit,
 | 
			
		||||
      ...getRectangleCallExpressions(rectangleOrigin, tags),
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
    let _recastAst = parse(recast(_ast))
 | 
			
		||||
    if (trap(_recastAst)) return Promise.reject(_recastAst)
 | 
			
		||||
    _ast = _recastAst
 | 
			
		||||
 | 
			
		||||
    const { programMemoryOverride, truncatedAst } = await this.setupSketch({
 | 
			
		||||
      sketchPathToNode,
 | 
			
		||||
      forward,
 | 
			
		||||
      up,
 | 
			
		||||
      position: sketchOrigin,
 | 
			
		||||
      maybeModdedAst: _ast,
 | 
			
		||||
      draftExpressionsIndices: { start: 0, end: 3 },
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    sceneInfra.setCallbacks({
 | 
			
		||||
      onMove: async (args) => {
 | 
			
		||||
        // Update the width and height of the draft rectangle
 | 
			
		||||
        const pathToNodeTwo = structuredClone(sketchPathToNode)
 | 
			
		||||
        pathToNodeTwo[1][0] = 0
 | 
			
		||||
 | 
			
		||||
        const _node = getNodeFromPath<VariableDeclaration>(
 | 
			
		||||
          truncatedAst,
 | 
			
		||||
          pathToNodeTwo || [],
 | 
			
		||||
          'VariableDeclaration'
 | 
			
		||||
        )
 | 
			
		||||
        if (trap(_node)) return Promise.reject(_node)
 | 
			
		||||
        const sketchInit = _node.node?.declarations?.[0]?.init
 | 
			
		||||
 | 
			
		||||
        const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
 | 
			
		||||
        const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
 | 
			
		||||
 | 
			
		||||
        if (sketchInit.type === 'PipeExpression') {
 | 
			
		||||
          updateCenterRectangleSketch(
 | 
			
		||||
            sketchInit,
 | 
			
		||||
            x,
 | 
			
		||||
            y,
 | 
			
		||||
            tags[0],
 | 
			
		||||
            rectangleOrigin[0],
 | 
			
		||||
            rectangleOrigin[1]
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const { execState } = await executeAst({
 | 
			
		||||
          ast: truncatedAst,
 | 
			
		||||
          useFakeExecutor: true,
 | 
			
		||||
          engineCommandManager: this.engineCommandManager,
 | 
			
		||||
          programMemoryOverride,
 | 
			
		||||
          idGenerator: kclManager.execState.idGenerator,
 | 
			
		||||
        })
 | 
			
		||||
        const programMemory = execState.memory
 | 
			
		||||
        this.sceneProgramMemory = programMemory
 | 
			
		||||
        const sketch = sketchFromKclValue(
 | 
			
		||||
          programMemory.get(variableDeclarationName),
 | 
			
		||||
          variableDeclarationName
 | 
			
		||||
        )
 | 
			
		||||
        if (err(sketch)) return Promise.reject(sketch)
 | 
			
		||||
        const sgPaths = sketch.paths
 | 
			
		||||
        const orthoFactor = orthoScale(sceneInfra.camControls.camera)
 | 
			
		||||
 | 
			
		||||
        this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
 | 
			
		||||
        sgPaths.forEach((seg, index) =>
 | 
			
		||||
          this.updateSegment(seg, index, 0, _ast, orthoFactor, sketch)
 | 
			
		||||
        )
 | 
			
		||||
      },
 | 
			
		||||
      onClick: async (args) => {
 | 
			
		||||
        // If there is a valid camera interaction that matches, do that instead
 | 
			
		||||
        const interaction = sceneInfra.camControls.getInteractionType(
 | 
			
		||||
          args.mouseEvent
 | 
			
		||||
        )
 | 
			
		||||
        if (interaction !== 'none') return
 | 
			
		||||
        // Commit the rectangle to the full AST/code and return to sketch.idle
 | 
			
		||||
        const cornerPoint = args.intersectionPoint?.twoD
 | 
			
		||||
        if (!cornerPoint || args.mouseEvent.button !== 0) return
 | 
			
		||||
 | 
			
		||||
        const x = roundOff((cornerPoint.x || 0) - rectangleOrigin[0])
 | 
			
		||||
        const y = roundOff((cornerPoint.y || 0) - rectangleOrigin[1])
 | 
			
		||||
 | 
			
		||||
        const _node = getNodeFromPath<VariableDeclaration>(
 | 
			
		||||
          _ast,
 | 
			
		||||
          sketchPathToNode || [],
 | 
			
		||||
          'VariableDeclaration'
 | 
			
		||||
        )
 | 
			
		||||
        if (trap(_node)) return
 | 
			
		||||
        const sketchInit = _node.node?.declarations?.[0]?.init
 | 
			
		||||
 | 
			
		||||
        if (sketchInit.type === 'PipeExpression') {
 | 
			
		||||
          updateCenterRectangleSketch(
 | 
			
		||||
            sketchInit,
 | 
			
		||||
            x,
 | 
			
		||||
            y,
 | 
			
		||||
            tags[0],
 | 
			
		||||
            rectangleOrigin[0],
 | 
			
		||||
            rectangleOrigin[1]
 | 
			
		||||
          )
 | 
			
		||||
 | 
			
		||||
          let _recastAst = parse(recast(_ast))
 | 
			
		||||
          if (trap(_recastAst)) return
 | 
			
		||||
          _ast = _recastAst
 | 
			
		||||
 | 
			
		||||
          // Update the primary AST and unequip the rectangle tool
 | 
			
		||||
          await kclManager.executeAstMock(_ast)
 | 
			
		||||
          sceneInfra.modelingSend({ type: 'Finish center rectangle' })
 | 
			
		||||
 | 
			
		||||
          const { execState } = await executeAst({
 | 
			
		||||
            ast: _ast,
 | 
			
		||||
            useFakeExecutor: true,
 | 
			
		||||
            engineCommandManager: this.engineCommandManager,
 | 
			
		||||
            programMemoryOverride,
 | 
			
		||||
            idGenerator: kclManager.execState.idGenerator,
 | 
			
		||||
          })
 | 
			
		||||
          const programMemory = execState.memory
 | 
			
		||||
 | 
			
		||||
          // Prepare to update the THREEjs scene
 | 
			
		||||
          this.sceneProgramMemory = programMemory
 | 
			
		||||
          const sketch = sketchFromKclValue(
 | 
			
		||||
            programMemory.get(variableDeclarationName),
 | 
			
		||||
            variableDeclarationName
 | 
			
		||||
          )
 | 
			
		||||
          if (err(sketch)) return
 | 
			
		||||
          const sgPaths = sketch.paths
 | 
			
		||||
          const orthoFactor = orthoScale(sceneInfra.camControls.camera)
 | 
			
		||||
 | 
			
		||||
          // Update the starting segment of the THREEjs scene
 | 
			
		||||
          this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
 | 
			
		||||
          // Update the rest of the segments of the THREEjs scene
 | 
			
		||||
          sgPaths.forEach((seg, index) =>
 | 
			
		||||
            this.updateSegment(seg, index, 0, _ast, orthoFactor, sketch)
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  setupDraftCircle = async (
 | 
			
		||||
    sketchPathToNode: PathToNode,
 | 
			
		||||
    forward: [number, number, number],
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import {
 | 
			
		||||
  VariableDeclarator,
 | 
			
		||||
  Expr,
 | 
			
		||||
  Literal,
 | 
			
		||||
  LiteralValue,
 | 
			
		||||
  PipeSubstitution,
 | 
			
		||||
  Identifier,
 | 
			
		||||
  ArrayExpression,
 | 
			
		||||
@ -573,7 +574,7 @@ export function splitPathAtPipeExpression(pathToNode: PathToNode): {
 | 
			
		||||
  return splitPathAtPipeExpression(pathToNode.slice(0, -1))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createLiteral(value: string | number): Node<Literal> {
 | 
			
		||||
export function createLiteral(value: LiteralValue): Node<Literal> {
 | 
			
		||||
  return {
 | 
			
		||||
    type: 'Literal',
 | 
			
		||||
    start: 0,
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,12 @@
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { Program, PathToNode } from './wasm'
 | 
			
		||||
import {
 | 
			
		||||
  Program,
 | 
			
		||||
  PathToNode,
 | 
			
		||||
  CallExpression,
 | 
			
		||||
  Literal,
 | 
			
		||||
  ArrayExpression,
 | 
			
		||||
  BinaryExpression,
 | 
			
		||||
} from './wasm'
 | 
			
		||||
import { getNodeFromPath } from './queryAst'
 | 
			
		||||
import { ArtifactGraph, filterArtifacts } from 'lang/std/artifactGraph'
 | 
			
		||||
import { isOverlap } from 'lib/utils'
 | 
			
		||||
@ -84,3 +91,19 @@ export function isCursorInSketchCommandRange(
 | 
			
		||||
        ([, artifact]) => artifact.type === 'path'
 | 
			
		||||
      )?.[0] || false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isCallExpression(e: any): e is CallExpression {
 | 
			
		||||
  return e && e.type === 'CallExpression'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isArrayExpression(e: any): e is ArrayExpression {
 | 
			
		||||
  return e && e.type === 'ArrayExpression'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isLiteral(e: any): e is Literal {
 | 
			
		||||
  return e && e.type === 'Literal'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isBinaryExpression(e: any): e is BinaryExpression {
 | 
			
		||||
  return e && e.type === 'BinaryExpression'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -62,6 +62,7 @@ export type { CallExpression } from '../wasm-lib/kcl/bindings/CallExpression'
 | 
			
		||||
export type { VariableDeclarator } from '../wasm-lib/kcl/bindings/VariableDeclarator'
 | 
			
		||||
export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
 | 
			
		||||
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
 | 
			
		||||
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
 | 
			
		||||
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
 | 
			
		||||
 | 
			
		||||
export type SyntaxType =
 | 
			
		||||
@ -81,6 +82,7 @@ export type SyntaxType =
 | 
			
		||||
  | 'PipeExpression'
 | 
			
		||||
  | 'PipeSubstitution'
 | 
			
		||||
  | 'Literal'
 | 
			
		||||
  | 'LiteralValue'
 | 
			
		||||
  | 'NonCodeNode'
 | 
			
		||||
  | 'UnaryExpression'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,8 +9,16 @@ import {
 | 
			
		||||
  createUnaryExpression,
 | 
			
		||||
} from 'lang/modifyAst'
 | 
			
		||||
import { ArrayExpression, CallExpression, PipeExpression } from 'lang/wasm'
 | 
			
		||||
import { roundOff } from 'lib/utils'
 | 
			
		||||
import {
 | 
			
		||||
  isCallExpression,
 | 
			
		||||
  isArrayExpression,
 | 
			
		||||
  isLiteral,
 | 
			
		||||
  isBinaryExpression,
 | 
			
		||||
} from 'lang/util'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * It does not create the startSketchOn and it does not create the startProfileAt.
 | 
			
		||||
 * Returns AST expressions for this KCL code:
 | 
			
		||||
 * const yo = startSketchOn('XY')
 | 
			
		||||
 *  |> startProfileAt([0, 0], %)
 | 
			
		||||
@ -92,3 +100,69 @@ export function updateRectangleSketch(
 | 
			
		||||
    createLiteral(Math.abs(y)), // This will be the height of the rectangle
 | 
			
		||||
  ])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Mutates the pipeExpression to update the center rectangle sketch
 | 
			
		||||
 * @param pipeExpression
 | 
			
		||||
 * @param x
 | 
			
		||||
 * @param y
 | 
			
		||||
 * @param tag
 | 
			
		||||
 */
 | 
			
		||||
export function updateCenterRectangleSketch(
 | 
			
		||||
  pipeExpression: PipeExpression,
 | 
			
		||||
  deltaX: number,
 | 
			
		||||
  deltaY: number,
 | 
			
		||||
  tag: string,
 | 
			
		||||
  originX: number,
 | 
			
		||||
  originY: number
 | 
			
		||||
) {
 | 
			
		||||
  let startX = originX - Math.abs(deltaX)
 | 
			
		||||
  let startY = originY - Math.abs(deltaY)
 | 
			
		||||
 | 
			
		||||
  // pipeExpression.body[1] is startProfileAt
 | 
			
		||||
  let callExpression = pipeExpression.body[1]
 | 
			
		||||
  if (isCallExpression(callExpression)) {
 | 
			
		||||
    const arrayExpression = callExpression.arguments[0]
 | 
			
		||||
    if (isArrayExpression(arrayExpression)) {
 | 
			
		||||
      callExpression.arguments[0] = createArrayExpression([
 | 
			
		||||
        createLiteral(roundOff(startX)),
 | 
			
		||||
        createLiteral(roundOff(startY)),
 | 
			
		||||
      ])
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const twoX = deltaX * 2
 | 
			
		||||
  const twoY = deltaY * 2
 | 
			
		||||
 | 
			
		||||
  callExpression = pipeExpression.body[2]
 | 
			
		||||
  if (isCallExpression(callExpression)) {
 | 
			
		||||
    const arrayExpression = callExpression.arguments[0]
 | 
			
		||||
    if (isArrayExpression(arrayExpression)) {
 | 
			
		||||
      const literal = arrayExpression.elements[0]
 | 
			
		||||
      if (isLiteral(literal)) {
 | 
			
		||||
        callExpression.arguments[0] = createArrayExpression([
 | 
			
		||||
          createLiteral(literal.value),
 | 
			
		||||
          createLiteral(Math.abs(twoX)),
 | 
			
		||||
        ])
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  callExpression = pipeExpression.body[3]
 | 
			
		||||
  if (isCallExpression(callExpression)) {
 | 
			
		||||
    const arrayExpression = callExpression.arguments[0]
 | 
			
		||||
    if (isArrayExpression(arrayExpression)) {
 | 
			
		||||
      const binaryExpression = arrayExpression.elements[0]
 | 
			
		||||
      if (isBinaryExpression(binaryExpression)) {
 | 
			
		||||
        callExpression.arguments[0] = createArrayExpression([
 | 
			
		||||
          createBinaryExpression([
 | 
			
		||||
            createCallExpressionStdLib('segAng', [createIdentifier(tag)]),
 | 
			
		||||
            binaryExpression.operator,
 | 
			
		||||
            createLiteral(90),
 | 
			
		||||
          ]), // 90 offset from the previous line
 | 
			
		||||
          createLiteral(Math.abs(twoY)), // This will be the height of the rectangle
 | 
			
		||||
        ])
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -407,8 +407,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
          status: 'available',
 | 
			
		||||
          title: 'Center circle',
 | 
			
		||||
          disabled: (state) =>
 | 
			
		||||
            !canRectangleOrCircleTool(state.context) &&
 | 
			
		||||
            !state.matches({ Sketch: 'Circle tool' }),
 | 
			
		||||
            state.matches('Sketch no face') ||
 | 
			
		||||
            (!canRectangleOrCircleTool(state.context) &&
 | 
			
		||||
              !state.matches({ Sketch: 'Circle tool' })),
 | 
			
		||||
          isActive: (state) => state.matches({ Sketch: 'Circle tool' }),
 | 
			
		||||
          hotkey: (state) =>
 | 
			
		||||
            state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
 | 
			
		||||
@ -448,8 +449,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
          icon: 'rectangle',
 | 
			
		||||
          status: 'available',
 | 
			
		||||
          disabled: (state) =>
 | 
			
		||||
            !canRectangleOrCircleTool(state.context) &&
 | 
			
		||||
            !state.matches({ Sketch: 'Rectangle tool' }),
 | 
			
		||||
            state.matches('Sketch no face') ||
 | 
			
		||||
            (!canRectangleOrCircleTool(state.context) &&
 | 
			
		||||
              !state.matches({ Sketch: 'Rectangle tool' })),
 | 
			
		||||
          title: 'Corner rectangle',
 | 
			
		||||
          hotkey: (state) =>
 | 
			
		||||
            state.matches({ Sketch: 'Rectangle tool' }) ? ['Esc', 'R'] : 'R',
 | 
			
		||||
@ -459,13 +461,33 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          id: 'center-rectangle',
 | 
			
		||||
          onClick: () => console.error('Center rectangle not yet implemented'),
 | 
			
		||||
          icon: 'rectangle',
 | 
			
		||||
          status: 'unavailable',
 | 
			
		||||
          onClick: ({ modelingState, modelingSend }) =>
 | 
			
		||||
            modelingSend({
 | 
			
		||||
              type: 'change tool',
 | 
			
		||||
              data: {
 | 
			
		||||
                tool: !modelingState.matches({
 | 
			
		||||
                  Sketch: 'Center Rectangle tool',
 | 
			
		||||
                })
 | 
			
		||||
                  ? 'center rectangle'
 | 
			
		||||
                  : 'none',
 | 
			
		||||
              },
 | 
			
		||||
            }),
 | 
			
		||||
          icon: 'arc',
 | 
			
		||||
          status: 'available',
 | 
			
		||||
          disabled: (state) =>
 | 
			
		||||
            state.matches('Sketch no face') ||
 | 
			
		||||
            (!canRectangleOrCircleTool(state.context) &&
 | 
			
		||||
              !state.matches({ Sketch: 'Center Rectangle tool' })),
 | 
			
		||||
          title: 'Center rectangle',
 | 
			
		||||
          showTitle: false,
 | 
			
		||||
          hotkey: (state) =>
 | 
			
		||||
            state.matches({ Sketch: 'Center Rectangle tool' })
 | 
			
		||||
              ? ['Esc', 'C']
 | 
			
		||||
              : 'C',
 | 
			
		||||
          description: 'Start drawing a rectangle from its center',
 | 
			
		||||
          links: [],
 | 
			
		||||
          isActive: (state) => {
 | 
			
		||||
            return state.matches({ Sketch: 'Center Rectangle tool' })
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      {
 | 
			
		||||
 | 
			
		||||
@ -184,6 +184,7 @@ export type SketchTool =
 | 
			
		||||
  | 'line'
 | 
			
		||||
  | 'tangentialArc'
 | 
			
		||||
  | 'rectangle'
 | 
			
		||||
  | 'center rectangle'
 | 
			
		||||
  | 'circle'
 | 
			
		||||
  | 'none'
 | 
			
		||||
 | 
			
		||||
@ -238,6 +239,10 @@ export type ModelingMachineEvent =
 | 
			
		||||
      type: 'Add rectangle origin'
 | 
			
		||||
      data: [x: number, y: number]
 | 
			
		||||
    }
 | 
			
		||||
  | {
 | 
			
		||||
      type: 'Add center rectangle origin'
 | 
			
		||||
      data: [x: number, y: number]
 | 
			
		||||
    }
 | 
			
		||||
  | {
 | 
			
		||||
      type: 'Add circle origin'
 | 
			
		||||
      data: [x: number, y: number]
 | 
			
		||||
@ -278,6 +283,7 @@ export type ModelingMachineEvent =
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  | { type: 'Finish rectangle' }
 | 
			
		||||
  | { type: 'Finish center rectangle' }
 | 
			
		||||
  | { type: 'Finish circle' }
 | 
			
		||||
  | { type: 'Artifact graph populated' }
 | 
			
		||||
  | { type: 'Artifact graph emptied' }
 | 
			
		||||
@ -506,6 +512,9 @@ export const modelingMachine = setup({
 | 
			
		||||
    'next is rectangle': ({ context: { sketchDetails, currentTool } }) =>
 | 
			
		||||
      currentTool === 'rectangle' &&
 | 
			
		||||
      canRectangleOrCircleTool({ sketchDetails }),
 | 
			
		||||
    'next is center rectangle': ({ context: { sketchDetails, currentTool } }) =>
 | 
			
		||||
      currentTool === 'center rectangle' &&
 | 
			
		||||
      canRectangleOrCircleTool({ sketchDetails }),
 | 
			
		||||
    'next is circle': ({ context: { sketchDetails, currentTool } }) =>
 | 
			
		||||
      currentTool === 'circle' && canRectangleOrCircleTool({ sketchDetails }),
 | 
			
		||||
    'next is line': ({ context }) => context.currentTool === 'line',
 | 
			
		||||
@ -806,6 +815,26 @@ export const modelingMachine = setup({
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    'listen for center rectangle origin': ({ context: { sketchDetails } }) => {
 | 
			
		||||
      if (!sketchDetails) return
 | 
			
		||||
      // setupNoPointsListener has the code for startProfileAt onClick
 | 
			
		||||
      sceneEntitiesManager.setupNoPointsListener({
 | 
			
		||||
        sketchDetails,
 | 
			
		||||
        afterClick: (args) => {
 | 
			
		||||
          const twoD = args.intersectionPoint?.twoD
 | 
			
		||||
          if (twoD) {
 | 
			
		||||
            sceneInfra.modelingSend({
 | 
			
		||||
              type: 'Add center rectangle origin',
 | 
			
		||||
              data: [twoD.x, twoD.y],
 | 
			
		||||
            })
 | 
			
		||||
          } else {
 | 
			
		||||
            console.error('No intersection point found')
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    'listen for circle origin': ({ context: { sketchDetails } }) => {
 | 
			
		||||
      if (!sketchDetails) return
 | 
			
		||||
      sceneEntitiesManager.createIntersectionPlane()
 | 
			
		||||
@ -859,6 +888,21 @@ export const modelingMachine = setup({
 | 
			
		||||
          return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
 | 
			
		||||
        })
 | 
			
		||||
    },
 | 
			
		||||
    'set up draft center rectangle': ({
 | 
			
		||||
      context: { sketchDetails },
 | 
			
		||||
      event,
 | 
			
		||||
    }) => {
 | 
			
		||||
      if (event.type !== 'Add center rectangle origin') return
 | 
			
		||||
      if (!sketchDetails || !event.data) return
 | 
			
		||||
      // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | 
			
		||||
      sceneEntitiesManager.setupDraftCenterRectangle(
 | 
			
		||||
        sketchDetails.sketchPathToNode,
 | 
			
		||||
        sketchDetails.zAxis,
 | 
			
		||||
        sketchDetails.yAxis,
 | 
			
		||||
        sketchDetails.origin,
 | 
			
		||||
        event.data
 | 
			
		||||
      )
 | 
			
		||||
    },
 | 
			
		||||
    'set up draft circle': ({ context: { sketchDetails }, event }) => {
 | 
			
		||||
      if (event.type !== 'Add circle origin') return
 | 
			
		||||
      if (!sketchDetails || !event.data) return
 | 
			
		||||
@ -1822,6 +1866,40 @@ export const modelingMachine = setup({
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'Center Rectangle tool': {
 | 
			
		||||
          entry: ['listen for center rectangle origin'],
 | 
			
		||||
 | 
			
		||||
          states: {
 | 
			
		||||
            'Awaiting corner': {
 | 
			
		||||
              on: {
 | 
			
		||||
                'Finish center rectangle': 'Finished Center Rectangle',
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            'Awaiting origin': {
 | 
			
		||||
              on: {
 | 
			
		||||
                'Add center rectangle origin': {
 | 
			
		||||
                  target: 'Awaiting corner',
 | 
			
		||||
                  // TODO
 | 
			
		||||
                  actions: 'set up draft center rectangle',
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            'Finished Center Rectangle': {
 | 
			
		||||
              always: '#Modeling.Sketch.SketchIdle',
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
 | 
			
		||||
          initial: 'Awaiting origin',
 | 
			
		||||
 | 
			
		||||
          on: {
 | 
			
		||||
            'change tool': {
 | 
			
		||||
              target: 'Change Tool',
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'clean slate': {
 | 
			
		||||
          always: 'SketchIdle',
 | 
			
		||||
        },
 | 
			
		||||
@ -2015,6 +2093,10 @@ export const modelingMachine = setup({
 | 
			
		||||
              target: 'Circle tool',
 | 
			
		||||
              guard: 'next is circle',
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              target: 'Center Rectangle tool',
 | 
			
		||||
              guard: 'next is center rectangle',
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
 | 
			
		||||
          entry: 'assign tool in context',
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user