Merge branch 'main' into coredump-clientstate
This commit is contained in:
		@ -2440,6 +2440,293 @@ test('Extrude from command bar selects extrude line after', async ({
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test.describe('Testing constraints', () => {
 | 
			
		||||
  test.describe('Test ABS distance constraint', () => {
 | 
			
		||||
    const cases = [
 | 
			
		||||
      {
 | 
			
		||||
        testName: 'Add variable',
 | 
			
		||||
        addVariable: true,
 | 
			
		||||
        constraint: 'ABS X',
 | 
			
		||||
        value: 'xDis001, 61.34',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        testName: 'No variable',
 | 
			
		||||
        addVariable: false,
 | 
			
		||||
        constraint: 'ABS X',
 | 
			
		||||
        value: '154.9, 61.34',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        testName: 'Add variable',
 | 
			
		||||
        addVariable: true,
 | 
			
		||||
        constraint: 'ABS Y',
 | 
			
		||||
        value: '154.9, yDis001',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        testName: 'No variable',
 | 
			
		||||
        addVariable: false,
 | 
			
		||||
        constraint: 'ABS Y',
 | 
			
		||||
        value: '154.9, 61.34',
 | 
			
		||||
      },
 | 
			
		||||
    ] as const
 | 
			
		||||
    for (const { testName, addVariable, value, constraint } of cases) {
 | 
			
		||||
      test(`${constraint} - ${testName}`, async ({ page }) => {
 | 
			
		||||
        await page.addInitScript(async () => {
 | 
			
		||||
          localStorage.setItem(
 | 
			
		||||
            'persistCode',
 | 
			
		||||
            `const yo = 5
 | 
			
		||||
const part001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([-7.54, -26.74], %)
 | 
			
		||||
  |> line([74.36, 130.4], %)
 | 
			
		||||
  |> line([78.92, -120.11], %)
 | 
			
		||||
  |> line([9.16, 77.79], %)
 | 
			
		||||
  |> line([41.19, 28.97], %)
 | 
			
		||||
const part002 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([299.05, 231.45], %)
 | 
			
		||||
  |> xLine(-425.34, %, 'seg-what')
 | 
			
		||||
  |> yLine(-264.06, %)
 | 
			
		||||
  |> xLine(segLen('seg-what', %), %)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)`
 | 
			
		||||
          )
 | 
			
		||||
        })
 | 
			
		||||
        const u = await getUtils(page)
 | 
			
		||||
        await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
        await page.goto('/')
 | 
			
		||||
        await u.waitForAuthSkipAppStart()
 | 
			
		||||
 | 
			
		||||
        await page.getByText('line([74.36, 130.4], %)').click()
 | 
			
		||||
        await page.getByRole('button', { name: 'Edit Sketch' }).click()
 | 
			
		||||
 | 
			
		||||
        const [line3] = await Promise.all([
 | 
			
		||||
          u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
 | 
			
		||||
        ])
 | 
			
		||||
 | 
			
		||||
        if (constraint === 'ABS X') {
 | 
			
		||||
          await page.mouse.click(600, 130)
 | 
			
		||||
        } else {
 | 
			
		||||
          await page.mouse.click(900, 250)
 | 
			
		||||
        }
 | 
			
		||||
        await page.keyboard.down('Shift')
 | 
			
		||||
        await page.mouse.click(line3.x, line3.y)
 | 
			
		||||
        await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
 | 
			
		||||
        await page.keyboard.up('Shift')
 | 
			
		||||
        await page
 | 
			
		||||
          .getByRole('button', {
 | 
			
		||||
            name: 'Constrain',
 | 
			
		||||
          })
 | 
			
		||||
          .click()
 | 
			
		||||
        await page
 | 
			
		||||
          .getByRole('button', { name: constraint, exact: true })
 | 
			
		||||
          .click()
 | 
			
		||||
 | 
			
		||||
        const createNewVariableCheckbox = page.getByTestId(
 | 
			
		||||
          'create-new-variable-checkbox'
 | 
			
		||||
        )
 | 
			
		||||
        const isChecked = await createNewVariableCheckbox.isChecked()
 | 
			
		||||
        ;((isChecked && !addVariable) || (!isChecked && addVariable)) &&
 | 
			
		||||
          (await createNewVariableCheckbox.click())
 | 
			
		||||
 | 
			
		||||
        await page
 | 
			
		||||
          .getByRole('button', { name: 'Add constraining value' })
 | 
			
		||||
          .click()
 | 
			
		||||
 | 
			
		||||
        // checking activeLines assures the cursors are where they should be
 | 
			
		||||
        const codeAfter = [`|> lineTo([${value}], %)`]
 | 
			
		||||
 | 
			
		||||
        const activeLinesContent = await page.locator('.cm-activeLine').all()
 | 
			
		||||
        await Promise.all(
 | 
			
		||||
          activeLinesContent.map(async (line, i) => {
 | 
			
		||||
            await expect(page.locator('.cm-content')).toContainText(
 | 
			
		||||
              codeAfter[i]
 | 
			
		||||
            )
 | 
			
		||||
            // if the code is an active line then the cursor should be on that line
 | 
			
		||||
            await expect(line).toHaveText(codeAfter[i])
 | 
			
		||||
          })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
 | 
			
		||||
        await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  test.describe('Test Angle constraint double segment selection', () => {
 | 
			
		||||
    const cases = [
 | 
			
		||||
      {
 | 
			
		||||
        testName: 'Add variable',
 | 
			
		||||
        addVariable: true,
 | 
			
		||||
        axisSelect: false,
 | 
			
		||||
        value: "segAng('seg01', %) + angle001",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        testName: 'No variable',
 | 
			
		||||
        addVariable: false,
 | 
			
		||||
        axisSelect: false,
 | 
			
		||||
        value: "segAng('seg01', %) + 22.69",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        testName: 'Add variable, selecting axis',
 | 
			
		||||
        addVariable: true,
 | 
			
		||||
        axisSelect: true,
 | 
			
		||||
        value: 'QUARTER_TURN - angle001',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        testName: 'No variable, selecting axis',
 | 
			
		||||
        addVariable: false,
 | 
			
		||||
        axisSelect: true,
 | 
			
		||||
        value: 'QUARTER_TURN - 7',
 | 
			
		||||
      },
 | 
			
		||||
    ] as const
 | 
			
		||||
    for (const { testName, addVariable, value, axisSelect } of cases) {
 | 
			
		||||
      test(`${testName}`, async ({ page }) => {
 | 
			
		||||
        await page.addInitScript(async () => {
 | 
			
		||||
          localStorage.setItem(
 | 
			
		||||
            'persistCode',
 | 
			
		||||
            `const yo = 5
 | 
			
		||||
const part001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([-7.54, -26.74], %)
 | 
			
		||||
  |> line([74.36, 130.4], %)
 | 
			
		||||
  |> line([78.92, -120.11], %)
 | 
			
		||||
  |> line([9.16, 77.79], %)
 | 
			
		||||
  |> line([41.19, 28.97], %)
 | 
			
		||||
const part002 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([299.05, 231.45], %)
 | 
			
		||||
  |> xLine(-425.34, %, 'seg-what')
 | 
			
		||||
  |> yLine(-264.06, %)
 | 
			
		||||
  |> xLine(segLen('seg-what', %), %)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)`
 | 
			
		||||
          )
 | 
			
		||||
        })
 | 
			
		||||
        const u = await getUtils(page)
 | 
			
		||||
        await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
        await page.goto('/')
 | 
			
		||||
        await u.waitForAuthSkipAppStart()
 | 
			
		||||
 | 
			
		||||
        await page.getByText('line([74.36, 130.4], %)').click()
 | 
			
		||||
        await page.getByRole('button', { name: 'Edit Sketch' }).click()
 | 
			
		||||
 | 
			
		||||
        const [line1, line3] = await Promise.all([
 | 
			
		||||
          u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`),
 | 
			
		||||
          u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
 | 
			
		||||
        ])
 | 
			
		||||
 | 
			
		||||
        if (axisSelect) {
 | 
			
		||||
          await page.mouse.click(600, 130)
 | 
			
		||||
        } else {
 | 
			
		||||
          await page.mouse.click(line1.x, line1.y)
 | 
			
		||||
        }
 | 
			
		||||
        await page.keyboard.down('Shift')
 | 
			
		||||
        await page.mouse.click(line3.x, line3.y)
 | 
			
		||||
        await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
 | 
			
		||||
        await page.keyboard.up('Shift')
 | 
			
		||||
        await page
 | 
			
		||||
          .getByRole('button', {
 | 
			
		||||
            name: 'Constrain',
 | 
			
		||||
          })
 | 
			
		||||
          .click()
 | 
			
		||||
        await page.getByTestId('angle').click()
 | 
			
		||||
 | 
			
		||||
        const createNewVariableCheckbox = page.getByTestId(
 | 
			
		||||
          'create-new-variable-checkbox'
 | 
			
		||||
        )
 | 
			
		||||
        const isChecked = await createNewVariableCheckbox.isChecked()
 | 
			
		||||
        ;((isChecked && !addVariable) || (!isChecked && addVariable)) &&
 | 
			
		||||
          (await createNewVariableCheckbox.click())
 | 
			
		||||
 | 
			
		||||
        await page
 | 
			
		||||
          .getByRole('button', { name: 'Add constraining value' })
 | 
			
		||||
          .click()
 | 
			
		||||
 | 
			
		||||
        // checking activeLines assures the cursors are where they should be
 | 
			
		||||
        const codeAfter = [
 | 
			
		||||
          "|> line([74.36, 130.4], %, 'seg01')",
 | 
			
		||||
          `|> angledLine([${value}, 78.33], %)`,
 | 
			
		||||
        ]
 | 
			
		||||
        if (axisSelect) codeAfter.shift()
 | 
			
		||||
 | 
			
		||||
        const activeLinesContent = await page.locator('.cm-activeLine').all()
 | 
			
		||||
        await Promise.all(
 | 
			
		||||
          activeLinesContent.map(async (line, i) => {
 | 
			
		||||
            await expect(page.locator('.cm-content')).toContainText(
 | 
			
		||||
              codeAfter[i]
 | 
			
		||||
            )
 | 
			
		||||
            // if the code is an active line then the cursor should be on that line
 | 
			
		||||
            await expect(line).toHaveText(codeAfter[i])
 | 
			
		||||
          })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
 | 
			
		||||
        await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  test.describe('Test Angle constraint single selection', () => {
 | 
			
		||||
    const cases = [
 | 
			
		||||
      {
 | 
			
		||||
        testName: 'Add variable',
 | 
			
		||||
        addVariable: true,
 | 
			
		||||
        value: 'angle001',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        testName: 'No variable',
 | 
			
		||||
        addVariable: false,
 | 
			
		||||
        value: '83',
 | 
			
		||||
      },
 | 
			
		||||
    ] as const
 | 
			
		||||
    for (const { testName, addVariable, value } of cases) {
 | 
			
		||||
      test(`${testName}`, async ({ page }) => {
 | 
			
		||||
        await page.addInitScript(async () => {
 | 
			
		||||
          localStorage.setItem(
 | 
			
		||||
            'persistCode',
 | 
			
		||||
            `const yo = 5
 | 
			
		||||
    const part001 = startSketchOn('XZ')
 | 
			
		||||
      |> startProfileAt([-7.54, -26.74], %)
 | 
			
		||||
      |> line([74.36, 130.4], %)
 | 
			
		||||
      |> line([78.92, -120.11], %)
 | 
			
		||||
      |> line([9.16, 77.79], %)
 | 
			
		||||
      |> line([41.19, 28.97], %)
 | 
			
		||||
    const part002 = startSketchOn('XZ')
 | 
			
		||||
      |> startProfileAt([299.05, 231.45], %)
 | 
			
		||||
      |> xLine(-425.34, %, 'seg-what')
 | 
			
		||||
      |> yLine(-264.06, %)
 | 
			
		||||
      |> xLine(segLen('seg-what', %), %)
 | 
			
		||||
      |> lineTo([profileStartX(%), profileStartY(%)], %)`
 | 
			
		||||
          )
 | 
			
		||||
        })
 | 
			
		||||
        const u = await getUtils(page)
 | 
			
		||||
        await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
        await page.goto('/')
 | 
			
		||||
        await u.waitForAuthSkipAppStart()
 | 
			
		||||
 | 
			
		||||
        await page.getByText('line([74.36, 130.4], %)').click()
 | 
			
		||||
        await page.getByRole('button', { name: 'Edit Sketch' }).click()
 | 
			
		||||
 | 
			
		||||
        const line3 = await u.getSegmentBodyCoords(
 | 
			
		||||
          `[data-overlay-index="${2}"]`
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        await page.mouse.click(line3.x, line3.y)
 | 
			
		||||
        await page
 | 
			
		||||
          .getByRole('button', {
 | 
			
		||||
            name: 'Constrain',
 | 
			
		||||
          })
 | 
			
		||||
          .click()
 | 
			
		||||
        await page.getByTestId('angle').click()
 | 
			
		||||
 | 
			
		||||
        if (!addVariable) {
 | 
			
		||||
          await page.getByTestId('create-new-variable-checkbox').click()
 | 
			
		||||
        }
 | 
			
		||||
        await page
 | 
			
		||||
          .getByRole('button', { name: 'Add constraining value' })
 | 
			
		||||
          .click()
 | 
			
		||||
 | 
			
		||||
        const changedCode = `|> angledLine([${value}, 78.33], %)`
 | 
			
		||||
        await expect(page.locator('.cm-content')).toContainText(changedCode)
 | 
			
		||||
        // checking active assures the cursor is where it should be
 | 
			
		||||
        await expect(page.locator('.cm-activeLine')).toHaveText(changedCode)
 | 
			
		||||
 | 
			
		||||
        // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
 | 
			
		||||
        await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  test.describe('Many segments - no modal constraints', () => {
 | 
			
		||||
    const cases = [
 | 
			
		||||
      {
 | 
			
		||||
 | 
			
		||||
@ -132,6 +132,19 @@ export async function getUtils(page: Page) {
 | 
			
		||||
    },
 | 
			
		||||
    waitForCmdReceive: (commandType: string) =>
 | 
			
		||||
      waitForCmdReceive(page, commandType),
 | 
			
		||||
    getSegmentBodyCoords: async (locator: string, px = 30) => {
 | 
			
		||||
      const overlay = page.locator(locator)
 | 
			
		||||
      const bbox = await overlay
 | 
			
		||||
        .boundingBox()
 | 
			
		||||
        .then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 }))
 | 
			
		||||
      const angle = Number(await overlay.getAttribute('data-overlay-angle'))
 | 
			
		||||
      const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px
 | 
			
		||||
      const angleYOffset = Math.sin(((angle - 180) * Math.PI) / 180) * px
 | 
			
		||||
      return {
 | 
			
		||||
        x: bbox.x + angleXOffset,
 | 
			
		||||
        y: bbox.y - angleYOffset,
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    getBoundingBox: async (locator: string) =>
 | 
			
		||||
      page
 | 
			
		||||
        .locator(locator)
 | 
			
		||||
 | 
			
		||||
@ -531,8 +531,7 @@ const ConstraintSymbol = ({
 | 
			
		||||
    varNameMap[_type as LineInputsType]?.implicitConstraintDesc
 | 
			
		||||
 | 
			
		||||
  const node = useMemo(
 | 
			
		||||
    () =>
 | 
			
		||||
      getNodeFromPath<Value>(parse(recast(kclManager.ast)), pathToNode).node,
 | 
			
		||||
    () => getNodeFromPath<Value>(kclManager.ast, pathToNode).node,
 | 
			
		||||
    [kclManager.ast, pathToNode]
 | 
			
		||||
  )
 | 
			
		||||
  const range: SourceRange = node ? [node.start, node.end] : [0, 0]
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,7 @@ export function ActionButtonDropdown({
 | 
			
		||||
              onClick={item.onClick}
 | 
			
		||||
              className="block px-3 py-1 hover:bg-primary/10 dark:hover:bg-chalkboard-80 border-0 m-0 text-sm w-full rounded-none text-left disabled:!bg-transparent dark:disabled:text-chalkboard-60"
 | 
			
		||||
              disabled={item.disabled}
 | 
			
		||||
              data-testid={item.label}
 | 
			
		||||
            >
 | 
			
		||||
              <span className="capitalize">{item.label}</span>
 | 
			
		||||
              {item.shortcut && (
 | 
			
		||||
 | 
			
		||||
@ -214,13 +214,17 @@ export const CreateNewVariable = ({
 | 
			
		||||
}) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <label htmlFor="create-new-variable" className="block mt-3 font-mono">
 | 
			
		||||
      <label
 | 
			
		||||
        htmlFor="create-new-variable"
 | 
			
		||||
        className="block mt-3 font-mono text-chalkboard-90"
 | 
			
		||||
      >
 | 
			
		||||
        Create new variable
 | 
			
		||||
      </label>
 | 
			
		||||
      <div className="mt-1 flex gap-2 items-center">
 | 
			
		||||
        {showCheckbox && (
 | 
			
		||||
          <input
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            data-testid="create-new-variable-checkbox"
 | 
			
		||||
            checked={shouldCreateVariable}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              setShouldCreateVariable(e.target.checked)
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,10 @@ import {
 | 
			
		||||
import { SetSelections, modelingMachine } from 'machines/modelingMachine'
 | 
			
		||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import { isCursorInSketchCommandRange } from 'lang/util'
 | 
			
		||||
import {
 | 
			
		||||
  isCursorInSketchCommandRange,
 | 
			
		||||
  updatePathToNodeFromMap,
 | 
			
		||||
} from 'lang/util'
 | 
			
		||||
import {
 | 
			
		||||
  kclManager,
 | 
			
		||||
  sceneInfra,
 | 
			
		||||
@ -34,7 +37,6 @@ import {
 | 
			
		||||
  handleSelectionBatch,
 | 
			
		||||
  isSelectionLastLine,
 | 
			
		||||
  isSketchPipe,
 | 
			
		||||
  updateSelections,
 | 
			
		||||
} from 'lib/selections'
 | 
			
		||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
 | 
			
		||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
 | 
			
		||||
@ -54,7 +56,6 @@ import {
 | 
			
		||||
} from 'lang/modifyAst'
 | 
			
		||||
import {
 | 
			
		||||
  Program,
 | 
			
		||||
  Value,
 | 
			
		||||
  VariableDeclaration,
 | 
			
		||||
  coreDump,
 | 
			
		||||
  parse,
 | 
			
		||||
@ -75,7 +76,6 @@ import { useSearchParams } from 'react-router-dom'
 | 
			
		||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
 | 
			
		||||
import { getVarNameModal } from 'hooks/useToolbarGuards'
 | 
			
		||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
 | 
			
		||||
import { applyConstraintEqualAngle } from './Toolbar/EqualAngle'
 | 
			
		||||
 | 
			
		||||
type MachineContext<T extends AnyStateMachine> = {
 | 
			
		||||
  state: StateFrom<T>
 | 
			
		||||
@ -221,7 +221,7 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
              }
 | 
			
		||||
            : {}
 | 
			
		||||
        ),
 | 
			
		||||
        'Set selection': assign(({ selectionRanges }, event) => {
 | 
			
		||||
        'Set selection': assign(({ selectionRanges, sketchDetails }, event) => {
 | 
			
		||||
          const setSelections = event.data as SetSelections // this was needed for ts after adding 'Set selection' action to on done modal events
 | 
			
		||||
          if (!editorManager.editorView) return {}
 | 
			
		||||
          const dispatchSelection = (selection?: EditorSelection) => {
 | 
			
		||||
@ -311,8 +311,19 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
          }
 | 
			
		||||
          if (setSelections.selectionType === 'completeSelection') {
 | 
			
		||||
            editorManager.selectRange(setSelections.selection)
 | 
			
		||||
            if (!sketchDetails)
 | 
			
		||||
              return {
 | 
			
		||||
                selectionRanges: setSelections.selection,
 | 
			
		||||
              }
 | 
			
		||||
            return {
 | 
			
		||||
              selectionRanges: setSelections.selection,
 | 
			
		||||
              sketchDetails: {
 | 
			
		||||
                ...sketchDetails,
 | 
			
		||||
                sketchPathToNode:
 | 
			
		||||
                  setSelections.updatedPathToNode ||
 | 
			
		||||
                  sketchDetails?.sketchPathToNode ||
 | 
			
		||||
                  [],
 | 
			
		||||
              },
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@ -533,6 +544,7 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
        },
 | 
			
		||||
        'Get angle info': async ({
 | 
			
		||||
          selectionRanges,
 | 
			
		||||
          sketchDetails,
 | 
			
		||||
        }): Promise<SetSelections> => {
 | 
			
		||||
          const { modifiedAst, pathToNodeMap } = await (angleBetweenInfo({
 | 
			
		||||
            selectionRanges,
 | 
			
		||||
@ -544,14 +556,27 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
                selectionRanges,
 | 
			
		||||
                angleOrLength: 'setAngle',
 | 
			
		||||
              }))
 | 
			
		||||
          await kclManager.updateAst(modifiedAst, true)
 | 
			
		||||
          const _modifiedAst = parse(recast(modifiedAst))
 | 
			
		||||
          if (!sketchDetails) throw new Error('No sketch details')
 | 
			
		||||
          const updatedPathToNode = updatePathToNodeFromMap(
 | 
			
		||||
            sketchDetails.sketchPathToNode,
 | 
			
		||||
            pathToNodeMap
 | 
			
		||||
          )
 | 
			
		||||
          await sceneEntitiesManager.updateAstAndRejigSketch(
 | 
			
		||||
            updatedPathToNode,
 | 
			
		||||
            _modifiedAst,
 | 
			
		||||
            sketchDetails.zAxis,
 | 
			
		||||
            sketchDetails.yAxis,
 | 
			
		||||
            sketchDetails.origin
 | 
			
		||||
          )
 | 
			
		||||
          return {
 | 
			
		||||
            selectionType: 'completeSelection',
 | 
			
		||||
            selection: pathMapToSelections(
 | 
			
		||||
              kclManager.ast,
 | 
			
		||||
              _modifiedAst,
 | 
			
		||||
              selectionRanges,
 | 
			
		||||
              pathToNodeMap
 | 
			
		||||
            ),
 | 
			
		||||
            updatedPathToNode,
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        'Get length info': async ({
 | 
			
		||||
@ -589,13 +614,26 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
        },
 | 
			
		||||
        'Get ABS X info': async ({
 | 
			
		||||
          selectionRanges,
 | 
			
		||||
          sketchDetails,
 | 
			
		||||
        }): Promise<SetSelections> => {
 | 
			
		||||
          const { modifiedAst, pathToNodeMap } =
 | 
			
		||||
            await applyConstraintAbsDistance({
 | 
			
		||||
              constraint: 'xAbs',
 | 
			
		||||
              selectionRanges,
 | 
			
		||||
            })
 | 
			
		||||
          await kclManager.updateAst(modifiedAst, true)
 | 
			
		||||
          const _modifiedAst = parse(recast(modifiedAst))
 | 
			
		||||
          if (!sketchDetails) throw new Error('No sketch details')
 | 
			
		||||
          const updatedPathToNode = updatePathToNodeFromMap(
 | 
			
		||||
            sketchDetails.sketchPathToNode,
 | 
			
		||||
            pathToNodeMap
 | 
			
		||||
          )
 | 
			
		||||
          await sceneEntitiesManager.updateAstAndRejigSketch(
 | 
			
		||||
            updatedPathToNode,
 | 
			
		||||
            _modifiedAst,
 | 
			
		||||
            sketchDetails.zAxis,
 | 
			
		||||
            sketchDetails.yAxis,
 | 
			
		||||
            sketchDetails.origin
 | 
			
		||||
          )
 | 
			
		||||
          return {
 | 
			
		||||
            selectionType: 'completeSelection',
 | 
			
		||||
            selection: pathMapToSelections(
 | 
			
		||||
@ -603,17 +641,31 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
              selectionRanges,
 | 
			
		||||
              pathToNodeMap
 | 
			
		||||
            ),
 | 
			
		||||
            updatedPathToNode,
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        'Get ABS Y info': async ({
 | 
			
		||||
          selectionRanges,
 | 
			
		||||
          sketchDetails,
 | 
			
		||||
        }): Promise<SetSelections> => {
 | 
			
		||||
          const { modifiedAst, pathToNodeMap } =
 | 
			
		||||
            await applyConstraintAbsDistance({
 | 
			
		||||
              constraint: 'yAbs',
 | 
			
		||||
              selectionRanges,
 | 
			
		||||
            })
 | 
			
		||||
          await kclManager.updateAst(modifiedAst, true)
 | 
			
		||||
          const _modifiedAst = parse(recast(modifiedAst))
 | 
			
		||||
          if (!sketchDetails) throw new Error('No sketch details')
 | 
			
		||||
          const updatedPathToNode = updatePathToNodeFromMap(
 | 
			
		||||
            sketchDetails.sketchPathToNode,
 | 
			
		||||
            pathToNodeMap
 | 
			
		||||
          )
 | 
			
		||||
          await sceneEntitiesManager.updateAstAndRejigSketch(
 | 
			
		||||
            updatedPathToNode,
 | 
			
		||||
            _modifiedAst,
 | 
			
		||||
            sketchDetails.zAxis,
 | 
			
		||||
            sketchDetails.yAxis,
 | 
			
		||||
            sketchDetails.origin
 | 
			
		||||
          )
 | 
			
		||||
          return {
 | 
			
		||||
            selectionType: 'completeSelection',
 | 
			
		||||
            selection: pathMapToSelections(
 | 
			
		||||
@ -621,6 +673,7 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
              selectionRanges,
 | 
			
		||||
              pathToNodeMap
 | 
			
		||||
            ),
 | 
			
		||||
            updatedPathToNode,
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        'Get convert to variable info': async ({ sketchDetails }, { data }) => {
 | 
			
		||||
 | 
			
		||||
@ -31,43 +31,6 @@ const projectWellFormed = {
 | 
			
		||||
} satisfies Project
 | 
			
		||||
 | 
			
		||||
describe('ProjectSidebarMenu tests', () => {
 | 
			
		||||
  test('Renders the project name', () => {
 | 
			
		||||
    render(
 | 
			
		||||
      <BrowserRouter>
 | 
			
		||||
        <CommandBarProvider>
 | 
			
		||||
          <SettingsAuthProviderJest>
 | 
			
		||||
            <ProjectSidebarMenu project={projectWellFormed} enableMenu={true} />
 | 
			
		||||
          </SettingsAuthProviderJest>
 | 
			
		||||
        </CommandBarProvider>
 | 
			
		||||
      </BrowserRouter>
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    fireEvent.click(screen.getByTestId('project-sidebar-toggle'))
 | 
			
		||||
 | 
			
		||||
    expect(screen.getByTestId('projectName')).toHaveTextContent(
 | 
			
		||||
      projectWellFormed.name
 | 
			
		||||
    )
 | 
			
		||||
    expect(screen.getByTestId('createdAt')).toHaveTextContent(
 | 
			
		||||
      `Created ${now.toLocaleDateString()}`
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('Renders app name if given no project', () => {
 | 
			
		||||
    render(
 | 
			
		||||
      <BrowserRouter>
 | 
			
		||||
        <CommandBarProvider>
 | 
			
		||||
          <SettingsAuthProviderJest>
 | 
			
		||||
            <ProjectSidebarMenu enableMenu={true} />
 | 
			
		||||
          </SettingsAuthProviderJest>
 | 
			
		||||
        </CommandBarProvider>
 | 
			
		||||
      </BrowserRouter>
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    fireEvent.click(screen.getByTestId('project-sidebar-toggle'))
 | 
			
		||||
 | 
			
		||||
    expect(screen.getByTestId('projectName')).toHaveTextContent(APP_NAME)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('Disables popover menu by default', () => {
 | 
			
		||||
    render(
 | 
			
		||||
      <BrowserRouter>
 | 
			
		||||
 | 
			
		||||
@ -138,41 +138,7 @@ function ProjectMenuPopover({
 | 
			
		||||
        >
 | 
			
		||||
          {({ close }) => (
 | 
			
		||||
            <>
 | 
			
		||||
              <div className="flex items-center gap-4 px-4 py-3">
 | 
			
		||||
                <div>
 | 
			
		||||
                  <p className="m-0 text-mono" data-testid="projectName">
 | 
			
		||||
                    {project?.name ? project.name : APP_NAME}
 | 
			
		||||
                  </p>
 | 
			
		||||
                  {project?.metadata && project.metadata.created && (
 | 
			
		||||
                    <p
 | 
			
		||||
                      className="m-0 text-xs text-chalkboard-80 dark:text-chalkboard-40"
 | 
			
		||||
                      data-testid="createdAt"
 | 
			
		||||
                    >
 | 
			
		||||
                      Created{' '}
 | 
			
		||||
                      {new Date(project.metadata.created).toLocaleDateString()}
 | 
			
		||||
                    </p>
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              {isTauri() ? (
 | 
			
		||||
                <FileTree
 | 
			
		||||
                  file={file}
 | 
			
		||||
                  className="overflow-hidden border-0 border-y border-chalkboard-30 dark:border-chalkboard-80"
 | 
			
		||||
                  onNavigateToFile={close}
 | 
			
		||||
                />
 | 
			
		||||
              ) : (
 | 
			
		||||
                <div className="flex-1 p-4 text-sm overflow-hidden">
 | 
			
		||||
                  <p>
 | 
			
		||||
                    In the browser version of Modeling App you can only have one
 | 
			
		||||
                    part, and the code is stored in your browser's storage.
 | 
			
		||||
                  </p>
 | 
			
		||||
                  <p className="my-6">
 | 
			
		||||
                    Please save any code you want to keep more permanently, as
 | 
			
		||||
                    your browser's storage is not guaranteed to be permanent.
 | 
			
		||||
                  </p>
 | 
			
		||||
                </div>
 | 
			
		||||
              )}
 | 
			
		||||
              <div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
 | 
			
		||||
              <div className="flex flex-col gap-2 p-4">
 | 
			
		||||
                <ActionButton
 | 
			
		||||
                  Element="button"
 | 
			
		||||
                  iconStart={{ icon: 'exportFile', className: 'p-1' }}
 | 
			
		||||
 | 
			
		||||
@ -120,6 +120,10 @@ export async function applyConstraintAbsDistance({
 | 
			
		||||
      createVariableDeclaration(variableName, valueNode)
 | 
			
		||||
    )
 | 
			
		||||
    _modifiedAst.body = newBody
 | 
			
		||||
    Object.values(pathToNodeMap).forEach((pathToNode) => {
 | 
			
		||||
      const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
 | 
			
		||||
      pathToNode[index][0] = Number(pathToNode[index][0]) + 1
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  return { modifiedAst: _modifiedAst, pathToNodeMap }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -98,7 +98,11 @@ export async function applyConstraintAngleBetween({
 | 
			
		||||
    value: valueUsedInTransform,
 | 
			
		||||
    initialVariableName: 'angle',
 | 
			
		||||
  } as any)
 | 
			
		||||
  if (segName === tagInfo?.tag && Number(value) === valueUsedInTransform) {
 | 
			
		||||
  if (
 | 
			
		||||
    segName === tagInfo?.tag &&
 | 
			
		||||
    Number(value) === valueUsedInTransform &&
 | 
			
		||||
    !variableName
 | 
			
		||||
  ) {
 | 
			
		||||
    return {
 | 
			
		||||
      modifiedAst,
 | 
			
		||||
      pathToNodeMap,
 | 
			
		||||
@ -128,6 +132,10 @@ export async function applyConstraintAngleBetween({
 | 
			
		||||
      createVariableDeclaration(variableName, valueNode)
 | 
			
		||||
    )
 | 
			
		||||
    _modifiedAst.body = newBody
 | 
			
		||||
    Object.values(_pathToNodeMap).forEach((pathToNode) => {
 | 
			
		||||
      const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
 | 
			
		||||
      pathToNode[index][0] = Number(pathToNode[index][0]) + 1
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  return {
 | 
			
		||||
    modifiedAst: _modifiedAst,
 | 
			
		||||
 | 
			
		||||
@ -138,6 +138,10 @@ export async function applyConstraintAngleLength({
 | 
			
		||||
        createVariableDeclaration(variableName, valueNode)
 | 
			
		||||
      )
 | 
			
		||||
      _modifiedAst.body = newBody
 | 
			
		||||
      Object.values(pathToNodeMap).forEach((pathToNode) => {
 | 
			
		||||
        const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
 | 
			
		||||
        pathToNode[index][0] = Number(pathToNode[index][0]) + 1
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
      modifiedAst: _modifiedAst,
 | 
			
		||||
 | 
			
		||||
@ -341,36 +341,29 @@ const setAbsDistanceCreateNode =
 | 
			
		||||
    isXOrYLine = false,
 | 
			
		||||
    index = xOrY === 'x' ? 0 : 1
 | 
			
		||||
  ): TransformInfo['createNode'] =>
 | 
			
		||||
  ({ tag, forceValueUsedInTransform }) => {
 | 
			
		||||
    return (args, _, referencedSegment) => {
 | 
			
		||||
      const valueUsedInTransform = roundOff(
 | 
			
		||||
        getArgLiteralVal(args?.[index]) - (referencedSegment?.to?.[index] || 0),
 | 
			
		||||
        2
 | 
			
		||||
      )
 | 
			
		||||
      const val =
 | 
			
		||||
        (forceValueUsedInTransform as BinaryPart) ||
 | 
			
		||||
        createLiteral(valueUsedInTransform)
 | 
			
		||||
      if (isXOrYLine) {
 | 
			
		||||
        return createCallWrapper(
 | 
			
		||||
          xOrY === 'x' ? 'xLineTo' : 'yLineTo',
 | 
			
		||||
          val,
 | 
			
		||||
          tag,
 | 
			
		||||
          valueUsedInTransform
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
  ({ tag, forceValueUsedInTransform }) =>
 | 
			
		||||
  (args) => {
 | 
			
		||||
    const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[index]), 2)
 | 
			
		||||
    const val =
 | 
			
		||||
      (forceValueUsedInTransform as BinaryPart) ||
 | 
			
		||||
      createLiteral(valueUsedInTransform)
 | 
			
		||||
    if (isXOrYLine) {
 | 
			
		||||
      return createCallWrapper(
 | 
			
		||||
        'lineTo',
 | 
			
		||||
        !index ? [val, args[1]] : [args[0], val],
 | 
			
		||||
        xOrY === 'x' ? 'xLineTo' : 'yLineTo',
 | 
			
		||||
        val,
 | 
			
		||||
        tag,
 | 
			
		||||
        valueUsedInTransform
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
    return createCallWrapper(
 | 
			
		||||
      'lineTo',
 | 
			
		||||
      !index ? [val, args[1]] : [args[0], val],
 | 
			
		||||
      tag,
 | 
			
		||||
      valueUsedInTransform
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
const setAbsDistanceForAngleLineCreateNode =
 | 
			
		||||
  (
 | 
			
		||||
    xOrY: 'x' | 'y',
 | 
			
		||||
    index = xOrY === 'x' ? 0 : 1
 | 
			
		||||
  ): TransformInfo['createNode'] =>
 | 
			
		||||
  (xOrY: 'x' | 'y'): TransformInfo['createNode'] =>
 | 
			
		||||
  ({ tag, forceValueUsedInTransform, varValA }) => {
 | 
			
		||||
    return (args) => {
 | 
			
		||||
      const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[1]), 2)
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,22 @@ export function pathMapToSelections(
 | 
			
		||||
  return newSelections
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function updatePathToNodeFromMap(
 | 
			
		||||
  oldPath: PathToNode,
 | 
			
		||||
  pathToNodeMap: { [key: number]: PathToNode }
 | 
			
		||||
): PathToNode {
 | 
			
		||||
  const updatedPathToNode = JSON.parse(JSON.stringify(oldPath))
 | 
			
		||||
  let max = 0
 | 
			
		||||
  Object.values(pathToNodeMap).forEach((path) => {
 | 
			
		||||
    const index = Number(path[1][0])
 | 
			
		||||
    if (index > max) {
 | 
			
		||||
      max = index
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  updatedPathToNode[1][0] = max
 | 
			
		||||
  return updatedPathToNode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isCursorInSketchCommandRange(
 | 
			
		||||
  artifactMap: ArtifactMap,
 | 
			
		||||
  selectionRanges: Selections
 | 
			
		||||
 | 
			
		||||
@ -64,6 +64,7 @@ export type SetSelections =
 | 
			
		||||
  | {
 | 
			
		||||
      selectionType: 'completeSelection'
 | 
			
		||||
      selection: Selections
 | 
			
		||||
      updatedPathToNode?: PathToNode
 | 
			
		||||
    }
 | 
			
		||||
  | {
 | 
			
		||||
      selectionType: 'mirrorCodeMirrorSelections'
 | 
			
		||||
 | 
			
		||||
@ -504,51 +504,45 @@ impl SketchGroup {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_coords_from_paths(&self) -> Result<Point2d, KclError> {
 | 
			
		||||
        if self.value.is_empty() {
 | 
			
		||||
            return Ok(self.start.to.into());
 | 
			
		||||
        }
 | 
			
		||||
    /// Get the path most recently sketched.
 | 
			
		||||
    pub fn latest_path(&self) -> Option<&Path> {
 | 
			
		||||
        self.value.last()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        let index = self.value.len() - 1;
 | 
			
		||||
        if let Some(path) = self.value.get(index) {
 | 
			
		||||
            let base = path.get_base();
 | 
			
		||||
            Ok(base.to.into())
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(self.start.to.into())
 | 
			
		||||
        }
 | 
			
		||||
    /// The "pen" is an imaginary pen drawing the path.
 | 
			
		||||
    /// This gets the current point the pen is hovering over, i.e. the point
 | 
			
		||||
    /// where the last path segment ends, and the next path segment will begin.
 | 
			
		||||
    pub fn current_pen_position(&self) -> Result<Point2d, KclError> {
 | 
			
		||||
        let Some(path) = self.latest_path() else {
 | 
			
		||||
            return Ok(self.start.to.into());
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let base = path.get_base();
 | 
			
		||||
        Ok(base.to.into())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
 | 
			
		||||
        if self.value.is_empty() {
 | 
			
		||||
        let Some(path) = self.latest_path() else {
 | 
			
		||||
            return GetTangentialInfoFromPathsResult {
 | 
			
		||||
                center_or_tangent_point: self.start.to,
 | 
			
		||||
                is_center: false,
 | 
			
		||||
                ccw: false,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        let index = self.value.len() - 1;
 | 
			
		||||
        if let Some(path) = self.value.get(index) {
 | 
			
		||||
            match path {
 | 
			
		||||
                Path::TangentialArcTo { center, ccw, .. } => GetTangentialInfoFromPathsResult {
 | 
			
		||||
                    center_or_tangent_point: *center,
 | 
			
		||||
                    is_center: true,
 | 
			
		||||
                    ccw: *ccw,
 | 
			
		||||
                },
 | 
			
		||||
                _ => {
 | 
			
		||||
                    let base = path.get_base();
 | 
			
		||||
                    GetTangentialInfoFromPathsResult {
 | 
			
		||||
                        center_or_tangent_point: base.from,
 | 
			
		||||
                        is_center: false,
 | 
			
		||||
                        ccw: false,
 | 
			
		||||
                    }
 | 
			
		||||
        };
 | 
			
		||||
        match path {
 | 
			
		||||
            Path::TangentialArcTo { center, ccw, .. } => GetTangentialInfoFromPathsResult {
 | 
			
		||||
                center_or_tangent_point: *center,
 | 
			
		||||
                is_center: true,
 | 
			
		||||
                ccw: *ccw,
 | 
			
		||||
            },
 | 
			
		||||
            _ => {
 | 
			
		||||
                let base = path.get_base();
 | 
			
		||||
                GetTangentialInfoFromPathsResult {
 | 
			
		||||
                    center_or_tangent_point: base.from,
 | 
			
		||||
                    is_center: false,
 | 
			
		||||
                    ccw: false,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            GetTangentialInfoFromPathsResult {
 | 
			
		||||
                center_or_tangent_point: self.start.to,
 | 
			
		||||
                is_center: false,
 | 
			
		||||
                ccw: false,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -53,7 +53,7 @@ async fn inner_line_to(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from = sketch_group.current_pen_position()?;
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
 | 
			
		||||
    args.send_modeling_cmd(
 | 
			
		||||
@ -128,7 +128,7 @@ async fn inner_x_line_to(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from = sketch_group.current_pen_position()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch_group = inner_line_to([to, from.y], sketch_group, tag, args).await?;
 | 
			
		||||
 | 
			
		||||
@ -166,7 +166,7 @@ async fn inner_y_line_to(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from = sketch_group.current_pen_position()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch_group = inner_line_to([from.x, to], sketch_group, tag, args).await?;
 | 
			
		||||
    Ok(new_sketch_group)
 | 
			
		||||
@ -213,7 +213,7 @@ async fn inner_line(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from = sketch_group.current_pen_position()?;
 | 
			
		||||
    let to = [from.x + delta[0], from.y + delta[1]];
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
@ -381,7 +381,7 @@ async fn inner_angled_line(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from = sketch_group.current_pen_position()?;
 | 
			
		||||
    let (angle, length) = match data {
 | 
			
		||||
        AngledLineData::AngleAndLengthNamed { angle, length } => (angle, length),
 | 
			
		||||
        AngledLineData::AngleAndLengthPair(pair) => (pair[0], pair[1]),
 | 
			
		||||
@ -514,7 +514,7 @@ async fn inner_angled_line_to_x(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from = sketch_group.current_pen_position()?;
 | 
			
		||||
    let AngledLineToData { angle, to: x_to } = data;
 | 
			
		||||
 | 
			
		||||
    let x_component = x_to - from.x;
 | 
			
		||||
@ -600,7 +600,7 @@ async fn inner_angled_line_to_y(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from = sketch_group.current_pen_position()?;
 | 
			
		||||
    let AngledLineToData { angle, to: y_to } = data;
 | 
			
		||||
 | 
			
		||||
    let y_component = y_to - from.y;
 | 
			
		||||
@ -672,7 +672,7 @@ async fn inner_angled_line_that_intersects(
 | 
			
		||||
        })?
 | 
			
		||||
        .get_base();
 | 
			
		||||
 | 
			
		||||
    let from = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from = sketch_group.current_pen_position()?;
 | 
			
		||||
    let to = intersection_with_parallel_line(
 | 
			
		||||
        &[intersect_path.from.into(), intersect_path.to.into()],
 | 
			
		||||
        data.offset.unwrap_or_default(),
 | 
			
		||||
@ -1355,7 +1355,7 @@ pub(crate) async fn inner_close(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from = sketch_group.current_pen_position()?;
 | 
			
		||||
    let to: Point2d = sketch_group.start.from.into();
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
@ -1449,7 +1449,7 @@ pub(crate) async fn inner_arc(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from: Point2d = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from: Point2d = sketch_group.current_pen_position()?;
 | 
			
		||||
 | 
			
		||||
    let (center, angle_start, angle_end, radius, end) = match &data {
 | 
			
		||||
        ArcData::AnglesAndRadius {
 | 
			
		||||
@ -1555,7 +1555,7 @@ async fn inner_tangential_arc(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from: Point2d = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from: Point2d = sketch_group.current_pen_position()?;
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
 | 
			
		||||
@ -1676,7 +1676,7 @@ async fn inner_tangential_arc_to(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from: Point2d = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from: Point2d = sketch_group.current_pen_position()?;
 | 
			
		||||
    let tangent_info = sketch_group.get_tangential_info_from_paths();
 | 
			
		||||
    let tan_previous_point = if tangent_info.is_center {
 | 
			
		||||
        get_tangent_point_from_previous_arc(tangent_info.center_or_tangent_point, tangent_info.ccw, from.into())
 | 
			
		||||
@ -1762,7 +1762,7 @@ async fn inner_bezier_curve(
 | 
			
		||||
    tag: Option<String>,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<SketchGroup>, KclError> {
 | 
			
		||||
    let from = sketch_group.get_coords_from_paths()?;
 | 
			
		||||
    let from = sketch_group.current_pen_position()?;
 | 
			
		||||
 | 
			
		||||
    let relative = true;
 | 
			
		||||
    let delta = data.to;
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user