Compare commits

...

2 Commits

Author SHA1 Message Date
5f7a75a327 #7408 Can not pick sketch plane using the feature tree and related improvements (#7609)
* Add ability to pick default plane in feature tree in 'Sketch no face' mode

* add ability to select deoffset plane where starting a new sketch

* use selectDefaultSketchPlane

* refactor: remove some duplication

* warning cleanups

* feature tree items selectable depedngin on no face sketch mode

* lint

* fix small jump because of border:none when going into and back from 'No face sketch' mode

* grey out items other than offset planes in 'No face sketch' mode

* start sketching on plane in context menu

* sketch on offset plane with context menu

* add ability to right click on default plane and start sketch on it

* default planes in feature tree should be selectable because of right click context menu

* add right click Start sketch option for selected plane on the canvas

* selectDefaultSketchPlane returns error now

* circular deps

* move select functions to lib/selections.ts to avoid circular deps

* add test for clicking on feature tree after starting a new sketch

* graphite suggestion

* fix bug of not being able to create offset plane using another offset plane with command bar

* add ability to select default plane on feature when going through the Offset plane command bar flow
2025-07-04 13:14:12 -04:00
01230b0aa1 [Bug fix]: Do not store token in window! (#7671)
* fix: we really cannot do this

* fix: limiting footprint
2025-07-04 11:57:29 -04:00
9 changed files with 444 additions and 160 deletions

View File

@ -187,6 +187,68 @@ sketch001 = startProfile(sketch002, at = [12.34, -12.34])
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
})
test('Can select planes in Feature Tree after Start Sketch', async ({
page,
homePage,
toolbar,
editor,
}) => {
// Load the app with empty code
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`plane001 = offsetPlane(XZ, offset = 5)`
)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await test.step('Click Start Sketch button', async () => {
await page.getByRole('button', { name: 'Start Sketch' }).click()
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible()
await expect(page.getByText('select a plane or face')).toBeVisible()
})
await test.step('Open feature tree and select Front plane (XZ)', async () => {
await toolbar.openFeatureTreePane()
await page.getByRole('button', { name: 'Front plane' }).click()
await page.waitForTimeout(600)
await expect(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(XZ)')
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
})
await test.step('Click Start Sketch button again', async () => {
await page.getByRole('button', { name: 'Start Sketch' }).click()
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible()
})
await test.step('Select the offset plane', async () => {
await toolbar.openFeatureTreePane()
await page.getByRole('button', { name: 'Offset plane' }).click()
await page.waitForTimeout(600)
await expect(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(plane001)')
})
})
test('Can edit segments by dragging their handles', () => {
const doEditSegmentsByDraggingHandle = async (
page: Page,

View File

@ -24,7 +24,12 @@ import {
getOperationVariableName,
stdLibMap,
} from '@src/lib/operations'
import { editorManager, kclManager, rustContext } from '@src/lib/singletons'
import {
editorManager,
kclManager,
rustContext,
sceneInfra,
} from '@src/lib/singletons'
import {
featureTreeMachine,
featureTreeMachineDefaultContext,
@ -34,11 +39,20 @@ import {
kclEditorActor,
selectionEventSelector,
} from '@src/machines/kclEditorMachine'
import type { Plane } from '@rust/kcl-lib/bindings/Artifact'
import {
selectDefaultSketchPlane,
selectOffsetSketchPlane,
} from '@src/lib/selections'
import type { DefaultPlaneStr } from '@src/lib/planes'
export const FeatureTreePane = () => {
const isEditorMounted = useSelector(kclEditorActor, editorIsMountedSelector)
const lastSelectionEvent = useSelector(kclEditorActor, selectionEventSelector)
const { send: modelingSend, state: modelingState } = useModelingContext()
const sketchNoFace = modelingState.matches('Sketch no face')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_featureTreeState, featureTreeSend] = useMachine(
featureTreeMachine.provide({
@ -195,6 +209,7 @@ export const FeatureTreePane = () => {
key={key}
item={operation}
send={featureTreeSend}
sketchNoFace={sketchNoFace}
/>
)
})}
@ -251,6 +266,7 @@ const OperationItemWrapper = ({
customSuffix,
className,
selectable = true,
greyedOut = false,
...props
}: React.HTMLAttributes<HTMLButtonElement> & {
icon: CustomIconName
@ -262,18 +278,19 @@ const OperationItemWrapper = ({
menuItems?: ComponentProps<typeof ContextMenu>['items']
errors?: Diagnostic[]
selectable?: boolean
greyedOut?: boolean
}) => {
const menuRef = useRef<HTMLDivElement>(null)
return (
<div
ref={menuRef}
className={`flex select-none items-center group/item my-0 py-0.5 px-1 ${selectable ? 'focus-within:bg-primary/10 hover:bg-primary/5' : ''}`}
className={`flex select-none items-center group/item my-0 py-0.5 px-1 ${selectable ? 'focus-within:bg-primary/10 hover:bg-primary/5' : ''} ${greyedOut ? 'opacity-50 cursor-not-allowed' : ''}`}
data-testid="feature-tree-operation-item"
>
<button
{...props}
className={`reset !py-0.5 !px-1 flex-1 flex items-center gap-2 text-left text-base ${selectable ? 'border-transparent dark:border-transparent' : 'border-none cursor-default'} ${className}`}
className={`reset !py-0.5 !px-1 flex-1 flex items-center gap-2 text-left text-base ${selectable ? 'border-transparent dark:border-transparent' : '!border-transparent cursor-default'} ${className}`}
>
<CustomIcon name={icon} className="w-5 h-5 block" />
<div className="flex flex-1 items-baseline align-baseline">
@ -311,6 +328,7 @@ const OperationItemWrapper = ({
const OperationItem = (props: {
item: Operation
send: Prop<Actor<typeof featureTreeMachine>, 'send'>
sketchNoFace: boolean
}) => {
const kclContext = useKclContext()
const name = getOperationLabel(props.item)
@ -343,6 +361,12 @@ const OperationItem = (props: {
}, [kclContext.diagnostics.length])
function selectOperation() {
if (props.sketchNoFace) {
if (isOffsetPlane(props.item)) {
const artifact = findOperationArtifact(props.item)
void selectOffsetSketchPlane(artifact)
}
} else {
if (props.item.type === 'GroupEnd') {
return
}
@ -353,6 +377,7 @@ const OperationItem = (props: {
},
})
}
}
/**
* For now we can only enter the "edit" flow for the startSketchOn operation.
@ -432,6 +457,20 @@ const OperationItem = (props: {
}
}
function startSketchOnOffsetPlane() {
if (isOffsetPlane(props.item)) {
const artifact = findOperationArtifact(props.item)
if (artifact?.id) {
sceneInfra.modelingSend({
type: 'Enter sketch',
data: { forceNewSketch: true },
})
void selectOffsetSketchPlane(artifact)
}
}
}
const menuItems = useMemo(
() => [
<ContextMenuItem
@ -477,6 +516,13 @@ const OperationItem = (props: {
</ContextMenuItem>,
]
: []),
...(isOffsetPlane(props.item)
? [
<ContextMenuItem onClick={startSketchOnOffsetPlane}>
Start Sketch
</ContextMenuItem>,
]
: []),
...(props.item.type === 'StdLibCall' ||
props.item.type === 'VariableDeclaration'
? [
@ -550,22 +596,63 @@ const OperationItem = (props: {
[props.item, props.send]
)
const enabled = !props.sketchNoFace || isOffsetPlane(props.item)
return (
<OperationItemWrapper
selectable={enabled}
icon={getOperationIcon(props.item)}
name={name}
variableName={variableName}
valueDetail={valueDetail}
menuItems={menuItems}
onClick={selectOperation}
onDoubleClick={enterEditFlow}
onDoubleClick={props.sketchNoFace ? undefined : enterEditFlow} // no double click in "Sketch no face" mode
errors={errors}
greyedOut={!enabled}
/>
)
}
const DefaultPlanes = () => {
const { state: modelingState, send } = useModelingContext()
const sketchNoFace = modelingState.matches('Sketch no face')
const onClickPlane = useCallback(
(planeId: string) => {
if (sketchNoFace) {
selectDefaultSketchPlane(planeId)
} else {
const foundDefaultPlane =
rustContext.defaultPlanes !== null &&
Object.entries(rustContext.defaultPlanes).find(
([, plane]) => plane === planeId
)
if (foundDefaultPlane) {
send({
type: 'Set selection',
data: {
selectionType: 'defaultPlaneSelection',
selection: {
name: foundDefaultPlane[0] as DefaultPlaneStr,
id: planeId,
},
},
})
}
}
},
[sketchNoFace]
)
const startSketchOnDefaultPlane = useCallback((planeId: string) => {
sceneInfra.modelingSend({
type: 'Enter sketch',
data: { forceNewSketch: true },
})
selectDefaultSketchPlane(planeId)
}, [])
const defaultPlanes = rustContext.defaultPlanes
if (!defaultPlanes) return null
@ -603,7 +690,15 @@ const DefaultPlanes = () => {
customSuffix={plane.customSuffix}
icon={'plane'}
name={plane.name}
selectable={false}
selectable={true}
onClick={() => onClickPlane(plane.id)}
menuItems={[
<ContextMenuItem
onClick={() => startSketchOnDefaultPlane(plane.id)}
>
Start Sketch
</ContextMenuItem>,
]}
visibilityToggle={{
visible: modelingState.context.defaultPlaneVisibility[plane.key],
onVisibilityChange: () => {
@ -620,3 +715,17 @@ const DefaultPlanes = () => {
</div>
)
}
type StdLibCallOp = Extract<Operation, { type: 'StdLibCall' }>
const isOffsetPlane = (item: Operation): item is StdLibCallOp => {
return item.type === 'StdLibCall' && item.name === 'offsetPlane'
}
const findOperationArtifact = (item: StdLibCallOp) => {
const nodePath = JSON.stringify(item.nodePath)
const artifact = [...kclManager.artifactGraph.values()].find(
(a) => JSON.stringify((a as Plane).codeRef?.nodePath) === nodePath
)
return artifact
}

View File

@ -10,13 +10,22 @@ import {
import { useModelingContext } from '@src/hooks/useModelingContext'
import type { AxisNames } from '@src/lib/constants'
import { VIEW_NAMES_SEMANTIC } from '@src/lib/constants'
import { sceneInfra } from '@src/lib/singletons'
import { reportRejection } from '@src/lib/trap'
import { kclManager, sceneInfra } from '@src/lib/singletons'
import { err, reportRejection } from '@src/lib/trap'
import { useSettings } from '@src/lib/singletons'
import { resetCameraPosition } from '@src/lib/resetCameraPosition'
import type { Selections } from '@src/lib/selections'
import {
selectDefaultSketchPlane,
selectOffsetSketchPlane,
} from '@src/lib/selections'
export function useViewControlMenuItems() {
const { state: modelingState, send: modelingSend } = useModelingContext()
const selectedPlaneId = getCurrentPlaneId(
modelingState.context.selectionRanges
)
const settings = useSettings()
const shouldLockView =
modelingState.matches('Sketch') &&
@ -56,9 +65,35 @@ export function useViewControlMenuItems() {
Center view on selection
</ContextMenuItem>,
<ContextMenuDivider />,
<ContextMenuItem
onClick={() => {
if (selectedPlaneId) {
sceneInfra.modelingSend({
type: 'Enter sketch',
data: { forceNewSketch: true },
})
const defaultSketchPlaneSelected =
selectDefaultSketchPlane(selectedPlaneId)
if (
!err(defaultSketchPlaneSelected) &&
defaultSketchPlaneSelected
) {
return
}
const artifact = kclManager.artifactGraph.get(selectedPlaneId)
void selectOffsetSketchPlane(artifact)
}
}}
disabled={!selectedPlaneId}
>
Start sketch on selection
</ContextMenuItem>,
<ContextMenuDivider />,
<ContextMenuItemRefresh />,
],
[VIEW_NAMES_SEMANTIC, shouldLockView]
[VIEW_NAMES_SEMANTIC, shouldLockView, selectedPlaneId]
)
return menuItems
}
@ -77,3 +112,21 @@ export function ViewControlContextMenu({
/>
)
}
function getCurrentPlaneId(selectionRanges: Selections): string | null {
const defaultPlane = selectionRanges.otherSelections.find(
(selection) => typeof selection === 'object' && 'name' in selection
)
if (defaultPlane) {
return defaultPlane.id
}
const planeSelection = selectionRanges.graphSelections.find(
(selection) => selection.artifact?.type === 'plane'
)
if (planeSelection) {
return planeSelection.artifact?.id || null
}
return null
}

View File

@ -14,13 +14,15 @@ import {
import { isTopLevelModule } from '@src/lang/util'
import type { CallExpressionKw, PathToNode } from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/sourceRange'
import type { DefaultPlaneStr } from '@src/lib/planes'
import { getEventForSelectWithPoint } from '@src/lib/selections'
import {
getEventForSelectWithPoint,
selectDefaultSketchPlane,
selectOffsetSketchPlane,
} from '@src/lib/selections'
import {
editorManager,
engineCommandManager,
kclManager,
rustContext,
sceneEntitiesManager,
sceneInfra,
} from '@src/lib/singletons'
@ -96,131 +98,18 @@ export function useEngineConnectionSubscriptions() {
;(async () => {
let planeOrFaceId = data.entity_id
if (!planeOrFaceId) return
const defaultSketchPlaneSelected =
selectDefaultSketchPlane(planeOrFaceId)
if (
rustContext.defaultPlanes?.xy === planeOrFaceId ||
rustContext.defaultPlanes?.xz === planeOrFaceId ||
rustContext.defaultPlanes?.yz === planeOrFaceId ||
rustContext.defaultPlanes?.negXy === planeOrFaceId ||
rustContext.defaultPlanes?.negXz === planeOrFaceId ||
rustContext.defaultPlanes?.negYz === planeOrFaceId
!err(defaultSketchPlaneSelected) &&
defaultSketchPlaneSelected
) {
let planeId = planeOrFaceId
const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = {
[rustContext.defaultPlanes.xy]: 'XY',
[rustContext.defaultPlanes.xz]: 'XZ',
[rustContext.defaultPlanes.yz]: 'YZ',
[rustContext.defaultPlanes.negXy]: '-XY',
[rustContext.defaultPlanes.negXz]: '-XZ',
[rustContext.defaultPlanes.negYz]: '-YZ',
}
// TODO can we get this information from rust land when it creates the default planes?
// maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs)
let zAxis: [number, number, number] = [0, 0, 1]
let yAxis: [number, number, number] = [0, 1, 0]
// get unit vector from camera position to target
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
if (rustContext.defaultPlanes?.xy === planeId) {
zAxis = [0, 0, 1]
yAxis = [0, 1, 0]
if (camVector.z < 0) {
zAxis = [0, 0, -1]
planeId = rustContext.defaultPlanes?.negXy || ''
}
} else if (rustContext.defaultPlanes?.yz === planeId) {
zAxis = [1, 0, 0]
yAxis = [0, 0, 1]
if (camVector.x < 0) {
zAxis = [-1, 0, 0]
planeId = rustContext.defaultPlanes?.negYz || ''
}
} else if (rustContext.defaultPlanes?.xz === planeId) {
zAxis = [0, 1, 0]
yAxis = [0, 0, 1]
planeId = rustContext.defaultPlanes?.negXz || ''
if (camVector.y < 0) {
zAxis = [0, -1, 0]
planeId = rustContext.defaultPlanes?.xz || ''
}
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'defaultPlane',
planeId: planeId,
plane: defaultPlaneStrMap[planeId],
zAxis,
yAxis,
},
})
return
}
const artifact = kclManager.artifactGraph.get(planeOrFaceId)
if (artifact?.type === 'plane') {
const planeInfo =
await sceneEntitiesManager.getFaceDetails(planeOrFaceId)
// Apply camera-based orientation logic similar to default planes
let zAxis: [number, number, number] = [
planeInfo.z_axis.x,
planeInfo.z_axis.y,
planeInfo.z_axis.z,
]
let yAxis: [number, number, number] = [
planeInfo.y_axis.x,
planeInfo.y_axis.y,
planeInfo.y_axis.z,
]
// Get camera vector to determine which side of the plane we're viewing from
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
// Determine the canonical (absolute) plane orientation
const absZAxis: [number, number, number] = [
Math.abs(zAxis[0]),
Math.abs(zAxis[1]),
Math.abs(zAxis[2]),
]
// Find the dominant axis (like default planes do)
const maxComponent = Math.max(...absZAxis)
const dominantAxisIndex = absZAxis.indexOf(maxComponent)
// Check camera position against canonical orientation (like default planes)
const cameraComponents = [camVector.x, camVector.y, camVector.z]
let negated = cameraComponents[dominantAxisIndex] < 0
if (dominantAxisIndex === 1) {
// offset of the XZ is being weird, not sure if this is a camera bug
negated = !negated
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'offsetPlane',
zAxis,
yAxis,
position: [
planeInfo.origin.x,
planeInfo.origin.y,
planeInfo.origin.z,
].map((num) => num / sceneInfra._baseUnitMultiplier) as [
number,
number,
number,
],
planeId: planeOrFaceId,
pathToNode: artifact.codeRef.pathToNode,
negated,
},
})
if (await selectOffsetSketchPlane(artifact)) {
return
}

View File

@ -483,13 +483,13 @@ export function sketchOnExtrudedFace(
*/
export function addOffsetPlane({
node,
defaultPlane,
plane,
insertIndex,
offset,
planeName,
}: {
node: Node<Program>
defaultPlane: DefaultPlaneStr
plane: Node<Literal> | Node<Name> // Can be DefaultPlaneStr or string for offsetPlanes
insertIndex?: number
offset: Expr
planeName?: string
@ -500,11 +500,9 @@ export function addOffsetPlane({
const newPlane = createVariableDeclaration(
newPlaneName,
createCallExpressionStdLibKw(
'offsetPlane',
createLiteral(defaultPlane.toUpperCase()),
[createLabeledArg('offset', offset)]
)
createCallExpressionStdLibKw('offsetPlane', plane, [
createLabeledArg('offset', offset),
])
)
const insertAt =

View File

@ -336,14 +336,6 @@ export async function doPromptEdit({
const toastId = toast.loading('Submitting to Text-to-CAD API...')
let submitResult
// work around for @kittycad/lib not really being built for the browser
;(window as any).process = {
env: {
ZOO_API_TOKEN: token,
ZOO_HOST: withAPIBaseURL(''),
},
}
try {
submitResult = await submitPromptToEditToQueue({
prompt,

View File

@ -34,6 +34,7 @@ import {
kclManager,
rustContext,
sceneEntitiesManager,
sceneInfra,
} from '@src/lib/singletons'
import { err } from '@src/lib/trap'
import {
@ -803,3 +804,156 @@ export function getSemanticSelectionType(selectionType: Artifact['type'][]) {
return Array.from(semanticSelectionType)
}
export function selectDefaultSketchPlane(
defaultPlaneId: string
): Error | boolean {
const defaultPlanes = rustContext.defaultPlanes
if (!defaultPlanes) {
return new Error('No default planes defined in rustContext')
}
if (
![
defaultPlanes.xy,
defaultPlanes.xz,
defaultPlanes.yz,
defaultPlanes.negXy,
defaultPlanes.negXz,
defaultPlanes.negYz,
].includes(defaultPlaneId)
) {
// Supplied defaultPlaneId is not a valid default plane id
return false
}
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
// TODO can we get this information from rust land when it creates the default planes?
// maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs)
let zAxis: [number, number, number] = [0, 0, 1]
let yAxis: [number, number, number] = [0, 1, 0]
if (defaultPlanes?.xy === defaultPlaneId) {
zAxis = [0, 0, 1]
yAxis = [0, 1, 0]
if (camVector.z < 0) {
zAxis = [0, 0, -1]
defaultPlaneId = defaultPlanes?.negXy || ''
}
} else if (defaultPlanes?.yz === defaultPlaneId) {
zAxis = [1, 0, 0]
yAxis = [0, 0, 1]
if (camVector.x < 0) {
zAxis = [-1, 0, 0]
defaultPlaneId = defaultPlanes?.negYz || ''
}
} else if (defaultPlanes?.xz === defaultPlaneId) {
zAxis = [0, 1, 0]
yAxis = [0, 0, 1]
defaultPlaneId = defaultPlanes?.negXz || ''
if (camVector.y < 0) {
zAxis = [0, -1, 0]
defaultPlaneId = defaultPlanes?.xz || ''
}
}
const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = {
[defaultPlanes.xy]: 'XY',
[defaultPlanes.xz]: 'XZ',
[defaultPlanes.yz]: 'YZ',
[defaultPlanes.negXy]: '-XY',
[defaultPlanes.negXz]: '-XZ',
[defaultPlanes.negYz]: '-YZ',
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'defaultPlane',
planeId: defaultPlaneId,
plane: defaultPlaneStrMap[defaultPlaneId],
zAxis,
yAxis,
},
})
return true
}
export async function selectOffsetSketchPlane(artifact: Artifact | undefined) {
return new Promise((resolve) => {
if (artifact?.type === 'plane') {
const planeId = artifact.id
void sceneEntitiesManager
.getFaceDetails(planeId)
.then((planeInfo) => {
// Apply camera-based orientation logic similar to default planes
let zAxis: [number, number, number] = [
planeInfo.z_axis.x,
planeInfo.z_axis.y,
planeInfo.z_axis.z,
]
let yAxis: [number, number, number] = [
planeInfo.y_axis.x,
planeInfo.y_axis.y,
planeInfo.y_axis.z,
]
// Get camera vector to determine which side of the plane we're viewing from
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
// Determine the canonical (absolute) plane orientation
const absZAxis: [number, number, number] = [
Math.abs(zAxis[0]),
Math.abs(zAxis[1]),
Math.abs(zAxis[2]),
]
// Find the dominant axis (like default planes do)
const maxComponent = Math.max(...absZAxis)
const dominantAxisIndex = absZAxis.indexOf(maxComponent)
// Check camera position against canonical orientation (like default planes)
const cameraComponents = [camVector.x, camVector.y, camVector.z]
let negated = cameraComponents[dominantAxisIndex] < 0
if (dominantAxisIndex === 1) {
// offset of the XZ is being weird, not sure if this is a camera bug
negated = !negated
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'offsetPlane',
zAxis,
yAxis,
position: [
planeInfo.origin.x,
planeInfo.origin.y,
planeInfo.origin.z,
].map((num) => num / sceneInfra._baseUnitMultiplier) as [
number,
number,
number,
],
planeId,
pathToNode: artifact.codeRef.pathToNode,
negated,
},
})
resolve(true)
})
.catch((error) => {
console.error('Error getting face details:', error)
resolve(false)
})
} else {
// selectOffsetSketchPlane called with an invalid artifact type',
resolve(false)
}
})
}

View File

@ -216,9 +216,9 @@ async function getAndSyncStoredToken(input: {
['api token', !!VITE_KITTYCAD_API_TOKEN],
])
if (token) {
if (isDesktop()) {
// has just logged in, update storage
localStorage.setItem(TOKEN_PERSIST_KEY, token)
if (isDesktop()) {
await writeTokenFile(token)
}
return token

View File

@ -2609,15 +2609,15 @@ export const modelingMachine = setup({
insertIndex = nodeToEdit[1][0]
}
// Extract the default plane from selection
const plane = selection.otherSelections[0]
if (!(plane && plane instanceof Object && 'name' in plane))
const selectedPlane = getSelectedPlane(selection)
if (!selectedPlane) {
return trap('No plane selected')
}
// Get the default plane name from the selection
const offsetPlaneResult = addOffsetPlane({
node: ast,
defaultPlane: plane.name,
plane: selectedPlane,
offset:
'variableName' in distance
? distance.variableIdentifierAst
@ -5520,6 +5520,33 @@ export function isEditingExistingSketch({
return (hasStartProfileAt && maybePipeExpression.body.length > 1) || hasCircle
}
const getSelectedPlane = (
selection: Selections
): Node<Name> | Node<Literal> | undefined => {
const defaultPlane = selection.otherSelections[0]
if (
defaultPlane &&
defaultPlane instanceof Object &&
'name' in defaultPlane
) {
return createLiteral(defaultPlane.name.toUpperCase())
}
const offsetPlane = selection.graphSelections[0]
if (offsetPlane.artifact?.type === 'plane') {
const artifactId = offsetPlane.artifact?.id
const variableName = Object.entries(kclManager.variables).find(
([_, value]) => {
return value?.type === 'Plane' && value.value?.artifactId === artifactId
}
)
const offsetPlaneName = variableName?.[0]
return offsetPlaneName ? createLocalName(offsetPlaneName) : undefined
}
return undefined
}
export function pipeHasCircle({
sketchDetails,
}: {