[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:
@ -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,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -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' }) ||
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
@ -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',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
},
|
||||
|
@ -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`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
|
@ -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.).
|
||||
|
@ -273,7 +273,7 @@ export type SegmentOverlayPayload =
|
||||
}
|
||||
| { type: 'clear' }
|
||||
| {
|
||||
type: 'add-many'
|
||||
type: 'set-many'
|
||||
overlays: SegmentOverlays
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user