[BUG] circle and threePointArc and other overlay fixes (#6409)

* fix length constrainting

* conflicet

* fix circle center constraints

* fix up circle remove constraints more and add test

* fix three point arc overlays and add test for it

* fixes

* console log

* fix tangential arc stuff

* fmt

* fix unit test

* fix console error when selectiong arc
This commit is contained in:
Kurt Hutten
2025-04-30 12:08:45 +10:00
committed by GitHub
parent 0ea0d1703e
commit bf63b21d74
13 changed files with 877 additions and 190 deletions

View File

@ -1455,4 +1455,328 @@ part001 = startSketchOn(XZ)
})
}
})
test.describe('Testing with showAllOverlays flag', () => {
test('circle overlay constraints with showAllOverlays', async ({
page,
editor,
homePage,
scene,
cmdBar,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`myvar = -141
sketch001 = startSketchOn(XZ)
profile002 = circle(sketch001, center = [345, 0], radius = 238.38)
`
)
// Set flag to always show overlays without hover
localStorage.setItem('showAllOverlays', 'true')
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await scene.connectionEstablished()
await scene.settled(cmdBar)
// Click on the circle line to enter edit mode
await page
.getByText('circle(sketch001, center = [345, 0], radius = 238.38)')
.click()
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500)
// Verify that the overlay is visible without hovering
await expect(page.getByTestId('segment-overlay')).toHaveCount(1)
// First, constrain the X coordinate
const xConstraintBtn = page.locator(
'[data-constraint-type="xAbsolute"][data-is-constrained="false"]'
)
await expect(xConstraintBtn).toBeVisible()
await xConstraintBtn.click()
// Complete the command
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
// Verify the X constraint was added
await editor.expectEditor.toContain('center = [xAbs001, 0]', {
shouldNormalise: true,
})
// Now constrain the Y coordinate
const yConstraintBtn = page.locator(
'[data-constraint-type="yAbsolute"][data-is-constrained="false"]'
)
await expect(yConstraintBtn).toBeVisible()
await yConstraintBtn.click()
// Complete the command
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
// Verify the Y constraint was added
await editor.expectEditor.toContain('center = [xAbs001, yAbs001]', {
shouldNormalise: true,
})
// Now constrain the radius
const radiusConstraintBtn = page.locator(
'[data-constraint-type="radius"][data-is-constrained="false"]'
)
await expect(radiusConstraintBtn).toBeVisible()
await radiusConstraintBtn.click()
// Complete the command
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
// Verify all constraints were added
await editor.expectEditor.toContain(
'center = [xAbs001, yAbs001], radius = radius001',
{ shouldNormalise: true }
)
// Now unconstrain the X coordinate
const constrainedXBtn = page.locator(
'[data-constraint-type="xAbsolute"][data-is-constrained="true"]'
)
await expect(constrainedXBtn).toBeVisible()
await constrainedXBtn.click()
// Verify the X constraint was removed
await editor.expectEditor.toContain(
'center = [345, yAbs001], radius = radius001',
{ shouldNormalise: true }
)
// Now unconstrain the Y coordinate
const constrainedYBtn = page.locator(
'[data-constraint-type="yAbsolute"][data-is-constrained="true"]'
)
await expect(constrainedYBtn).toBeVisible()
await constrainedYBtn.click()
// Verify the Y constraint was removed
await editor.expectEditor.toContain(
'center = [345, 0], radius = radius001',
{ shouldNormalise: true }
)
// Finally, unconstrain the radius
const constrainedRadiusBtn = page.locator(
'[data-constraint-type="radius"][data-is-constrained="true"]'
)
await expect(constrainedRadiusBtn).toBeVisible()
await constrainedRadiusBtn.click()
// Verify all constraints were removed
await editor.expectEditor.toContain(
'center = [345, 0], radius = 238.38',
{ shouldNormalise: true }
)
})
})
test('arc with interiorAbsolute and endAbsolute kwargs overlay constraints', async ({
page,
editor,
homePage,
scene,
cmdBar,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`myvar = 141
sketch001 = startSketchOn(XZ)
profile001 = circleThreePoint(
sketch001,
p1 = [445.16, 202.16],
p2 = [445.16, 116.92],
p3 = [546.85, 103],
)
profile003 = startProfileAt([64.39, 35.16], sketch001)
|> line(end = [60.69, 23.02])
|> arc(interiorAbsolute = [159.26, 100.58], endAbsolute = [237.05, 84.07])
|> line(end = [70.31, 42.28])`
)
// Set flag to always show overlays without hover
localStorage.setItem('showAllOverlays', 'true')
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await scene.connectionEstablished()
await scene.settled(cmdBar)
// Click on the line before the arc to enter edit mode
await page.getByText('line(end = [60.69, 23.02])').click()
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500)
// Verify overlays are visible
// 3 for the three point arc, and 4 for the 3 segments (arc has two)
await expect(page.getByTestId('segment-overlay')).toHaveCount(7)
// ---- Testing interior point constraints ----
// 1. Constrain interior X coordinate
const interiorXConstraintBtn = page
.locator(
'[data-constraint-type="xAbsolute"][data-is-constrained="false"]'
)
.nth(3)
await expect(interiorXConstraintBtn).toBeVisible()
await interiorXConstraintBtn.click()
// Complete the command
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
// Verify the constraint was added
await editor.expectEditor.toContain(
'interiorAbsolute = [xAbs001, 100.58]',
{
shouldNormalise: true,
}
)
// 2. Constrain interior Y coordinate
const interiorYConstraintBtn = page
.locator(
'[data-constraint-type="yAbsolute"][data-is-constrained="false"]'
)
.nth(3)
await expect(interiorYConstraintBtn).toBeVisible()
await interiorYConstraintBtn.click()
// Complete the command
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
// Verify both constraints were added
await editor.expectEditor.toContain(
'interiorAbsolute = [xAbs001, yAbs001]',
{
shouldNormalise: true,
}
)
// ---- Testing end point constraints ----
// 3. Constrain end X coordinate
const endXConstraintBtn = page
.locator(
'[data-constraint-type="xAbsolute"][data-is-constrained="false"]'
)
.nth(3) // still number 3 because the interior ones are now constrained
await expect(endXConstraintBtn).toBeVisible()
await endXConstraintBtn.click()
// Complete the command
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
// Verify the constraint was added
await editor.expectEditor.toContain('endAbsolute = [xAbs002, 84.07]', {
shouldNormalise: true,
})
// 4. Constrain end Y coordinate
const endYConstraintBtn = page
.locator(
'[data-constraint-type="yAbsolute"][data-is-constrained="false"]'
)
.nth(3) // still number 3 because the interior ones are now constrained
await expect(endYConstraintBtn).toBeVisible()
await endYConstraintBtn.click()
// Complete the command
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page.getByRole('button', { name: 'arrow right Continue' }).click()
// Verify all constraints were added
await editor.expectEditor.toContain(
'interiorAbsolute = [xAbs001, yAbs001], endAbsolute = [xAbs002, yAbs002]',
{
shouldNormalise: true,
}
)
// ---- Unconstrain the coordinates in reverse order ----
// 5. Unconstrain end Y coordinate
const constrainedEndYBtn = page
.locator('[data-constraint-type="yAbsolute"][data-is-constrained="true"]')
.nth(1)
await expect(constrainedEndYBtn).toBeVisible()
await constrainedEndYBtn.click()
// Verify the constraint was removed
await editor.expectEditor.toContain('endAbsolute = [xAbs002, 84.07]', {
shouldNormalise: true,
})
// 6. Unconstrain end X coordinate
const constrainedEndXBtn = page
.locator('[data-constraint-type="xAbsolute"][data-is-constrained="true"]')
.nth(1)
await expect(constrainedEndXBtn).toBeVisible()
await constrainedEndXBtn.click()
// Verify the constraint was removed
await editor.expectEditor.toContain('endAbsolute = [237.05, 84.07]', {
shouldNormalise: true,
})
// 7. Unconstrain interior Y coordinate
const constrainedInteriorYBtn = page
.locator('[data-constraint-type="yAbsolute"][data-is-constrained="true"]')
.nth(0)
await expect(constrainedInteriorYBtn).toBeVisible()
await constrainedInteriorYBtn.click()
// Verify the constraint was removed
await editor.expectEditor.toContain(
'interiorAbsolute = [xAbs001, 100.58]',
{
shouldNormalise: true,
}
)
// 8. Unconstrain interior X coordinate
const constrainedInteriorXBtn = page
.locator('[data-constraint-type="xAbsolute"][data-is-constrained="true"]')
.nth(0)
await expect(constrainedInteriorXBtn).toBeVisible()
await constrainedInteriorXBtn.click()
// Verify all constraints were removed
await editor.expectEditor.toContain(
'interiorAbsolute = [159.26, 100.58], endAbsolute = [237.05, 84.07]',
{
shouldNormalise: true,
}
)
})
})

View File

@ -4,6 +4,10 @@ import toast from 'react-hot-toast'
import type { Node } from '@rust/kcl-lib/bindings/Node'
// Helper function to check if overlays should always be shown
const shouldAlwaysShowOverlays = () =>
localStorage.getItem('showAllOverlays') === 'true'
import type { ReactCameraProperties } from '@src/clientSideScene/CameraControls'
import {
EXTRA_SEGMENT_HANDLE,
@ -172,19 +176,28 @@ export const ClientSideScene = ({
const Overlays = () => {
const { context } = useModelingContext()
if (context.mouseState.type === 'isDragging') return null
// Simple check directly from localStorage
const alwaysShowOverlays = shouldAlwaysShowOverlays()
// Set a large zIndex, the overlay for hover dropdown menu on line segments needs to render
// over the length labels on the line segments
return (
<div className="absolute inset-0 pointer-events-none z-sketchOverlayDropdown">
{Object.entries(context.segmentOverlays)
.flatMap((a) =>
a[1].map((b) => ({ pathToNodeString: a[0], overlay: b }))
.flatMap(([pathToNodeString, overlays]) =>
overlays.map((b) => ({ pathToNodeString, overlay: b }))
)
.filter((a) => a.overlay.visible)
.filter((a) => alwaysShowOverlays || a.overlay.visible)
.map(({ pathToNodeString, overlay }, index) => {
// Force visibility if alwaysShowOverlays is true
const modifiedOverlay = alwaysShowOverlays
? { ...overlay, visible: true }
: overlay
return (
<Overlay
overlay={overlay}
overlay={modifiedOverlay}
key={pathToNodeString + String(index)}
pathToNodeString={pathToNodeString}
overlayIndex={index}
@ -205,6 +218,10 @@ const Overlay = ({
pathToNodeString: string
}) => {
const { context, send, state } = useModelingContext()
// Simple check directly from localStorage
const alwaysShowOverlays = shouldAlwaysShowOverlays()
let xAlignment = overlay.angle < 0 ? '0%' : '-100%'
let yAlignment = overlay.angle < -90 || overlay.angle >= 90 ? '0%' : '-100%'
@ -241,8 +258,9 @@ const Overlay = ({
Math.sin(((overlay.angle + offsetAngle) * Math.PI) / 180) * offset
const shouldShow =
overlay.visible &&
typeof context?.segmentHoverMap?.[pathToNodeString] === 'number' &&
(overlay.visible || alwaysShowOverlays) &&
(alwaysShowOverlays ||
typeof context?.segmentHoverMap?.[pathToNodeString] === 'number') &&
!(
state.matches({ Sketch: 'Line tool' }) ||
state.matches({ Sketch: 'Tangential arc to' }) ||

View File

@ -171,14 +171,14 @@ export class SceneInfra {
_overlayCallbacks(callbacks: (() => SegmentOverlayPayload | null)[]) {
const segmentOverlayPayload: SegmentOverlayPayload = {
type: 'add-many',
type: 'set-many',
overlays: {},
}
callbacks.forEach((cb) => {
const overlay = cb()
if (overlay?.type === 'set-one') {
segmentOverlayPayload.overlays[overlay.pathToNodeString] = overlay.seg
} else if (overlay?.type === 'add-many') {
} else if (overlay?.type === 'set-many') {
Object.assign(segmentOverlayPayload.overlays, overlay.overlays)
}
})

View File

@ -91,6 +91,7 @@ import type {
SegmentOverlays,
} from '@src/machines/modelingMachine'
import toast from 'react-hot-toast'
import { ARG_INTERIOR_ABSOLUTE } from '@src/lang/constants'
const ANGLE_INDICATOR_RADIUS = 30 // in px
interface CreateSegmentArgs {
@ -1551,10 +1552,10 @@ class ThreePointArcSegment implements SegmentUtils {
overlayDetails.forEach((payload, index) => {
if (payload?.type === 'set-one') {
overlays[payload.pathToNodeString] = payload.seg
// Add filterValue: 'interiorAbsolute' for p2 and 'end' for p3
// Add filterValue: 'interiorAbsolute' for p2 and 'endAbsolute' for p3
segmentOverlays.push({
...payload.seg[0],
filterValue: index === 0 ? 'interiorAbsolute' : 'end',
filterValue: index === 0 ? ARG_INTERIOR_ABSOLUTE : 'endAbsolute',
})
}
})

View File

@ -137,7 +137,8 @@ const Loading = ({ children, className, dataTestId }: LoadingProps) => {
__html: Marked.parse(
CONNECTION_ERROR_TEXT[error.error] +
(error.context
? '\n\nThe error details are: ' + error.context
? '\n\nThe error details are: ' +
JSON.stringify(error.context)
: ''),
{
renderer: new SafeRenderer(markedOptions),

View File

@ -267,9 +267,8 @@ export const ModelingMachineProvider = ({
'Set Segment Overlays': assign({
segmentOverlays: ({ context: { segmentOverlays }, event }) => {
if (event.type !== 'Set Segment Overlays') return {}
if (event.data.type === 'add-many')
if (event.data.type === 'set-many')
return {
...segmentOverlays,
...event.data.overlays,
}
if (event.data.type === 'set-one')
@ -1393,6 +1392,8 @@ export const ModelingMachineProvider = ({
})
)
)
result.exprInsertIndex = data.namedValue.insertIndex
if (
trap(parseResultAfterInsertion) ||
!resultIsOk(parseResultAfterInsertion)
@ -1401,7 +1402,7 @@ export const ModelingMachineProvider = ({
result = {
modifiedAst: parseResultAfterInsertion.program,
pathToReplaced: astAfterReplacement.pathToReplaced,
exprInsertIndex: astAfterReplacement.exprInsertIndex,
exprInsertIndex: result.exprInsertIndex,
}
} else if ('valueText' in data.namedValue) {
// If they didn't provide a constant name,

View File

@ -24,7 +24,11 @@ import { findUsesOfTagInPipe } from '@src/lang/queryAst'
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
import type { Artifact } from '@src/lang/std/artifactGraph'
import { codeRefFromRange } from '@src/lang/std/artifactGraph'
import type { InputArgKeys, SimplifiedArgDetails } from '@src/lang/std/stdTypes'
import type {
InputArg,
InputArgKeys,
SimplifiedArgDetails,
} from '@src/lang/std/stdTypes'
import { topLevelRange } from '@src/lang/util'
import type { Identifier, Literal, LiteralValue } from '@src/lang/wasm'
import { assertParse, recast } from '@src/lang/wasm'
@ -686,19 +690,19 @@ describe('Testing removeSingleConstraintInfo', () => {
|> /*4*/ angledLine(angle = 30 + 0, endAbsoluteY = 10.14 + 0)
|> angledLineThatIntersects(angle = 3.14 + 0, intersectTag = a, offset = 0 + 0)
|> tangentialArc(endAbsolute = [3.14 + 0, 13.14 + 0])`
test.each([
[' line(end = [3 + 0, 4])', 'arrayIndex', 1, ''],
const cases: [string, InputArg['type'], number | string, string][] = [
[' line(end = [3 + 0, 4])', 'arrayItem', 1, ''],
[
'/*0*/ angledLine(angle = 3, length = 3.14 + 0)',
'labeledArg',
'angle',
'',
],
['line(endAbsolute = [6.14 + 0, 3.14 + 0])', 'arrayIndex', 0, ''],
['xLine(endAbsolute = 8)', '', '', '/*xAbs*/'],
['yLine(endAbsolute = 5)', '', '', '/*yAbs*/'],
['yLine(length = 3.14, tag = $a)', '', '', '/*yRel*/'],
['xLine(length = 3.14)', '', '', '/*xRel*/'],
['line(endAbsolute = [6.14 + 0, 3.14 + 0])', 'arrayItem', 0, ''],
['xLine(endAbsolute = 8)', 'singleValue', '', '/*xAbs*/'],
['yLine(endAbsolute = 5)', 'singleValue', '', '/*yAbs*/'],
['yLine(length = 3.14, tag = $a)', 'singleValue', '', '/*yRel*/'],
['xLine(length = 3.14)', 'singleValue', '', '/*xRel*/'],
[
'/*1*/ angledLine(angle = 3, lengthX = 3.14 + 0)',
'labeledArg',
@ -731,11 +735,12 @@ describe('Testing removeSingleConstraintInfo', () => {
],
[
'tangentialArc(endAbsolute = [3.14 + 0, 13.14])',
'labeledArg',
'labeledArgArrayItem',
'endAbsolute',
'',
],
] as const)(
]
test.each(cases)(
'stdlib fn: %s',
async (expectedFinish, key, value, commentLabel) => {
const ast = assertParse(code)
@ -749,19 +754,26 @@ describe('Testing removeSingleConstraintInfo', () => {
const range = topLevelRange(start + 1, start + lineOfInterest.length)
const pathToNode = getNodePathFromSourceRange(ast, range)
let argPosition: SimplifiedArgDetails
if (key === 'arrayIndex' && typeof value === 'number') {
if (key === 'arrayItem' && typeof value === 'number') {
argPosition = {
type: 'arrayItem',
index: value === 0 ? 0 : 1,
}
} else if (key === '') {
} else if (key === 'singleValue') {
argPosition = {
type: 'singleValue',
}
} else if (key === 'labeledArg') {
} else if (key === 'labeledArg' && typeof value === 'string') {
argPosition = {
type: 'labeledArg',
key: value,
key: value as any,
}
} else if (key === 'labeledArgArrayItem') {
console.log()
argPosition = {
type: 'labeledArgArrayItem',
key: value as any,
index: 1,
}
} else {
throw new Error('argPosition is undefined')

View File

@ -1038,7 +1038,6 @@ export function updatePathToNodesAfterEdit(
if (err(oldNodeResult)) return oldNodeResult
const oldNode = oldNodeResult.node
const varName = oldNode.declaration.id.name
console.log('varName', varName)
// Find the old and new indices for this variable
const oldIndex = oldVarDecls.get(varName)

View File

@ -571,7 +571,11 @@ describe('testing getConstraintInfo', () => {
isConstrained: false,
value: '3.14',
sourceRange: [expect.any(Number), expect.any(Number), 0],
argPosition: { type: 'arrayItem', index: 0 },
argPosition: {
type: 'labeledArgArrayItem',
key: 'endAbsolute',
index: 0,
},
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArc',
},
@ -580,7 +584,11 @@ describe('testing getConstraintInfo', () => {
isConstrained: false,
value: '13.14',
sourceRange: [expect.any(Number), expect.any(Number), 0],
argPosition: { type: 'arrayItem', index: 1 },
argPosition: {
type: 'labeledArgArrayItem',
key: 'endAbsolute',
index: 1,
},
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArc',
},
@ -1085,7 +1093,11 @@ describe('testing getConstraintInfo', () => {
isConstrained: true,
value: '3.14 + 0',
sourceRange: [expect.any(Number), expect.any(Number), 0],
argPosition: { type: 'arrayItem', index: 0 },
argPosition: {
type: 'labeledArgArrayItem',
key: 'endAbsolute',
index: 0,
},
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArc',
},
@ -1094,7 +1106,11 @@ describe('testing getConstraintInfo', () => {
isConstrained: true,
value: '13.14 + 0',
sourceRange: [expect.any(Number), expect.any(Number), 0],
argPosition: { type: 'arrayItem', index: 1 },
argPosition: {
type: 'labeledArgArrayItem',
key: 'endAbsolute',
index: 1,
},
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArc',
},

View File

@ -98,7 +98,7 @@ import type { EdgeCutInfo } from '@src/machines/modelingMachine'
const STRAIGHT_SEGMENT_ERR = new Error(
'Invalid input, expected "straight-segment"'
)
const ARC_SEGMENT_ERR = new Error('Invalid input, expected "arc-segment"')
const ARC_SEGMENT_ERR = () => new Error('Invalid input, expected "arc-segment"')
const CIRCLE_THREE_POINT_SEGMENT_ERR = new Error(
'Invalid input, expected "circle-three-point-segment"'
)
@ -1026,13 +1026,15 @@ export const tangentialArc: SketchLineHelperKw = {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const result = replaceExistingCallback([
{
type: 'arrayItem',
type: 'labeledArgArrayItem',
key: ARG_END_ABSOLUTE,
index: 0,
argType: 'xAbsolute',
expr: toX,
},
{
type: 'arrayItem',
type: 'labeledArgArrayItem',
key: ARG_END_ABSOLUTE,
index: 1,
argType: 'yAbsolute',
expr: toY,
@ -1145,28 +1147,32 @@ export const tangentialArc: SketchLineHelperKw = {
['arg', LABELED_ARG_FIELD],
]
if (expr.type !== 'ArrayExpression' || expr.elements.length < 2) {
constraints.push(
constrainInfo(
'xAbsolute',
isNotLiteralArrayOrStatic(expr),
code.slice(expr.start, expr.end),
'tangentialArc',
0,
topLevelRange(expr.start, expr.end),
pathToArg
)
)
constraints.push(
constrainInfo(
'yAbsolute',
isNotLiteralArrayOrStatic(expr),
code.slice(expr.start, expr.end),
'tangentialArc',
1,
topLevelRange(expr.start, expr.end),
pathToArg
)
)
constraints.push({
stdLibFnName: 'tangentialArc',
type: 'xAbsolute',
isConstrained: isNotLiteralArrayOrStatic(expr),
sourceRange: topLevelRange(expr.start, expr.end),
pathToNode: pathToArg,
value: code.slice(expr.start, expr.end),
argPosition: {
type: 'labeledArgArrayItem',
index: 0,
key: ARG_END_ABSOLUTE,
},
})
constraints.push({
stdLibFnName: 'tangentialArc',
type: 'yAbsolute',
isConstrained: isNotLiteralArrayOrStatic(expr),
sourceRange: topLevelRange(expr.start, expr.end),
pathToNode: pathToArg,
value: code.slice(expr.start, expr.end),
argPosition: {
type: 'labeledArgArrayItem',
index: 1,
key: ARG_END_ABSOLUTE,
},
})
return constraints
}
const pathToX: PathToNode = [
@ -1181,72 +1187,79 @@ export const tangentialArc: SketchLineHelperKw = {
]
const exprX = expr.elements[0]
const exprY = expr.elements[1]
constraints.push(
constrainInfo(
'xAbsolute',
isNotLiteralArrayOrStatic(exprX),
code.slice(exprX.start, exprX.end),
'tangentialArc',
0,
topLevelRange(exprX.start, exprX.end),
pathToX
)
)
constraints.push(
constrainInfo(
'yAbsolute',
isNotLiteralArrayOrStatic(exprY),
code.slice(exprY.start, exprY.end),
'tangentialArc',
1,
topLevelRange(exprY.start, exprY.end),
pathToY
)
)
constraints.push({
stdLibFnName: 'tangentialArc',
type: 'xAbsolute',
isConstrained: isNotLiteralArrayOrStatic(exprX),
sourceRange: topLevelRange(exprX.start, exprX.end),
pathToNode: pathToX,
value: code.slice(exprX.start, exprX.end),
argPosition: {
type: 'labeledArgArrayItem',
index: 0,
key: ARG_END_ABSOLUTE,
},
})
constraints.push({
stdLibFnName: 'tangentialArc',
type: 'yAbsolute',
isConstrained: isNotLiteralArrayOrStatic(exprY),
sourceRange: topLevelRange(exprY.start, exprY.end),
pathToNode: pathToY,
value: code.slice(exprY.start, exprY.end),
argPosition: {
type: 'labeledArgArrayItem',
index: 1,
key: ARG_END_ABSOLUTE,
},
})
}
return constraints
},
}
export const circle: SketchLineHelperKw = {
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
if (segmentInput.type !== 'arc-segment') return ARC_SEGMENT_ERR
if (segmentInput.type !== 'arc-segment') return ARC_SEGMENT_ERR()
const { center, radius } = segmentInput
const _node = { ...node }
// Try to get the pipe expression first
const nodeMeta = getNodeFromPath<PipeExpression>(
_node,
pathToNode,
'PipeExpression'
)
if (err(nodeMeta)) return nodeMeta
// If we get a pipe expression, handle as before
if (!err(nodeMeta) && nodeMeta.node.type === 'PipeExpression') {
const { node: pipe } = nodeMeta
const x = createLiteral(roundOff(center[0], 2))
const y = createLiteral(roundOff(center[1], 2))
const radiusExp = createLiteral(roundOff(radius, 2))
const centerArray = createArrayExpression([x, y])
if (replaceExistingCallback) {
const result = replaceExistingCallback([
{
type: 'arrayInObject',
index: 0,
key: 'center',
type: 'labeledArgArrayItem',
argType: 'xAbsolute',
key: ARG_CIRCLE_CENTER,
index: 0,
expr: x,
},
{
type: 'arrayInObject',
index: 1,
key: 'center',
type: 'labeledArgArrayItem',
argType: 'yAbsolute',
key: ARG_CIRCLE_CENTER,
index: 1,
expr: y,
},
{
type: 'objectProperty',
key: 'radius',
type: 'labeledArg',
argType: 'radius',
key: ARG_RADIUS,
expr: radiusExp,
},
])
@ -1254,7 +1267,25 @@ export const circle: SketchLineHelperKw = {
const { callExp, valueUsedInTransform } = result
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
// Handle the case where the returned expression is not a proper kwarg expression
if (callExp.type !== 'CallExpressionKw') {
// In a pipe expression, the unlabeled first arg can be omitted
const centerArg = createLabeledArg(ARG_CIRCLE_CENTER, centerArray)
const radiusArg = createLabeledArg(ARG_RADIUS, radiusExp)
const circleKw = createCallExpressionStdLibKw('circle', null, [
centerArg,
radiusArg,
])
pipe.body[callIndex] = circleKw
} else {
// For CallExpressionKw, we don't need to set an unlabeled argument in pipe expressions
if (callExp.unlabeled) {
callExp.unlabeled = null
}
pipe.body[callIndex] = callExp
}
return {
modifiedAst: _node,
@ -1262,10 +1293,109 @@ export const circle: SketchLineHelperKw = {
valueUsedInTransform,
}
}
return new Error('not implemented')
return new Error('Problem with circle')
}
// If it's not in a pipe expression, try to get variable declarator
const varDecMeta = getNodeFromPath<VariableDeclarator>(
_node,
pathToNode,
'VariableDeclarator'
)
if (err(varDecMeta))
return new Error('Could not find pipe expression or variable declarator')
const { node: varDec } = varDecMeta
// Get the existing circle expression to extract the unlabeled first argument (sketch)
const existingCircleExpr = varDec.init as Node<CallExpressionKw>
let sketchArg: Expr | null = null
// Extract the unlabeled sketch argument if it exists
if (existingCircleExpr && existingCircleExpr.type === 'CallExpressionKw') {
sketchArg = existingCircleExpr.unlabeled
}
// These follow the same pattern whether we use the callback or not
const x = createLiteral(roundOff(center[0], 2))
const y = createLiteral(roundOff(center[1], 2))
const radiusExp = createLiteral(roundOff(radius, 2))
const centerArray = createArrayExpression([x, y])
if (replaceExistingCallback) {
// debugger
const result = replaceExistingCallback([
{
type: 'labeledArgArrayItem',
argType: 'xAbsolute',
key: ARG_CIRCLE_CENTER,
index: 0,
expr: x,
},
{
type: 'labeledArgArrayItem',
argType: 'yAbsolute',
key: ARG_CIRCLE_CENTER,
index: 1,
expr: y,
},
{
type: 'labeledArg',
argType: 'radius',
key: ARG_RADIUS,
expr: radiusExp,
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
// Make sure the unlabeled first argument (sketch) is preserved
if (callExp.type === 'CallExpressionKw') {
if (sketchArg && !callExp.unlabeled) {
callExp.unlabeled = sketchArg
}
// Replace the variable declarator init with the call expression
varDec.init = callExp
} else {
// If somehow we get a non-kw expression, create the correct one
const centerArg = createLabeledArg(ARG_CIRCLE_CENTER, centerArray)
const radiusArg = createLabeledArg(ARG_RADIUS, radiusExp)
const circleKw = createCallExpressionStdLibKw('circle', sketchArg, [
centerArg,
radiusArg,
])
// Replace the variable declarator init with the correct KW expression
varDec.init = circleKw
}
return {
modifiedAst: _node,
pathToNode,
valueUsedInTransform,
}
} else {
// If no callback, create a CallExpressionKw directly
const centerArg = createLabeledArg(ARG_CIRCLE_CENTER, centerArray)
const radiusArg = createLabeledArg(ARG_RADIUS, radiusExp)
const circleKw = createCallExpressionStdLibKw('circle', sketchArg, [
centerArg,
radiusArg,
])
// Replace the variable declarator init with the call expression
varDec.init = circleKw
return {
modifiedAst: _node,
pathToNode,
}
}
},
updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'arc-segment') return ARC_SEGMENT_ERR
if (input.type !== 'arc-segment') return ARC_SEGMENT_ERR()
const { center, radius } = input
const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpressionKw>(_node, pathToNode)
@ -1301,6 +1431,7 @@ export const circle: SketchLineHelperKw = {
['arguments', 'CallExpressionKw'],
[centerInfo.argIndex, ARG_INDEX_FIELD],
['arg', LABELED_ARG_FIELD],
['elements', 'ArrayExpression'],
]
const pathToRadiusLiteral: PathToNode = [
...pathToNode,
@ -1317,16 +1448,19 @@ export const circle: SketchLineHelperKw = {
[1, 'index'],
]
return [
constrainInfo(
'radius',
isNotLiteralArrayOrStatic(radiusInfo.expr),
code.slice(radiusInfo.expr.start, radiusInfo.expr.end),
'circle',
'radius',
topLevelRange(radiusInfo.expr.start, radiusInfo.expr.end),
pathToRadiusLiteral
),
const constraints: ConstrainInfo[] = [
{
stdLibFnName: 'circle',
type: 'radius',
isConstrained: isNotLiteralArrayOrStatic(radiusInfo.expr),
sourceRange: topLevelRange(radiusInfo.expr.start, radiusInfo.expr.end),
pathToNode: pathToRadiusLiteral,
value: code.slice(radiusInfo.expr.start, radiusInfo.expr.end),
argPosition: {
type: 'labeledArg',
key: ARG_RADIUS,
},
},
{
stdLibFnName: 'circle',
type: 'xAbsolute',
@ -1341,9 +1475,9 @@ export const circle: SketchLineHelperKw = {
centerInfo.expr.elements[0].end
),
argPosition: {
type: 'arrayInObject',
type: 'labeledArgArrayItem',
index: 0,
key: 'center',
key: ARG_CIRCLE_CENTER,
},
},
{
@ -1360,12 +1494,13 @@ export const circle: SketchLineHelperKw = {
centerInfo.expr.elements[1].end
),
argPosition: {
type: 'arrayInObject',
type: 'labeledArgArrayItem',
index: 1,
key: 'center',
},
},
]
return constraints
},
}
@ -1378,7 +1513,7 @@ export const arc: SketchLineHelperKw = {
replaceExistingCallback,
spliceBetween,
}) => {
if (segmentInput.type !== 'arc-segment') return ARC_SEGMENT_ERR
if (segmentInput.type !== 'arc-segment') return ARC_SEGMENT_ERR()
const { center, radius, from, to } = segmentInput
const _node = { ...node }
@ -1512,7 +1647,7 @@ export const arc: SketchLineHelperKw = {
}
},
updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'arc-segment') return ARC_SEGMENT_ERR
if (input.type !== 'arc-segment') return ARC_SEGMENT_ERR()
const { center, radius, from, to } = input
const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpressionKw>(_node, pathToNode)
@ -1653,7 +1788,7 @@ export const arcTo: SketchLineHelperKw = {
spliceBetween,
}) => {
if (segmentInput.type !== 'circle-three-point-segment')
return ARC_SEGMENT_ERR
return ARC_SEGMENT_ERR()
const { p2, p3 } = segmentInput
const _node = { ...node }
@ -1669,29 +1804,43 @@ export const arcTo: SketchLineHelperKw = {
// p1 is the start point (from the previous segment)
// p2 is the interiorAbsolute point
// p3 is the end point
const interiorAbsolute = createArrayExpression([
createLiteral(roundOff(p2[0], 2)),
createLiteral(roundOff(p2[1], 2)),
])
const p2x = createLiteral(roundOff(p2[0], 2))
const p2y = createLiteral(roundOff(p2[1], 2))
const interiorAbsolute = createArrayExpression([p2x, p2y])
const end = createArrayExpression([
createLiteral(roundOff(p3[0], 2)),
createLiteral(roundOff(p3[1], 2)),
])
const p3x = createLiteral(roundOff(p3[0], 2))
const p3y = createLiteral(roundOff(p3[1], 2))
const end = createArrayExpression([p3x, p3y])
if (replaceExistingCallback) {
const result = replaceExistingCallback([
{
type: 'objectProperty',
type: 'labeledArgArrayItem',
key: 'interiorAbsolute',
index: 0,
argType: 'xAbsolute',
expr: createLiteral(0), // This is a workaround, the actual value will be set later
expr: p2x,
},
{
type: 'objectProperty',
key: 'endAbsolute',
type: 'labeledArgArrayItem',
key: 'interiorAbsolute',
index: 1,
argType: 'yAbsolute',
expr: createLiteral(0), // This is a workaround, the actual value will be set later
expr: p2y,
},
{
type: 'labeledArgArrayItem',
key: 'endAbsolute',
index: 0,
argType: 'xAbsolute',
expr: p3x,
},
{
type: 'labeledArgArrayItem',
key: 'endAbsolute',
index: 1,
argType: 'yAbsolute',
expr: p3y,
},
])
if (err(result)) return result
@ -1757,7 +1906,7 @@ export const arcTo: SketchLineHelperKw = {
}
},
updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'circle-three-point-segment') return ARC_SEGMENT_ERR
if (input.type !== 'circle-three-point-segment') return ARC_SEGMENT_ERR()
const { p2, p3 } = input
const _node = { ...node }
@ -1866,8 +2015,8 @@ export const arcTo: SketchLineHelperKw = {
),
stdLibFnName: 'arc',
argPosition: {
type: 'arrayInObject',
key: 'interiorAbsolute',
type: 'labeledArgArrayItem',
key: ARG_INTERIOR_ABSOLUTE,
index: 0,
},
sourceRange: topLevelRange(
@ -1875,7 +2024,7 @@ export const arcTo: SketchLineHelperKw = {
interiorAbsoluteArr.elements[0].end
),
pathToNode: pathToInteriorX,
filterValue: 'interiorAbsolute',
filterValue: ARG_INTERIOR_ABSOLUTE,
},
{
type: 'yAbsolute',
@ -1888,8 +2037,8 @@ export const arcTo: SketchLineHelperKw = {
),
stdLibFnName: 'arc',
argPosition: {
type: 'arrayInObject',
key: 'interiorAbsolute',
type: 'labeledArgArrayItem',
key: ARG_INTERIOR_ABSOLUTE,
index: 1,
},
sourceRange: topLevelRange(
@ -1897,7 +2046,7 @@ export const arcTo: SketchLineHelperKw = {
interiorAbsoluteArr.elements[1].end
),
pathToNode: pathToInteriorY,
filterValue: 'interiorAbsolute',
filterValue: ARG_INTERIOR_ABSOLUTE,
},
{
type: 'xAbsolute',
@ -1905,8 +2054,8 @@ export const arcTo: SketchLineHelperKw = {
value: code.slice(endArr.elements[0].start, endArr.elements[0].end),
stdLibFnName: 'arc',
argPosition: {
type: 'arrayInObject',
key: 'end',
type: 'labeledArgArrayItem',
key: 'endAbsolute',
index: 0,
},
sourceRange: topLevelRange(
@ -1914,7 +2063,7 @@ export const arcTo: SketchLineHelperKw = {
endArr.elements[0].end
),
pathToNode: pathToEndX,
filterValue: 'end',
filterValue: 'endAbsolute',
},
{
type: 'yAbsolute',
@ -1922,8 +2071,8 @@ export const arcTo: SketchLineHelperKw = {
value: code.slice(endArr.elements[1].start, endArr.elements[1].end),
stdLibFnName: 'arc',
argPosition: {
type: 'arrayInObject',
key: 'end',
type: 'labeledArgArrayItem',
key: 'endAbsolute',
index: 1,
},
sourceRange: topLevelRange(
@ -1931,7 +2080,7 @@ export const arcTo: SketchLineHelperKw = {
endArr.elements[1].end
),
pathToNode: pathToEndY,
filterValue: 'end',
filterValue: 'endAbsolute',
},
]
@ -3052,6 +3201,54 @@ export function changeSketchArguments(
return new Error(`not a sketch line helper: ${fnName}`)
}
/**
* Converts a function name to a ToolTip (UI hint/identifier) based on the segment type.
*
* This function differs from fnNameToTooltip() in that it uses the Path/segment
* type information to determine the correct ToolTip, rather than analyzing function
* argument labels. This is particularly important for functions like 'arc' where
* the same function name can map to different ToolTips ('arc' or 'arcTo') depending
* on the segment type (ArcThreePoint vs other types).
*
* While fnNameToTooltip() determines the ToolTip by examining the function's argument
* structure at call site, this function uses the segment geometry information, making
* it suitable for contexts where we have the Path object but not the full argument list.
*
* @param seg - The Path object containing segment type information
* @param fnName - The function name to convert to a ToolTip
* @returns The corresponding ToolTip or an Error if the function name is unknown
*/
export function fnNameToToolTipFromSegment(
seg: Path,
fnName: string
): ToolTip | Error {
switch (fnName) {
case 'arc': {
return seg.type === 'ArcThreePoint' ? 'arcTo' : 'arc'
}
case 'line':
case 'lineTo':
case 'xLine':
case 'xLineTo':
case 'yLine':
case 'yLineTo':
case 'angledLineToX':
case 'angledLineToY':
case 'angledLineOfXLength':
case 'angledLineOfYLength':
case 'angledLineThatIntersects':
case 'circleThreePoint':
case 'circle':
case 'tangentialArc':
case 'angledLine':
return fnName
default:
const err = `Unknown sketch line function ${fnName}`
console.error(err)
return new Error(err)
}
}
/**
* Function names no longer cleanly correspond to tooltips.
* A tooltip is a user action, like a line to a given point, or in a given direction.
@ -3770,18 +3967,30 @@ export const getArc = (
callExp: CallExpressionKw
):
| {
val: [Expr, Expr, Expr]
val: [Expr, Expr, Expr] | [Expr, Expr]
tag?: Expr
}
| Error => {
const angleStart = findKwArg(ARG_ANGLE_START, callExp)
const angleEnd = findKwArg(ARG_ANGLE_END, callExp)
const radius = findKwArg(ARG_RADIUS, callExp)
if (!angleStart || !angleEnd || !radius) {
return new Error(`arc call needs angleStart, angleEnd, and radius args`)
}
const isMissingAnyAngleKwArgs = !angleStart || !angleEnd || !radius
const interiorAbsolute = findKwArg(ARG_INTERIOR_ABSOLUTE, callExp)
const endAbsolute = findKwArg(ARG_END_ABSOLUTE, callExp)
const isMissingAnyEndKwArgs = !interiorAbsolute || !endAbsolute
if (!isMissingAnyAngleKwArgs) {
const tag = findKwArg(ARG_TAG, callExp)
return { val: [angleStart, angleEnd, radius], tag }
} else if (!isMissingAnyEndKwArgs) {
const tag = findKwArg(ARG_TAG, callExp)
return { val: [interiorAbsolute, endAbsolute], tag }
}
return new Error(
`arc call needs [angleStart, angleEnd, radius] or [interiorAbsolute, endAbsolute] args`
)
}
/**

View File

@ -39,6 +39,7 @@ import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
import {
createFirstArg,
fnNameToTooltip,
fnNameToToolTipFromSegment,
getAngledLine,
getAngledLineThatIntersects,
getArc,
@ -79,7 +80,12 @@ import type {
} from '@src/lang/wasm'
import { sketchFromKclValue } from '@src/lang/wasm'
import type { Selections } from '@src/lib/selections'
import { cleanErrs, err, isErr, isNotErr } from '@src/lib/trap'
import {
cleanErrs,
err,
isErr as _isErr,
isNotErr as _isNotErr,
} from '@src/lib/trap'
import {
allLabels,
getAngle,
@ -1420,40 +1426,114 @@ export function removeSingleConstraint({
'This code path only works with callExpressionKw but a positional call was somehow passed'
)
}
// Get all current labeled arguments from the CallExpressionKw
const existingArgs = callExp.node.arguments
const toReplace = inputToReplace.key
let argsPreFilter = inputs.map((arg) => {
if (arg.type !== 'labeledArg') {
return new Error(`arg isn't a labeled arg: ${arg.type}`)
}
const k = arg.key
if (k !== toReplace) {
return createLabeledArg(k, arg.expr)
} else {
// Basic approach: get the current args, filter out the TAG arg if it exists,
// replace the targetArg with the raw value
// 1. Filter out any existing tag argument since it will be handled separately
const filteredArgs = existingArgs.filter(
(arg) => arg.label.name !== ARG_TAG
)
// 2. Map through the args, replacing only the one we want to change
const labeledArgs = filteredArgs.map((arg) => {
if (arg.label.name === toReplace) {
// Find the raw value to use for the argument being replaced
const rawArgVersion = rawArgs.find(
(a) => a.type === 'labeledArg' && a.key === k
(a) => a.type === 'labeledArg' && a.key === toReplace
)
if (!rawArgVersion) {
return new Error(
`raw arg version not found while trying to remove constraint: ${JSON.stringify(arg)}`
)
console.error(`Raw arg version not found for key: ${toReplace}`)
// If raw value not found, preserve the original argument
return arg
}
return createLabeledArg(k, rawArgVersion.expr)
// Replace with raw value
return createLabeledArg(toReplace, rawArgVersion.expr)
}
// Keep other arguments as they are
return arg
})
const args = argsPreFilter.filter(isNotErr)
const errorArgs = argsPreFilter.filter(isErr)
if (errorArgs.length > 0) {
return new Error('Error while trying to remove constraint', {
cause: errorArgs,
})
}
const noncode = callExp.node.nonCodeMeta
// Use the existing unlabeled argument if available, otherwise use undefined
const unlabeledArg = callExp.node.unlabeled ?? undefined
return createStdlibCallExpressionKw(
callExp.node.callee.name.name as ToolTip,
args,
labeledArgs,
tag,
undefined,
unlabeledArg,
noncode
)
}
if (inputToReplace.type === 'labeledArgArrayItem') {
if (callExp.node.type !== 'CallExpressionKw') {
return new Error(
'This code path only works with callExpressionKw but a positional call was somehow passed'
)
}
// Get all current labeled arguments from the CallExpressionKw
const existingArgs = callExp.node.arguments
const targetKey = inputToReplace.key
const targetIndex = inputToReplace.index
// Create a copy of the existing labeled arguments
const labeledArgs = existingArgs.map((arg) => {
// Only modify the specific argument that matches the targeted key
if (
arg.label.name === targetKey &&
arg.arg.type === 'ArrayExpression'
) {
// We're dealing with an array expression within a labeled argument
const arrayElements = [...arg.arg.elements]
// Find the raw value to use for the argument item being replaced
const rawArgVersion = rawArgs.find(
(a) =>
a.type === 'labeledArgArrayItem' &&
a.key === targetKey &&
a.index === targetIndex
)
if (rawArgVersion && 'expr' in rawArgVersion) {
// Replace just the specific array element with the raw value
arrayElements[targetIndex] = rawArgVersion.expr
// Create a new labeled argument with the modified array
return createLabeledArg(
targetKey,
createArrayExpression(arrayElements)
)
}
// If no raw value found, keep the original argument
return arg
}
// Return other arguments unchanged
return arg
})
const noncode = callExp.node.nonCodeMeta
// Use the existing unlabeled argument if available, otherwise use undefined
const unlabeledArg = callExp.node.unlabeled ?? undefined
return createStdlibCallExpressionKw(
callExp.node.callee.name.name as ToolTip,
labeledArgs,
tag,
undefined,
unlabeledArg,
noncode
)
}
@ -2057,6 +2137,15 @@ export function transformAstSketchLines({
argType: a.type,
})
break
case 'labeledArgArrayItem':
inputs.push({
type: 'labeledArgArrayItem',
key: a.argPosition.key,
index: a.argPosition.index,
expr: nodeMeta.node,
argType: a.type,
})
break
case 'arrayInObject':
inputs.push({
type: 'arrayInObject',
@ -2105,13 +2194,9 @@ export function transformAstSketchLines({
}
const { to, from } = seg
// Note to ADAM: Here is where the replaceExisting call gets sent.
const replacedSketchLine = replaceSketchLine({
node: node,
variables: memVars,
pathToNode: _pathToNode,
referencedSegment,
fnName: transformTo || (call.node.callee.name.name as ToolTip),
segmentInput:
const segmentInput: Parameters<
typeof replaceSketchLine
>[0]['segmentInput'] =
seg.type === 'Circle'
? {
type: 'arc-segment',
@ -2132,8 +2217,19 @@ export function transformAstSketchLines({
type: 'straight-segment',
to,
from,
},
}
const fnName = fnNameToToolTipFromSegment(
seg,
transformTo || (call.node.callee.name.name as ToolTip)
)
if (err(fnName)) return fnName
const replacedSketchLine = replaceSketchLine({
node: node,
variables: memVars,
pathToNode: _pathToNode,
referencedSegment,
fnName,
segmentInput,
replaceExistingCallback: (rawArgs) =>
callBack({
referenceSegName: _referencedSegmentName,

View File

@ -171,6 +171,14 @@ interface LabeledArg<T> {
expr: T
overrideExpr?: Node<Expr>
}
interface LabeledArgArrayItem<T> {
type: 'labeledArgArrayItem'
key: InputArgKeys
index: 0 | 1
argType: LineInputsType
expr: T
overrideExpr?: Node<Expr>
}
type _InputArg<T> =
| SingleValueInput<T>
@ -179,6 +187,7 @@ type _InputArg<T> =
| ArrayOrObjItemInput<T>
| ArrayInObject<T>
| LabeledArg<T>
| LabeledArgArrayItem<T>
/**
* {@link RawArg.expr} is the current expression for each of the args for a segment
@ -222,6 +231,7 @@ export type SimplifiedArgDetails =
| Omit<ArrayOrObjItemInput<null>, 'expr' | 'argType'>
| Omit<ArrayInObject<null>, 'expr' | 'argType'>
| Omit<LabeledArg<null>, 'expr' | 'argType'>
| Omit<LabeledArgArrayItem<null>, 'expr' | 'argType'>
/**
* Represents the result of creating a sketch expression (line, tangentialArc, angledLine, circle, etc.).

View File

@ -273,7 +273,7 @@ export type SegmentOverlayPayload =
}
| { type: 'clear' }
| {
type: 'add-many'
type: 'set-many'
overlays: SegmentOverlays
}