[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'
|
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 type { ReactCameraProperties } from '@src/clientSideScene/CameraControls'
|
||||||
import {
|
import {
|
||||||
EXTRA_SEGMENT_HANDLE,
|
EXTRA_SEGMENT_HANDLE,
|
||||||
@ -172,19 +176,28 @@ export const ClientSideScene = ({
|
|||||||
const Overlays = () => {
|
const Overlays = () => {
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
if (context.mouseState.type === 'isDragging') return null
|
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
|
// Set a large zIndex, the overlay for hover dropdown menu on line segments needs to render
|
||||||
// over the length labels on the line segments
|
// over the length labels on the line segments
|
||||||
return (
|
return (
|
||||||
<div className="absolute inset-0 pointer-events-none z-sketchOverlayDropdown">
|
<div className="absolute inset-0 pointer-events-none z-sketchOverlayDropdown">
|
||||||
{Object.entries(context.segmentOverlays)
|
{Object.entries(context.segmentOverlays)
|
||||||
.flatMap((a) =>
|
.flatMap(([pathToNodeString, overlays]) =>
|
||||||
a[1].map((b) => ({ pathToNodeString: a[0], overlay: b }))
|
overlays.map((b) => ({ pathToNodeString, overlay: b }))
|
||||||
)
|
)
|
||||||
.filter((a) => a.overlay.visible)
|
.filter((a) => alwaysShowOverlays || a.overlay.visible)
|
||||||
.map(({ pathToNodeString, overlay }, index) => {
|
.map(({ pathToNodeString, overlay }, index) => {
|
||||||
|
// Force visibility if alwaysShowOverlays is true
|
||||||
|
const modifiedOverlay = alwaysShowOverlays
|
||||||
|
? { ...overlay, visible: true }
|
||||||
|
: overlay
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay
|
<Overlay
|
||||||
overlay={overlay}
|
overlay={modifiedOverlay}
|
||||||
key={pathToNodeString + String(index)}
|
key={pathToNodeString + String(index)}
|
||||||
pathToNodeString={pathToNodeString}
|
pathToNodeString={pathToNodeString}
|
||||||
overlayIndex={index}
|
overlayIndex={index}
|
||||||
@ -205,6 +218,10 @@ const Overlay = ({
|
|||||||
pathToNodeString: string
|
pathToNodeString: string
|
||||||
}) => {
|
}) => {
|
||||||
const { context, send, state } = useModelingContext()
|
const { context, send, state } = useModelingContext()
|
||||||
|
|
||||||
|
// Simple check directly from localStorage
|
||||||
|
const alwaysShowOverlays = shouldAlwaysShowOverlays()
|
||||||
|
|
||||||
let xAlignment = overlay.angle < 0 ? '0%' : '-100%'
|
let xAlignment = overlay.angle < 0 ? '0%' : '-100%'
|
||||||
let yAlignment = overlay.angle < -90 || overlay.angle >= 90 ? '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
|
Math.sin(((overlay.angle + offsetAngle) * Math.PI) / 180) * offset
|
||||||
|
|
||||||
const shouldShow =
|
const shouldShow =
|
||||||
overlay.visible &&
|
(overlay.visible || alwaysShowOverlays) &&
|
||||||
typeof context?.segmentHoverMap?.[pathToNodeString] === 'number' &&
|
(alwaysShowOverlays ||
|
||||||
|
typeof context?.segmentHoverMap?.[pathToNodeString] === 'number') &&
|
||||||
!(
|
!(
|
||||||
state.matches({ Sketch: 'Line tool' }) ||
|
state.matches({ Sketch: 'Line tool' }) ||
|
||||||
state.matches({ Sketch: 'Tangential arc to' }) ||
|
state.matches({ Sketch: 'Tangential arc to' }) ||
|
||||||
|
@ -171,14 +171,14 @@ export class SceneInfra {
|
|||||||
|
|
||||||
_overlayCallbacks(callbacks: (() => SegmentOverlayPayload | null)[]) {
|
_overlayCallbacks(callbacks: (() => SegmentOverlayPayload | null)[]) {
|
||||||
const segmentOverlayPayload: SegmentOverlayPayload = {
|
const segmentOverlayPayload: SegmentOverlayPayload = {
|
||||||
type: 'add-many',
|
type: 'set-many',
|
||||||
overlays: {},
|
overlays: {},
|
||||||
}
|
}
|
||||||
callbacks.forEach((cb) => {
|
callbacks.forEach((cb) => {
|
||||||
const overlay = cb()
|
const overlay = cb()
|
||||||
if (overlay?.type === 'set-one') {
|
if (overlay?.type === 'set-one') {
|
||||||
segmentOverlayPayload.overlays[overlay.pathToNodeString] = overlay.seg
|
segmentOverlayPayload.overlays[overlay.pathToNodeString] = overlay.seg
|
||||||
} else if (overlay?.type === 'add-many') {
|
} else if (overlay?.type === 'set-many') {
|
||||||
Object.assign(segmentOverlayPayload.overlays, overlay.overlays)
|
Object.assign(segmentOverlayPayload.overlays, overlay.overlays)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -91,6 +91,7 @@ import type {
|
|||||||
SegmentOverlays,
|
SegmentOverlays,
|
||||||
} from '@src/machines/modelingMachine'
|
} from '@src/machines/modelingMachine'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
import { ARG_INTERIOR_ABSOLUTE } from '@src/lang/constants'
|
||||||
|
|
||||||
const ANGLE_INDICATOR_RADIUS = 30 // in px
|
const ANGLE_INDICATOR_RADIUS = 30 // in px
|
||||||
interface CreateSegmentArgs {
|
interface CreateSegmentArgs {
|
||||||
@ -1551,10 +1552,10 @@ class ThreePointArcSegment implements SegmentUtils {
|
|||||||
overlayDetails.forEach((payload, index) => {
|
overlayDetails.forEach((payload, index) => {
|
||||||
if (payload?.type === 'set-one') {
|
if (payload?.type === 'set-one') {
|
||||||
overlays[payload.pathToNodeString] = payload.seg
|
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({
|
segmentOverlays.push({
|
||||||
...payload.seg[0],
|
...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(
|
__html: Marked.parse(
|
||||||
CONNECTION_ERROR_TEXT[error.error] +
|
CONNECTION_ERROR_TEXT[error.error] +
|
||||||
(error.context
|
(error.context
|
||||||
? '\n\nThe error details are: ' + error.context
|
? '\n\nThe error details are: ' +
|
||||||
|
JSON.stringify(error.context)
|
||||||
: ''),
|
: ''),
|
||||||
{
|
{
|
||||||
renderer: new SafeRenderer(markedOptions),
|
renderer: new SafeRenderer(markedOptions),
|
||||||
|
@ -267,9 +267,8 @@ export const ModelingMachineProvider = ({
|
|||||||
'Set Segment Overlays': assign({
|
'Set Segment Overlays': assign({
|
||||||
segmentOverlays: ({ context: { segmentOverlays }, event }) => {
|
segmentOverlays: ({ context: { segmentOverlays }, event }) => {
|
||||||
if (event.type !== 'Set Segment Overlays') return {}
|
if (event.type !== 'Set Segment Overlays') return {}
|
||||||
if (event.data.type === 'add-many')
|
if (event.data.type === 'set-many')
|
||||||
return {
|
return {
|
||||||
...segmentOverlays,
|
|
||||||
...event.data.overlays,
|
...event.data.overlays,
|
||||||
}
|
}
|
||||||
if (event.data.type === 'set-one')
|
if (event.data.type === 'set-one')
|
||||||
@ -1393,6 +1392,8 @@ export const ModelingMachineProvider = ({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
result.exprInsertIndex = data.namedValue.insertIndex
|
||||||
|
|
||||||
if (
|
if (
|
||||||
trap(parseResultAfterInsertion) ||
|
trap(parseResultAfterInsertion) ||
|
||||||
!resultIsOk(parseResultAfterInsertion)
|
!resultIsOk(parseResultAfterInsertion)
|
||||||
@ -1401,7 +1402,7 @@ export const ModelingMachineProvider = ({
|
|||||||
result = {
|
result = {
|
||||||
modifiedAst: parseResultAfterInsertion.program,
|
modifiedAst: parseResultAfterInsertion.program,
|
||||||
pathToReplaced: astAfterReplacement.pathToReplaced,
|
pathToReplaced: astAfterReplacement.pathToReplaced,
|
||||||
exprInsertIndex: astAfterReplacement.exprInsertIndex,
|
exprInsertIndex: result.exprInsertIndex,
|
||||||
}
|
}
|
||||||
} else if ('valueText' in data.namedValue) {
|
} else if ('valueText' in data.namedValue) {
|
||||||
// If they didn't provide a constant name,
|
// 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 { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
||||||
import type { Artifact } from '@src/lang/std/artifactGraph'
|
import type { Artifact } from '@src/lang/std/artifactGraph'
|
||||||
import { codeRefFromRange } 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 { topLevelRange } from '@src/lang/util'
|
||||||
import type { Identifier, Literal, LiteralValue } from '@src/lang/wasm'
|
import type { Identifier, Literal, LiteralValue } from '@src/lang/wasm'
|
||||||
import { assertParse, recast } 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)
|
|> /*4*/ angledLine(angle = 30 + 0, endAbsoluteY = 10.14 + 0)
|
||||||
|> angledLineThatIntersects(angle = 3.14 + 0, intersectTag = a, offset = 0 + 0)
|
|> angledLineThatIntersects(angle = 3.14 + 0, intersectTag = a, offset = 0 + 0)
|
||||||
|> tangentialArc(endAbsolute = [3.14 + 0, 13.14 + 0])`
|
|> tangentialArc(endAbsolute = [3.14 + 0, 13.14 + 0])`
|
||||||
test.each([
|
const cases: [string, InputArg['type'], number | string, string][] = [
|
||||||
[' line(end = [3 + 0, 4])', 'arrayIndex', 1, ''],
|
[' line(end = [3 + 0, 4])', 'arrayItem', 1, ''],
|
||||||
[
|
[
|
||||||
'/*0*/ angledLine(angle = 3, length = 3.14 + 0)',
|
'/*0*/ angledLine(angle = 3, length = 3.14 + 0)',
|
||||||
'labeledArg',
|
'labeledArg',
|
||||||
'angle',
|
'angle',
|
||||||
'',
|
'',
|
||||||
],
|
],
|
||||||
['line(endAbsolute = [6.14 + 0, 3.14 + 0])', 'arrayIndex', 0, ''],
|
['line(endAbsolute = [6.14 + 0, 3.14 + 0])', 'arrayItem', 0, ''],
|
||||||
['xLine(endAbsolute = 8)', '', '', '/*xAbs*/'],
|
['xLine(endAbsolute = 8)', 'singleValue', '', '/*xAbs*/'],
|
||||||
['yLine(endAbsolute = 5)', '', '', '/*yAbs*/'],
|
['yLine(endAbsolute = 5)', 'singleValue', '', '/*yAbs*/'],
|
||||||
['yLine(length = 3.14, tag = $a)', '', '', '/*yRel*/'],
|
['yLine(length = 3.14, tag = $a)', 'singleValue', '', '/*yRel*/'],
|
||||||
['xLine(length = 3.14)', '', '', '/*xRel*/'],
|
['xLine(length = 3.14)', 'singleValue', '', '/*xRel*/'],
|
||||||
[
|
[
|
||||||
'/*1*/ angledLine(angle = 3, lengthX = 3.14 + 0)',
|
'/*1*/ angledLine(angle = 3, lengthX = 3.14 + 0)',
|
||||||
'labeledArg',
|
'labeledArg',
|
||||||
@ -731,11 +735,12 @@ describe('Testing removeSingleConstraintInfo', () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
'tangentialArc(endAbsolute = [3.14 + 0, 13.14])',
|
'tangentialArc(endAbsolute = [3.14 + 0, 13.14])',
|
||||||
'labeledArg',
|
'labeledArgArrayItem',
|
||||||
'endAbsolute',
|
'endAbsolute',
|
||||||
'',
|
'',
|
||||||
],
|
],
|
||||||
] as const)(
|
]
|
||||||
|
test.each(cases)(
|
||||||
'stdlib fn: %s',
|
'stdlib fn: %s',
|
||||||
async (expectedFinish, key, value, commentLabel) => {
|
async (expectedFinish, key, value, commentLabel) => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
@ -749,19 +754,26 @@ describe('Testing removeSingleConstraintInfo', () => {
|
|||||||
const range = topLevelRange(start + 1, start + lineOfInterest.length)
|
const range = topLevelRange(start + 1, start + lineOfInterest.length)
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
let argPosition: SimplifiedArgDetails
|
let argPosition: SimplifiedArgDetails
|
||||||
if (key === 'arrayIndex' && typeof value === 'number') {
|
if (key === 'arrayItem' && typeof value === 'number') {
|
||||||
argPosition = {
|
argPosition = {
|
||||||
type: 'arrayItem',
|
type: 'arrayItem',
|
||||||
index: value === 0 ? 0 : 1,
|
index: value === 0 ? 0 : 1,
|
||||||
}
|
}
|
||||||
} else if (key === '') {
|
} else if (key === 'singleValue') {
|
||||||
argPosition = {
|
argPosition = {
|
||||||
type: 'singleValue',
|
type: 'singleValue',
|
||||||
}
|
}
|
||||||
} else if (key === 'labeledArg') {
|
} else if (key === 'labeledArg' && typeof value === 'string') {
|
||||||
argPosition = {
|
argPosition = {
|
||||||
type: 'labeledArg',
|
type: 'labeledArg',
|
||||||
key: value,
|
key: value as any,
|
||||||
|
}
|
||||||
|
} else if (key === 'labeledArgArrayItem') {
|
||||||
|
console.log()
|
||||||
|
argPosition = {
|
||||||
|
type: 'labeledArgArrayItem',
|
||||||
|
key: value as any,
|
||||||
|
index: 1,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('argPosition is undefined')
|
throw new Error('argPosition is undefined')
|
||||||
|
@ -1038,7 +1038,6 @@ export function updatePathToNodesAfterEdit(
|
|||||||
if (err(oldNodeResult)) return oldNodeResult
|
if (err(oldNodeResult)) return oldNodeResult
|
||||||
const oldNode = oldNodeResult.node
|
const oldNode = oldNodeResult.node
|
||||||
const varName = oldNode.declaration.id.name
|
const varName = oldNode.declaration.id.name
|
||||||
console.log('varName', varName)
|
|
||||||
|
|
||||||
// Find the old and new indices for this variable
|
// Find the old and new indices for this variable
|
||||||
const oldIndex = oldVarDecls.get(varName)
|
const oldIndex = oldVarDecls.get(varName)
|
||||||
|
@ -571,7 +571,11 @@ describe('testing getConstraintInfo', () => {
|
|||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [expect.any(Number), expect.any(Number), 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),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'tangentialArc',
|
stdLibFnName: 'tangentialArc',
|
||||||
},
|
},
|
||||||
@ -580,7 +584,11 @@ describe('testing getConstraintInfo', () => {
|
|||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '13.14',
|
value: '13.14',
|
||||||
sourceRange: [expect.any(Number), expect.any(Number), 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),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'tangentialArc',
|
stdLibFnName: 'tangentialArc',
|
||||||
},
|
},
|
||||||
@ -1085,7 +1093,11 @@ describe('testing getConstraintInfo', () => {
|
|||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3.14 + 0',
|
value: '3.14 + 0',
|
||||||
sourceRange: [expect.any(Number), expect.any(Number), 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),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'tangentialArc',
|
stdLibFnName: 'tangentialArc',
|
||||||
},
|
},
|
||||||
@ -1094,7 +1106,11 @@ describe('testing getConstraintInfo', () => {
|
|||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '13.14 + 0',
|
value: '13.14 + 0',
|
||||||
sourceRange: [expect.any(Number), expect.any(Number), 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),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'tangentialArc',
|
stdLibFnName: 'tangentialArc',
|
||||||
},
|
},
|
||||||
|
@ -98,7 +98,7 @@ import type { EdgeCutInfo } from '@src/machines/modelingMachine'
|
|||||||
const STRAIGHT_SEGMENT_ERR = new Error(
|
const STRAIGHT_SEGMENT_ERR = new Error(
|
||||||
'Invalid input, expected "straight-segment"'
|
'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(
|
const CIRCLE_THREE_POINT_SEGMENT_ERR = new Error(
|
||||||
'Invalid input, expected "circle-three-point-segment"'
|
'Invalid input, expected "circle-three-point-segment"'
|
||||||
)
|
)
|
||||||
@ -1026,13 +1026,15 @@ export const tangentialArc: SketchLineHelperKw = {
|
|||||||
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
||||||
const result = replaceExistingCallback([
|
const result = replaceExistingCallback([
|
||||||
{
|
{
|
||||||
type: 'arrayItem',
|
type: 'labeledArgArrayItem',
|
||||||
|
key: ARG_END_ABSOLUTE,
|
||||||
index: 0,
|
index: 0,
|
||||||
argType: 'xAbsolute',
|
argType: 'xAbsolute',
|
||||||
expr: toX,
|
expr: toX,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'arrayItem',
|
type: 'labeledArgArrayItem',
|
||||||
|
key: ARG_END_ABSOLUTE,
|
||||||
index: 1,
|
index: 1,
|
||||||
argType: 'yAbsolute',
|
argType: 'yAbsolute',
|
||||||
expr: toY,
|
expr: toY,
|
||||||
@ -1145,28 +1147,32 @@ export const tangentialArc: SketchLineHelperKw = {
|
|||||||
['arg', LABELED_ARG_FIELD],
|
['arg', LABELED_ARG_FIELD],
|
||||||
]
|
]
|
||||||
if (expr.type !== 'ArrayExpression' || expr.elements.length < 2) {
|
if (expr.type !== 'ArrayExpression' || expr.elements.length < 2) {
|
||||||
constraints.push(
|
constraints.push({
|
||||||
constrainInfo(
|
stdLibFnName: 'tangentialArc',
|
||||||
'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isNotLiteralArrayOrStatic(expr),
|
isConstrained: isNotLiteralArrayOrStatic(expr),
|
||||||
code.slice(expr.start, expr.end),
|
sourceRange: topLevelRange(expr.start, expr.end),
|
||||||
'tangentialArc',
|
pathToNode: pathToArg,
|
||||||
0,
|
value: code.slice(expr.start, expr.end),
|
||||||
topLevelRange(expr.start, expr.end),
|
argPosition: {
|
||||||
pathToArg
|
type: 'labeledArgArrayItem',
|
||||||
)
|
index: 0,
|
||||||
)
|
key: ARG_END_ABSOLUTE,
|
||||||
constraints.push(
|
},
|
||||||
constrainInfo(
|
})
|
||||||
'yAbsolute',
|
constraints.push({
|
||||||
isNotLiteralArrayOrStatic(expr),
|
stdLibFnName: 'tangentialArc',
|
||||||
code.slice(expr.start, expr.end),
|
type: 'yAbsolute',
|
||||||
'tangentialArc',
|
isConstrained: isNotLiteralArrayOrStatic(expr),
|
||||||
1,
|
sourceRange: topLevelRange(expr.start, expr.end),
|
||||||
topLevelRange(expr.start, expr.end),
|
pathToNode: pathToArg,
|
||||||
pathToArg
|
value: code.slice(expr.start, expr.end),
|
||||||
)
|
argPosition: {
|
||||||
)
|
type: 'labeledArgArrayItem',
|
||||||
|
index: 1,
|
||||||
|
key: ARG_END_ABSOLUTE,
|
||||||
|
},
|
||||||
|
})
|
||||||
return constraints
|
return constraints
|
||||||
}
|
}
|
||||||
const pathToX: PathToNode = [
|
const pathToX: PathToNode = [
|
||||||
@ -1181,72 +1187,79 @@ export const tangentialArc: SketchLineHelperKw = {
|
|||||||
]
|
]
|
||||||
const exprX = expr.elements[0]
|
const exprX = expr.elements[0]
|
||||||
const exprY = expr.elements[1]
|
const exprY = expr.elements[1]
|
||||||
constraints.push(
|
constraints.push({
|
||||||
constrainInfo(
|
stdLibFnName: 'tangentialArc',
|
||||||
'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isNotLiteralArrayOrStatic(exprX),
|
isConstrained: isNotLiteralArrayOrStatic(exprX),
|
||||||
code.slice(exprX.start, exprX.end),
|
sourceRange: topLevelRange(exprX.start, exprX.end),
|
||||||
'tangentialArc',
|
pathToNode: pathToX,
|
||||||
0,
|
value: code.slice(exprX.start, exprX.end),
|
||||||
topLevelRange(exprX.start, exprX.end),
|
argPosition: {
|
||||||
pathToX
|
type: 'labeledArgArrayItem',
|
||||||
)
|
index: 0,
|
||||||
)
|
key: ARG_END_ABSOLUTE,
|
||||||
constraints.push(
|
},
|
||||||
constrainInfo(
|
})
|
||||||
'yAbsolute',
|
constraints.push({
|
||||||
isNotLiteralArrayOrStatic(exprY),
|
stdLibFnName: 'tangentialArc',
|
||||||
code.slice(exprY.start, exprY.end),
|
type: 'yAbsolute',
|
||||||
'tangentialArc',
|
isConstrained: isNotLiteralArrayOrStatic(exprY),
|
||||||
1,
|
sourceRange: topLevelRange(exprY.start, exprY.end),
|
||||||
topLevelRange(exprY.start, exprY.end),
|
pathToNode: pathToY,
|
||||||
pathToY
|
value: code.slice(exprY.start, exprY.end),
|
||||||
)
|
argPosition: {
|
||||||
)
|
type: 'labeledArgArrayItem',
|
||||||
|
index: 1,
|
||||||
|
key: ARG_END_ABSOLUTE,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return constraints
|
return constraints
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
export const circle: SketchLineHelperKw = {
|
export const circle: SketchLineHelperKw = {
|
||||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
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 { center, radius } = segmentInput
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
|
|
||||||
|
// Try to get the pipe expression first
|
||||||
const nodeMeta = getNodeFromPath<PipeExpression>(
|
const nodeMeta = getNodeFromPath<PipeExpression>(
|
||||||
_node,
|
_node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
'PipeExpression'
|
'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 { node: pipe } = nodeMeta
|
||||||
|
|
||||||
const x = createLiteral(roundOff(center[0], 2))
|
const x = createLiteral(roundOff(center[0], 2))
|
||||||
const y = createLiteral(roundOff(center[1], 2))
|
const y = createLiteral(roundOff(center[1], 2))
|
||||||
|
|
||||||
const radiusExp = createLiteral(roundOff(radius, 2))
|
const radiusExp = createLiteral(roundOff(radius, 2))
|
||||||
|
const centerArray = createArrayExpression([x, y])
|
||||||
|
|
||||||
if (replaceExistingCallback) {
|
if (replaceExistingCallback) {
|
||||||
const result = replaceExistingCallback([
|
const result = replaceExistingCallback([
|
||||||
{
|
{
|
||||||
type: 'arrayInObject',
|
type: 'labeledArgArrayItem',
|
||||||
index: 0,
|
|
||||||
key: 'center',
|
|
||||||
argType: 'xAbsolute',
|
argType: 'xAbsolute',
|
||||||
|
key: ARG_CIRCLE_CENTER,
|
||||||
|
index: 0,
|
||||||
expr: x,
|
expr: x,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'arrayInObject',
|
type: 'labeledArgArrayItem',
|
||||||
index: 1,
|
|
||||||
key: 'center',
|
|
||||||
argType: 'yAbsolute',
|
argType: 'yAbsolute',
|
||||||
|
key: ARG_CIRCLE_CENTER,
|
||||||
|
index: 1,
|
||||||
expr: y,
|
expr: y,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'objectProperty',
|
type: 'labeledArg',
|
||||||
key: 'radius',
|
|
||||||
argType: 'radius',
|
argType: 'radius',
|
||||||
|
key: ARG_RADIUS,
|
||||||
expr: radiusExp,
|
expr: radiusExp,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@ -1254,7 +1267,25 @@ export const circle: SketchLineHelperKw = {
|
|||||||
const { callExp, valueUsedInTransform } = result
|
const { callExp, valueUsedInTransform } = result
|
||||||
|
|
||||||
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
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
|
pipe.body[callIndex] = callExp
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
@ -1262,10 +1293,109 @@ export const circle: SketchLineHelperKw = {
|
|||||||
valueUsedInTransform,
|
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 }) => {
|
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 { center, radius } = input
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const nodeMeta = getNodeFromPath<CallExpressionKw>(_node, pathToNode)
|
const nodeMeta = getNodeFromPath<CallExpressionKw>(_node, pathToNode)
|
||||||
@ -1301,6 +1431,7 @@ export const circle: SketchLineHelperKw = {
|
|||||||
['arguments', 'CallExpressionKw'],
|
['arguments', 'CallExpressionKw'],
|
||||||
[centerInfo.argIndex, ARG_INDEX_FIELD],
|
[centerInfo.argIndex, ARG_INDEX_FIELD],
|
||||||
['arg', LABELED_ARG_FIELD],
|
['arg', LABELED_ARG_FIELD],
|
||||||
|
['elements', 'ArrayExpression'],
|
||||||
]
|
]
|
||||||
const pathToRadiusLiteral: PathToNode = [
|
const pathToRadiusLiteral: PathToNode = [
|
||||||
...pathToNode,
|
...pathToNode,
|
||||||
@ -1317,16 +1448,19 @@ export const circle: SketchLineHelperKw = {
|
|||||||
[1, 'index'],
|
[1, 'index'],
|
||||||
]
|
]
|
||||||
|
|
||||||
return [
|
const constraints: ConstrainInfo[] = [
|
||||||
constrainInfo(
|
{
|
||||||
'radius',
|
stdLibFnName: 'circle',
|
||||||
isNotLiteralArrayOrStatic(radiusInfo.expr),
|
type: 'radius',
|
||||||
code.slice(radiusInfo.expr.start, radiusInfo.expr.end),
|
isConstrained: isNotLiteralArrayOrStatic(radiusInfo.expr),
|
||||||
'circle',
|
sourceRange: topLevelRange(radiusInfo.expr.start, radiusInfo.expr.end),
|
||||||
'radius',
|
pathToNode: pathToRadiusLiteral,
|
||||||
topLevelRange(radiusInfo.expr.start, radiusInfo.expr.end),
|
value: code.slice(radiusInfo.expr.start, radiusInfo.expr.end),
|
||||||
pathToRadiusLiteral
|
argPosition: {
|
||||||
),
|
type: 'labeledArg',
|
||||||
|
key: ARG_RADIUS,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
stdLibFnName: 'circle',
|
stdLibFnName: 'circle',
|
||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
@ -1341,9 +1475,9 @@ export const circle: SketchLineHelperKw = {
|
|||||||
centerInfo.expr.elements[0].end
|
centerInfo.expr.elements[0].end
|
||||||
),
|
),
|
||||||
argPosition: {
|
argPosition: {
|
||||||
type: 'arrayInObject',
|
type: 'labeledArgArrayItem',
|
||||||
index: 0,
|
index: 0,
|
||||||
key: 'center',
|
key: ARG_CIRCLE_CENTER,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1360,12 +1494,13 @@ export const circle: SketchLineHelperKw = {
|
|||||||
centerInfo.expr.elements[1].end
|
centerInfo.expr.elements[1].end
|
||||||
),
|
),
|
||||||
argPosition: {
|
argPosition: {
|
||||||
type: 'arrayInObject',
|
type: 'labeledArgArrayItem',
|
||||||
index: 1,
|
index: 1,
|
||||||
key: 'center',
|
key: 'center',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
return constraints
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1378,7 +1513,7 @@ export const arc: SketchLineHelperKw = {
|
|||||||
replaceExistingCallback,
|
replaceExistingCallback,
|
||||||
spliceBetween,
|
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 { center, radius, from, to } = segmentInput
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
|
|
||||||
@ -1512,7 +1647,7 @@ export const arc: SketchLineHelperKw = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateArgs: ({ node, pathToNode, input }) => {
|
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 { center, radius, from, to } = input
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const nodeMeta = getNodeFromPath<CallExpressionKw>(_node, pathToNode)
|
const nodeMeta = getNodeFromPath<CallExpressionKw>(_node, pathToNode)
|
||||||
@ -1653,7 +1788,7 @@ export const arcTo: SketchLineHelperKw = {
|
|||||||
spliceBetween,
|
spliceBetween,
|
||||||
}) => {
|
}) => {
|
||||||
if (segmentInput.type !== 'circle-three-point-segment')
|
if (segmentInput.type !== 'circle-three-point-segment')
|
||||||
return ARC_SEGMENT_ERR
|
return ARC_SEGMENT_ERR()
|
||||||
|
|
||||||
const { p2, p3 } = segmentInput
|
const { p2, p3 } = segmentInput
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
@ -1669,29 +1804,43 @@ export const arcTo: SketchLineHelperKw = {
|
|||||||
// p1 is the start point (from the previous segment)
|
// p1 is the start point (from the previous segment)
|
||||||
// p2 is the interiorAbsolute point
|
// p2 is the interiorAbsolute point
|
||||||
// p3 is the end point
|
// p3 is the end point
|
||||||
const interiorAbsolute = createArrayExpression([
|
const p2x = createLiteral(roundOff(p2[0], 2))
|
||||||
createLiteral(roundOff(p2[0], 2)),
|
const p2y = createLiteral(roundOff(p2[1], 2))
|
||||||
createLiteral(roundOff(p2[1], 2)),
|
const interiorAbsolute = createArrayExpression([p2x, p2y])
|
||||||
])
|
|
||||||
|
|
||||||
const end = createArrayExpression([
|
const p3x = createLiteral(roundOff(p3[0], 2))
|
||||||
createLiteral(roundOff(p3[0], 2)),
|
const p3y = createLiteral(roundOff(p3[1], 2))
|
||||||
createLiteral(roundOff(p3[1], 2)),
|
const end = createArrayExpression([p3x, p3y])
|
||||||
])
|
|
||||||
|
|
||||||
if (replaceExistingCallback) {
|
if (replaceExistingCallback) {
|
||||||
const result = replaceExistingCallback([
|
const result = replaceExistingCallback([
|
||||||
{
|
{
|
||||||
type: 'objectProperty',
|
type: 'labeledArgArrayItem',
|
||||||
key: 'interiorAbsolute',
|
key: 'interiorAbsolute',
|
||||||
|
index: 0,
|
||||||
argType: 'xAbsolute',
|
argType: 'xAbsolute',
|
||||||
expr: createLiteral(0), // This is a workaround, the actual value will be set later
|
expr: p2x,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'objectProperty',
|
type: 'labeledArgArrayItem',
|
||||||
key: 'endAbsolute',
|
key: 'interiorAbsolute',
|
||||||
|
index: 1,
|
||||||
argType: 'yAbsolute',
|
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
|
if (err(result)) return result
|
||||||
@ -1757,7 +1906,7 @@ export const arcTo: SketchLineHelperKw = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateArgs: ({ node, pathToNode, input }) => {
|
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 { p2, p3 } = input
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
@ -1866,8 +2015,8 @@ export const arcTo: SketchLineHelperKw = {
|
|||||||
),
|
),
|
||||||
stdLibFnName: 'arc',
|
stdLibFnName: 'arc',
|
||||||
argPosition: {
|
argPosition: {
|
||||||
type: 'arrayInObject',
|
type: 'labeledArgArrayItem',
|
||||||
key: 'interiorAbsolute',
|
key: ARG_INTERIOR_ABSOLUTE,
|
||||||
index: 0,
|
index: 0,
|
||||||
},
|
},
|
||||||
sourceRange: topLevelRange(
|
sourceRange: topLevelRange(
|
||||||
@ -1875,7 +2024,7 @@ export const arcTo: SketchLineHelperKw = {
|
|||||||
interiorAbsoluteArr.elements[0].end
|
interiorAbsoluteArr.elements[0].end
|
||||||
),
|
),
|
||||||
pathToNode: pathToInteriorX,
|
pathToNode: pathToInteriorX,
|
||||||
filterValue: 'interiorAbsolute',
|
filterValue: ARG_INTERIOR_ABSOLUTE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
@ -1888,8 +2037,8 @@ export const arcTo: SketchLineHelperKw = {
|
|||||||
),
|
),
|
||||||
stdLibFnName: 'arc',
|
stdLibFnName: 'arc',
|
||||||
argPosition: {
|
argPosition: {
|
||||||
type: 'arrayInObject',
|
type: 'labeledArgArrayItem',
|
||||||
key: 'interiorAbsolute',
|
key: ARG_INTERIOR_ABSOLUTE,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
sourceRange: topLevelRange(
|
sourceRange: topLevelRange(
|
||||||
@ -1897,7 +2046,7 @@ export const arcTo: SketchLineHelperKw = {
|
|||||||
interiorAbsoluteArr.elements[1].end
|
interiorAbsoluteArr.elements[1].end
|
||||||
),
|
),
|
||||||
pathToNode: pathToInteriorY,
|
pathToNode: pathToInteriorY,
|
||||||
filterValue: 'interiorAbsolute',
|
filterValue: ARG_INTERIOR_ABSOLUTE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
@ -1905,8 +2054,8 @@ export const arcTo: SketchLineHelperKw = {
|
|||||||
value: code.slice(endArr.elements[0].start, endArr.elements[0].end),
|
value: code.slice(endArr.elements[0].start, endArr.elements[0].end),
|
||||||
stdLibFnName: 'arc',
|
stdLibFnName: 'arc',
|
||||||
argPosition: {
|
argPosition: {
|
||||||
type: 'arrayInObject',
|
type: 'labeledArgArrayItem',
|
||||||
key: 'end',
|
key: 'endAbsolute',
|
||||||
index: 0,
|
index: 0,
|
||||||
},
|
},
|
||||||
sourceRange: topLevelRange(
|
sourceRange: topLevelRange(
|
||||||
@ -1914,7 +2063,7 @@ export const arcTo: SketchLineHelperKw = {
|
|||||||
endArr.elements[0].end
|
endArr.elements[0].end
|
||||||
),
|
),
|
||||||
pathToNode: pathToEndX,
|
pathToNode: pathToEndX,
|
||||||
filterValue: 'end',
|
filterValue: 'endAbsolute',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
@ -1922,8 +2071,8 @@ export const arcTo: SketchLineHelperKw = {
|
|||||||
value: code.slice(endArr.elements[1].start, endArr.elements[1].end),
|
value: code.slice(endArr.elements[1].start, endArr.elements[1].end),
|
||||||
stdLibFnName: 'arc',
|
stdLibFnName: 'arc',
|
||||||
argPosition: {
|
argPosition: {
|
||||||
type: 'arrayInObject',
|
type: 'labeledArgArrayItem',
|
||||||
key: 'end',
|
key: 'endAbsolute',
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
sourceRange: topLevelRange(
|
sourceRange: topLevelRange(
|
||||||
@ -1931,7 +2080,7 @@ export const arcTo: SketchLineHelperKw = {
|
|||||||
endArr.elements[1].end
|
endArr.elements[1].end
|
||||||
),
|
),
|
||||||
pathToNode: pathToEndY,
|
pathToNode: pathToEndY,
|
||||||
filterValue: 'end',
|
filterValue: 'endAbsolute',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3052,6 +3201,54 @@ export function changeSketchArguments(
|
|||||||
return new Error(`not a sketch line helper: ${fnName}`)
|
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.
|
* 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.
|
* 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
|
callExp: CallExpressionKw
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
val: [Expr, Expr, Expr]
|
val: [Expr, Expr, Expr] | [Expr, Expr]
|
||||||
tag?: Expr
|
tag?: Expr
|
||||||
}
|
}
|
||||||
| Error => {
|
| Error => {
|
||||||
const angleStart = findKwArg(ARG_ANGLE_START, callExp)
|
const angleStart = findKwArg(ARG_ANGLE_START, callExp)
|
||||||
const angleEnd = findKwArg(ARG_ANGLE_END, callExp)
|
const angleEnd = findKwArg(ARG_ANGLE_END, callExp)
|
||||||
const radius = findKwArg(ARG_RADIUS, callExp)
|
const radius = findKwArg(ARG_RADIUS, callExp)
|
||||||
if (!angleStart || !angleEnd || !radius) {
|
const isMissingAnyAngleKwArgs = !angleStart || !angleEnd || !radius
|
||||||
return new Error(`arc call needs angleStart, angleEnd, and radius args`)
|
|
||||||
}
|
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)
|
const tag = findKwArg(ARG_TAG, callExp)
|
||||||
return { val: [angleStart, angleEnd, radius], tag }
|
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 {
|
import {
|
||||||
createFirstArg,
|
createFirstArg,
|
||||||
fnNameToTooltip,
|
fnNameToTooltip,
|
||||||
|
fnNameToToolTipFromSegment,
|
||||||
getAngledLine,
|
getAngledLine,
|
||||||
getAngledLineThatIntersects,
|
getAngledLineThatIntersects,
|
||||||
getArc,
|
getArc,
|
||||||
@ -79,7 +80,12 @@ import type {
|
|||||||
} from '@src/lang/wasm'
|
} from '@src/lang/wasm'
|
||||||
import { sketchFromKclValue } from '@src/lang/wasm'
|
import { sketchFromKclValue } from '@src/lang/wasm'
|
||||||
import type { Selections } from '@src/lib/selections'
|
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 {
|
import {
|
||||||
allLabels,
|
allLabels,
|
||||||
getAngle,
|
getAngle,
|
||||||
@ -1420,40 +1426,114 @@ export function removeSingleConstraint({
|
|||||||
'This code path only works with callExpressionKw but a positional call was somehow passed'
|
'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
|
const toReplace = inputToReplace.key
|
||||||
let argsPreFilter = inputs.map((arg) => {
|
|
||||||
if (arg.type !== 'labeledArg') {
|
// Basic approach: get the current args, filter out the TAG arg if it exists,
|
||||||
return new Error(`arg isn't a labeled arg: ${arg.type}`)
|
// replace the targetArg with the raw value
|
||||||
}
|
|
||||||
const k = arg.key
|
// 1. Filter out any existing tag argument since it will be handled separately
|
||||||
if (k !== toReplace) {
|
const filteredArgs = existingArgs.filter(
|
||||||
return createLabeledArg(k, arg.expr)
|
(arg) => arg.label.name !== ARG_TAG
|
||||||
} else {
|
)
|
||||||
|
|
||||||
|
// 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(
|
const rawArgVersion = rawArgs.find(
|
||||||
(a) => a.type === 'labeledArg' && a.key === k
|
(a) => a.type === 'labeledArg' && a.key === toReplace
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!rawArgVersion) {
|
if (!rawArgVersion) {
|
||||||
return new Error(
|
console.error(`Raw arg version not found for key: ${toReplace}`)
|
||||||
`raw arg version not found while trying to remove constraint: ${JSON.stringify(arg)}`
|
// 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
|
const noncode = callExp.node.nonCodeMeta
|
||||||
|
|
||||||
|
// Use the existing unlabeled argument if available, otherwise use undefined
|
||||||
|
const unlabeledArg = callExp.node.unlabeled ?? undefined
|
||||||
|
|
||||||
return createStdlibCallExpressionKw(
|
return createStdlibCallExpressionKw(
|
||||||
callExp.node.callee.name.name as ToolTip,
|
callExp.node.callee.name.name as ToolTip,
|
||||||
args,
|
labeledArgs,
|
||||||
tag,
|
tag,
|
||||||
undefined,
|
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,
|
undefined,
|
||||||
|
unlabeledArg,
|
||||||
noncode
|
noncode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -2057,6 +2137,15 @@ export function transformAstSketchLines({
|
|||||||
argType: a.type,
|
argType: a.type,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
|
case 'labeledArgArrayItem':
|
||||||
|
inputs.push({
|
||||||
|
type: 'labeledArgArrayItem',
|
||||||
|
key: a.argPosition.key,
|
||||||
|
index: a.argPosition.index,
|
||||||
|
expr: nodeMeta.node,
|
||||||
|
argType: a.type,
|
||||||
|
})
|
||||||
|
break
|
||||||
case 'arrayInObject':
|
case 'arrayInObject':
|
||||||
inputs.push({
|
inputs.push({
|
||||||
type: 'arrayInObject',
|
type: 'arrayInObject',
|
||||||
@ -2105,13 +2194,9 @@ export function transformAstSketchLines({
|
|||||||
}
|
}
|
||||||
const { to, from } = seg
|
const { to, from } = seg
|
||||||
// Note to ADAM: Here is where the replaceExisting call gets sent.
|
// Note to ADAM: Here is where the replaceExisting call gets sent.
|
||||||
const replacedSketchLine = replaceSketchLine({
|
const segmentInput: Parameters<
|
||||||
node: node,
|
typeof replaceSketchLine
|
||||||
variables: memVars,
|
>[0]['segmentInput'] =
|
||||||
pathToNode: _pathToNode,
|
|
||||||
referencedSegment,
|
|
||||||
fnName: transformTo || (call.node.callee.name.name as ToolTip),
|
|
||||||
segmentInput:
|
|
||||||
seg.type === 'Circle'
|
seg.type === 'Circle'
|
||||||
? {
|
? {
|
||||||
type: 'arc-segment',
|
type: 'arc-segment',
|
||||||
@ -2132,8 +2217,19 @@ export function transformAstSketchLines({
|
|||||||
type: 'straight-segment',
|
type: 'straight-segment',
|
||||||
to,
|
to,
|
||||||
from,
|
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) =>
|
replaceExistingCallback: (rawArgs) =>
|
||||||
callBack({
|
callBack({
|
||||||
referenceSegName: _referencedSegmentName,
|
referenceSegName: _referencedSegmentName,
|
||||||
|
@ -171,6 +171,14 @@ interface LabeledArg<T> {
|
|||||||
expr: T
|
expr: T
|
||||||
overrideExpr?: Node<Expr>
|
overrideExpr?: Node<Expr>
|
||||||
}
|
}
|
||||||
|
interface LabeledArgArrayItem<T> {
|
||||||
|
type: 'labeledArgArrayItem'
|
||||||
|
key: InputArgKeys
|
||||||
|
index: 0 | 1
|
||||||
|
argType: LineInputsType
|
||||||
|
expr: T
|
||||||
|
overrideExpr?: Node<Expr>
|
||||||
|
}
|
||||||
|
|
||||||
type _InputArg<T> =
|
type _InputArg<T> =
|
||||||
| SingleValueInput<T>
|
| SingleValueInput<T>
|
||||||
@ -179,6 +187,7 @@ type _InputArg<T> =
|
|||||||
| ArrayOrObjItemInput<T>
|
| ArrayOrObjItemInput<T>
|
||||||
| ArrayInObject<T>
|
| ArrayInObject<T>
|
||||||
| LabeledArg<T>
|
| LabeledArg<T>
|
||||||
|
| LabeledArgArrayItem<T>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link RawArg.expr} is the current expression for each of the args for a segment
|
* {@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<ArrayOrObjItemInput<null>, 'expr' | 'argType'>
|
||||||
| Omit<ArrayInObject<null>, 'expr' | 'argType'>
|
| Omit<ArrayInObject<null>, 'expr' | 'argType'>
|
||||||
| Omit<LabeledArg<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.).
|
* Represents the result of creating a sketch expression (line, tangentialArc, angledLine, circle, etc.).
|
||||||
|
@ -273,7 +273,7 @@ export type SegmentOverlayPayload =
|
|||||||
}
|
}
|
||||||
| { type: 'clear' }
|
| { type: 'clear' }
|
||||||
| {
|
| {
|
||||||
type: 'add-many'
|
type: 'set-many'
|
||||||
overlays: SegmentOverlays
|
overlays: SegmentOverlays
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user