Refactor clientSide scene (#3859)

* refactor clientSide scene

* start consolidate threejs segment funcitons

* rename stuff

* first pass of integrating threejs segment create and update into one

* reduce create segment complexity

* add color back in

* use input

* fix comment

* feedback changes
This commit is contained in:
Kurt Hutten
2024-09-13 21:14:14 +10:00
committed by GitHub
parent 728e87a627
commit 8610d606f4
23 changed files with 1823 additions and 1634 deletions

View File

@ -34,13 +34,11 @@ import { CustomIcon, CustomIconName } from 'components/CustomIcon'
import { ConstrainInfo } from 'lang/std/stdTypes' import { ConstrainInfo } from 'lang/std/stdTypes'
import { getConstraintInfo } from 'lang/std/sketch' import { getConstraintInfo } from 'lang/std/sketch'
import { Dialog, Popover, Transition } from '@headlessui/react' import { Dialog, Popover, Transition } from '@headlessui/react'
import { LineInputsType } from 'lang/std/sketchcombos'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { InstanceProps, create } from 'react-modal-promise' import { InstanceProps, create } from 'react-modal-promise'
import { executeAst } from 'lang/langHelpers' import { executeAst } from 'lang/langHelpers'
import { import {
deleteSegmentFromPipeExpression, deleteSegmentFromPipeExpression,
makeRemoveSingleConstraintInput,
removeSingleConstraintInfo, removeSingleConstraintInfo,
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
@ -542,12 +540,10 @@ const ConstraintSymbol = ({
iconName: 'dimension', iconName: 'dimension',
}, },
} }
const varName = const varName = varNameMap?.[_type]?.varName || 'var'
_type in varNameMap ? varNameMap[_type as LineInputsType].varName : 'var' const name: CustomIconName = varNameMap[_type].iconName
const name: CustomIconName = varNameMap[_type as LineInputsType].iconName const displayName = varNameMap[_type]?.displayName
const displayName = varNameMap[_type as LineInputsType]?.displayName const implicitDesc = varNameMap[_type]?.implicitConstraintDesc
const implicitDesc =
varNameMap[_type as LineInputsType]?.implicitConstraintDesc
const _node = useMemo( const _node = useMemo(
() => getNodeFromPath<Expr>(kclManager.ast, pathToNode), () => getNodeFromPath<Expr>(kclManager.ast, pathToNode),
@ -604,13 +600,10 @@ const ConstraintSymbol = ({
if (trap(_node1)) return Promise.reject(_node1) if (trap(_node1)) return Promise.reject(_node1)
const shallowPath = _node1.shallowPath const shallowPath = _node1.shallowPath
const input = makeRemoveSingleConstraintInput( if (!context.sketchDetails || !argPosition) return
argPosition,
shallowPath
)
if (!input || !context.sketchDetails) return
const transform = removeSingleConstraintInfo( const transform = removeSingleConstraintInfo(
input, shallowPath,
argPosition,
kclManager.ast, kclManager.ast,
kclManager.programMemory kclManager.programMemory
) )

View File

@ -1,10 +1,8 @@
import { import {
BoxGeometry, BoxGeometry,
DoubleSide, DoubleSide,
ExtrudeGeometry,
Group, Group,
Intersection, Intersection,
LineCurve3,
Mesh, Mesh,
MeshBasicMaterial, MeshBasicMaterial,
Object3D, Object3D,
@ -15,7 +13,6 @@ import {
Points, Points,
Quaternion, Quaternion,
Scene, Scene,
Shape,
Vector2, Vector2,
Vector3, Vector3,
} from 'three' } from 'three'
@ -27,8 +24,6 @@ import {
OnClickCallbackArgs, OnClickCallbackArgs,
OnMouseEnterLeaveArgs, OnMouseEnterLeaveArgs,
RAYCASTABLE_PLANE, RAYCASTABLE_PLANE,
SEGMENT_LENGTH_LABEL,
SEGMENT_LENGTH_LABEL_TEXT,
SKETCH_GROUP_SEGMENTS, SKETCH_GROUP_SEGMENTS,
SKETCH_LAYER, SKETCH_LAYER,
X_AXIS, X_AXIS,
@ -37,7 +32,6 @@ import {
import { isQuaternionVertical, quaternionFromUpNForward } from './helpers' import { isQuaternionVertical, quaternionFromUpNForward } from './helpers'
import { import {
CallExpression, CallExpression,
getTangentialArcToInfo,
parse, parse,
Path, Path,
PathToNode, PathToNode,
@ -61,11 +55,9 @@ import {
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { executeAst } from 'lang/langHelpers' import { executeAst } from 'lang/langHelpers'
import { import {
createArcGeometry, createProfileStartHandle,
dashedStraight, SegmentUtils,
profileStart, segmentUtils,
straightSegment,
tangentialArcToSegment,
} from './segments' } from './segments'
import { import {
addCallExpressionsToPipe, addCallExpressionsToPipe,
@ -74,13 +66,7 @@ import {
changeSketchArguments, changeSketchArguments,
updateStartProfileAtArgs, updateStartProfileAtArgs,
} from 'lang/std/sketch' } from 'lang/std/sketch'
import { import { isArray, isOverlap, roundOff } from 'lib/utils'
isArray,
isOverlap,
normaliseAngle,
roundOff,
throttle,
} from 'lib/utils'
import { import {
addStartProfileAt, addStartProfileAt,
createArrayExpression, createArrayExpression,
@ -91,7 +77,6 @@ import {
findUniqueName, findUniqueName,
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { Selections, getEventForSegmentSelection } from 'lib/selections' import { Selections, getEventForSegmentSelection } from 'lib/selections'
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
import { createGridHelper, orthoScale, perspScale } from './helpers' import { createGridHelper, orthoScale, perspScale } from './helpers'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { uuidv4 } from 'lib/utils' import { uuidv4 } from 'lib/utils'
@ -121,6 +106,11 @@ export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
export const SEGMENT_WIDTH_PX = 1.6 export const SEGMENT_WIDTH_PX = 1.6
export const HIDE_SEGMENT_LENGTH = 75 // in pixels export const HIDE_SEGMENT_LENGTH = 75 // in pixels
export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels
export const SEGMENT_BODIES = [STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT]
export const SEGMENT_BODIES_PLUS_PROFILE_START = [
...SEGMENT_BODIES,
PROFILE_START,
]
type Vec3Array = [number, number, number] type Vec3Array = [number, number, number]
@ -154,37 +144,35 @@ export class SceneEntities {
? orthoFactor ? orthoFactor
: perspScale(sceneInfra.camControls.camera, segment)) / : perspScale(sceneInfra.camControls.camera, segment)) /
sceneInfra._baseUnitMultiplier sceneInfra._baseUnitMultiplier
const input = {
type: 'straight-segment',
from: segment.userData.from,
to: segment.userData.to,
} as const
let update: SegmentUtils['update'] | null = null
if ( if (
segment.userData.from && segment.userData.from &&
segment.userData.to && segment.userData.to &&
segment.userData.type === STRAIGHT_SEGMENT segment.userData.type === STRAIGHT_SEGMENT
) { ) {
callbacks.push( update = segmentUtils.straight.update
this.updateStraightSegment({
from: segment.userData.from,
to: segment.userData.to,
group: segment,
scale: factor,
})
)
} }
if ( if (
segment.userData.from && segment.userData.from &&
segment.userData.to && segment.userData.to &&
segment.userData.prevSegment && segment.userData.prevSegment &&
segment.userData.type === TANGENTIAL_ARC_TO_SEGMENT segment.userData.type === TANGENTIAL_ARC_TO_SEGMENT
) { ) {
callbacks.push( update = segmentUtils.tangentialArcTo.update
this.updateTangentialArcToSegment({
prevSegment: segment.userData.prevSegment,
from: segment.userData.from,
to: segment.userData.to,
group: segment,
scale: factor,
})
)
} }
const callBack = update?.({
prevSegment: segment.userData.prevSegment,
input,
group: segment,
scale: factor,
sceneInfra,
})
callBack && !err(callBack) && callbacks.push(callBack)
if (segment.name === PROFILE_START) { if (segment.name === PROFILE_START) {
segment.scale.set(factor, factor, factor) segment.scale.set(factor, factor, factor)
} }
@ -421,7 +409,7 @@ export class SceneEntities {
maybeModdedAst, maybeModdedAst,
sketchGroup.start.__geoMeta.sourceRange sketchGroup.start.__geoMeta.sourceRange
) )
const _profileStart = profileStart({ const _profileStart = createProfileStartHandle({
from: sketchGroup.start.from, from: sketchGroup.start.from,
id: sketchGroup.start.__geoMeta.id, id: sketchGroup.start.__geoMeta.id,
pathToNode: segPathToNode, pathToNode: segPathToNode,
@ -476,50 +464,31 @@ export class SceneEntities {
if (err(_node1)) return if (err(_node1)) return
const callExpName = _node1.node?.callee?.name const callExpName = _node1.node?.callee?.name
if (segment.type === 'TangentialArcTo') { const initSegment =
seg = tangentialArcToSegment({ segment.type === 'TangentialArcTo'
prevSegment: sketchGroup.value[index - 1], ? segmentUtils.tangentialArcTo.init
: segmentUtils.straight.init
const result = initSegment({
prevSegment: sketchGroup.value[index - 1],
callExpName,
input: {
type: 'straight-segment',
from: segment.from, from: segment.from,
to: segment.to, to: segment.to,
id: segment.__geoMeta.id, },
pathToNode: segPathToNode, id: segment.__geoMeta.id,
isDraftSegment, pathToNode: segPathToNode,
scale: factor, isDraftSegment,
texture: sceneInfra.extraSegmentTexture, scale: factor,
theme: sceneInfra._theme, texture: sceneInfra.extraSegmentTexture,
isSelected, theme: sceneInfra._theme,
}) isSelected,
callbacks.push( sceneInfra,
this.updateTangentialArcToSegment({ })
prevSegment: sketchGroup.value[index - 1], if (err(result)) return
from: segment.from, const { group: _group, updateOverlaysCallback } = result
to: segment.to, seg = _group
group: seg, callbacks.push(updateOverlaysCallback)
scale: factor,
})
)
} else {
seg = straightSegment({
from: segment.from,
to: segment.to,
id: segment.__geoMeta.id,
pathToNode: segPathToNode,
isDraftSegment,
scale: factor,
callExpName,
texture: sceneInfra.extraSegmentTexture,
theme: sceneInfra._theme,
isSelected,
})
callbacks.push(
this.updateStraightSegment({
from: segment.from,
to: segment.to,
group: seg,
scale: factor,
})
)
}
seg.layers.set(SKETCH_LAYER) seg.layers.set(SKETCH_LAYER)
seg.traverse((child) => { seg.traverse((child) => {
child.layers.set(SKETCH_LAYER) child.layers.set(SKETCH_LAYER)
@ -602,16 +571,19 @@ export class SceneEntities {
kclManager.programMemory.get(variableDeclarationName), kclManager.programMemory.get(variableDeclarationName),
variableDeclarationName variableDeclarationName
) )
if (err(sg)) return sg if (err(sg)) return Promise.reject(sg)
const lastSeg = sg.value?.slice(-1)[0] || sg.start const lastSeg = sg?.value?.slice(-1)[0] || sg.start
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1` const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
const mod = addNewSketchLn({ const mod = addNewSketchLn({
node: _ast, node: _ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
to: [lastSeg.to[0], lastSeg.to[1]], input: {
from: [lastSeg.to[0], lastSeg.to[1]], type: 'straight-segment',
to: lastSeg.to,
from: lastSeg.to,
},
fnName: segmentName, fnName: segmentName,
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
}) })
@ -682,8 +654,11 @@ export class SceneEntities {
const tmp = addNewSketchLn({ const tmp = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
to: [intersection2d.x, intersection2d.y], input: {
from: [lastSegment.to[0], lastSegment.to[1]], type: 'straight-segment',
from: [lastSegment.to[0], lastSegment.to[1]],
to: [intersection2d.x, intersection2d.y],
},
fnName: fnName:
lastSegment.type === 'TangentialArcTo' lastSegment.type === 'TangentialArcTo'
? 'tangentialArcTo' ? 'tangentialArcTo'
@ -834,14 +809,14 @@ export class SceneEntities {
sketchPathToNode || [], sketchPathToNode || [],
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node)) return Promise.reject(_node) if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init const sketchInit = _node.node?.declarations?.[0]?.init
if (sketchInit.type === 'PipeExpression') { if (sketchInit.type === 'PipeExpression') {
updateRectangleSketch(sketchInit, x, y, tags[0]) updateRectangleSketch(sketchInit, x, y, tags[0])
let _recastAst = parse(recast(_ast)) let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst) if (trap(_recastAst)) return
_ast = _recastAst _ast = _recastAst
// Update the primary AST and unequip the rectangle tool // Update the primary AST and unequip the rectangle tool
@ -861,7 +836,7 @@ export class SceneEntities {
programMemory.get(variableDeclarationName), programMemory.get(variableDeclarationName),
variableDeclarationName variableDeclarationName
) )
if (err(sketchGroup)) return sketchGroup if (err(sketchGroup)) return
const sgPaths = sketchGroup.value const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -950,8 +925,11 @@ export class SceneEntities {
const mod = addNewSketchLn({ const mod = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y], input: {
from: [prevSegment.from[0], prevSegment.from[1]], type: 'straight-segment',
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
from: prevSegment.from,
},
// TODO assuming it's always a straight segments being added // TODO assuming it's always a straight segments being added
// as this is easiest, and we'll need to add "tabbing" behavior // as this is easiest, and we'll need to add "tabbing" behavior
// to support other segment types // to support other segment types
@ -1072,7 +1050,7 @@ export class SceneEntities {
group.userData.from[0], group.userData.from[0],
group.userData.from[1], group.userData.from[1],
] ]
const to: [number, number] = [intersection2d.x, intersection2d.y] const dragTo: [number, number] = [intersection2d.x, intersection2d.y]
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast } let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
const _node = getNodeFromPath<CallExpression>( const _node = getNodeFromPath<CallExpression>(
@ -1095,8 +1073,11 @@ export class SceneEntities {
modded = updateStartProfileAtArgs({ modded = updateStartProfileAtArgs({
node: modifiedAst, node: modifiedAst,
pathToNode, pathToNode,
to, input: {
from, type: 'straight-segment',
to: dragTo,
from,
},
previousProgramMemory: kclManager.programMemory, previousProgramMemory: kclManager.programMemory,
}) })
} else { } else {
@ -1104,8 +1085,11 @@ export class SceneEntities {
modifiedAst, modifiedAst,
kclManager.programMemory, kclManager.programMemory,
[node.start, node.end], [node.start, node.end],
to, {
from type: 'straight-segment',
from,
to: dragTo,
}
) )
} }
if (trap(modded)) return if (trap(modded)) return
@ -1208,264 +1192,36 @@ export class SceneEntities {
? orthoFactor ? orthoFactor
: perspScale(sceneInfra.camControls.camera, group)) / : perspScale(sceneInfra.camControls.camera, group)) /
sceneInfra._baseUnitMultiplier sceneInfra._baseUnitMultiplier
const input = {
type: 'straight-segment',
from: segment.from,
to: segment.to,
} as const
let update: SegmentUtils['update'] | null = null
if (type === TANGENTIAL_ARC_TO_SEGMENT) { if (type === TANGENTIAL_ARC_TO_SEGMENT) {
return this.updateTangentialArcToSegment({ update = segmentUtils.tangentialArcTo.update
prevSegment: sgPaths[index - 1],
from: segment.from,
to: segment.to,
group: group,
scale: factor,
})
} else if (type === STRAIGHT_SEGMENT) { } else if (type === STRAIGHT_SEGMENT) {
return this.updateStraightSegment({ update = segmentUtils.straight.update
from: segment.from, }
to: segment.to, const callBack =
update &&
!err(update) &&
update({
input,
group, group,
scale: factor, scale: factor,
prevSegment: sgPaths[index - 1],
sceneInfra,
}) })
} else if (type === PROFILE_START) { if (callBack && !err(callBack)) return callBack
if (type === PROFILE_START) {
group.position.set(segment.from[0], segment.from[1], 0) group.position.set(segment.from[0], segment.from[1], 0)
group.scale.set(factor, factor, factor) group.scale.set(factor, factor, factor)
} }
return () => null return () => null
} }
updateTangentialArcToSegment({
prevSegment,
from,
to,
group,
scale = 1,
}: {
prevSegment: SketchGroup['value'][number]
from: [number, number]
to: [number, number]
group: Group
scale?: number
}): () => SegmentOverlayPayload | null {
group.userData.from = from
group.userData.to = to
group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
const previousPoint =
prevSegment?.type === 'TangentialArcTo'
? getTangentPointFromPreviousArc(
prevSegment.center,
prevSegment.ccw,
prevSegment.to
)
: prevSegment.from
const arcInfo = getTangentialArcToInfo({
arcStartPoint: from,
arcEndPoint: to,
tanPreviousPoint: previousPoint,
obtuse: true,
})
const pxLength = arcInfo.arcLength / scale
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
const hoveredParent =
sceneInfra.hoveredObject &&
getParentGroup(sceneInfra.hoveredObject, [TANGENTIAL_ARC_TO_SEGMENT])
let isHandlesVisible = !shouldHideIdle
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
isHandlesVisible = !shouldHideHover
}
if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0)
const arrowheadAngle =
arcInfo.endAngle + (Math.PI / 2) * (arcInfo.ccw ? 1 : -1)
arrowGroup.quaternion.setFromUnitVectors(
new Vector3(0, 1, 0),
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
)
arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = isHandlesVisible
}
if (extraSegmentGroup) {
const circumferenceInPx = (2 * Math.PI * arcInfo.radius) / scale
const extraSegmentAngleDelta =
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
const extraSegmentAngle =
arcInfo.startAngle + (arcInfo.ccw ? 1 : -1) * extraSegmentAngleDelta
const extraSegmentOffset = new Vector2(
Math.cos(extraSegmentAngle) * arcInfo.radius,
Math.sin(extraSegmentAngle) * arcInfo.radius
)
extraSegmentGroup.position.set(
arcInfo.center[0] + extraSegmentOffset.x,
arcInfo.center[1] + extraSegmentOffset.y,
0
)
extraSegmentGroup.scale.set(scale, scale, scale)
extraSegmentGroup.visible = isHandlesVisible
}
const tangentialArcToSegmentBody = group.children.find(
(child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY
) as Mesh
if (tangentialArcToSegmentBody) {
const newGeo = createArcGeometry({ ...arcInfo, scale })
tangentialArcToSegmentBody.geometry = newGeo
}
const tangentialArcToSegmentBodyDashed = group.children.find(
(child) => child.userData.type === TANGENTIAL_ARC_TO__SEGMENT_DASH
) as Mesh
if (tangentialArcToSegmentBodyDashed) {
// consider throttling the whole updateTangentialArcToSegment
// if there are more perf considerations going forward
this.throttledUpdateDashedArcGeo({
...arcInfo,
mesh: tangentialArcToSegmentBodyDashed,
isDashed: true,
scale,
})
}
const angle = normaliseAngle(
(arcInfo.endAngle * 180) / Math.PI + (arcInfo.ccw ? 90 : -90)
)
return () =>
sceneInfra.updateOverlayDetails({
arrowGroup,
group,
isHandlesVisible,
from,
to,
angle,
})
}
throttledUpdateDashedArcGeo = throttle(
(
args: Parameters<typeof createArcGeometry>[0] & {
mesh: Mesh
scale: number
}
) => (args.mesh.geometry = createArcGeometry(args)),
1000 / 30
)
updateStraightSegment({
from,
to,
group,
scale = 1,
}: {
from: [number, number]
to: [number, number]
group: Group
scale?: number
}): () => SegmentOverlayPayload | null {
group.userData.from = from
group.userData.to = to
const shape = new Shape()
shape.moveTo(0, (-SEGMENT_WIDTH_PX / 2) * scale) // The width of the line in px (2.4px in this case)
shape.lineTo(0, (SEGMENT_WIDTH_PX / 2) * scale)
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const labelGroup = group.getObjectByName(SEGMENT_LENGTH_LABEL) as Group
const length = Math.sqrt(
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
)
const pxLength = length / scale
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
const hoveredParent =
sceneInfra.hoveredObject &&
getParentGroup(sceneInfra.hoveredObject, [STRAIGHT_SEGMENT])
let isHandlesVisible = !shouldHideIdle
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
isHandlesVisible = !shouldHideHover
}
if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0)
const dir = new Vector3()
.subVectors(
new Vector3(to[0], to[1], 0),
new Vector3(from[0], from[1], 0)
)
.normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = isHandlesVisible
}
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (extraSegmentGroup) {
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
.normalize()
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
extraSegmentGroup.position.set(
from[0] + offsetFromBase.x,
from[1] + offsetFromBase.y,
0
)
extraSegmentGroup.scale.set(scale, scale, scale)
extraSegmentGroup.visible = isHandlesVisible
}
if (labelGroup) {
const labelWrapper = labelGroup.getObjectByName(
SEGMENT_LENGTH_LABEL_TEXT
) as CSS2DObject
const labelWrapperElem = labelWrapper.element as HTMLDivElement
const label = labelWrapperElem.children[0] as HTMLParagraphElement
label.innerText = `${roundOff(length)}`
label.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
const slope = (to[1] - from[1]) / (to[0] - from[0])
let slopeAngle = ((Math.atan(slope) * 180) / Math.PI) * -1
label.style.setProperty('--degree', `${slopeAngle}deg`)
label.style.setProperty('--x', `0px`)
label.style.setProperty('--y', `0px`)
labelWrapper.position.set((from[0] + to[0]) / 2, (from[1] + to[1]) / 2, 0)
labelGroup.visible = isHandlesVisible
}
const straightSegmentBody = group.children.find(
(child) => child.userData.type === STRAIGHT_SEGMENT_BODY
) as Mesh
if (straightSegmentBody) {
const line = new LineCurve3(
new Vector3(from[0], from[1], 0),
new Vector3(to[0], to[1], 0)
)
straightSegmentBody.geometry = new ExtrudeGeometry(shape, {
steps: 2,
bevelEnabled: false,
extrudePath: line,
})
}
const straightSegmentBodyDashed = group.children.find(
(child) => child.userData.type === STRAIGHT_SEGMENT_DASH
) as Mesh
if (straightSegmentBodyDashed) {
straightSegmentBodyDashed.geometry = dashedStraight(
from,
to,
shape,
scale
)
}
return () =>
sceneInfra.updateOverlayDetails({
arrowGroup,
group,
isHandlesVisible,
from,
to,
})
}
/** /**
* Update the base color of each of the THREEjs meshes * Update the base color of each of the THREEjs meshes
* that represent each of the sketch segments, to get the * that represent each of the sketch segments, to get the
@ -1578,27 +1334,30 @@ export class SceneEntities {
} }
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const input = {
type: 'straight-segment',
from: parent.userData.from,
to: parent.userData.to,
} as const
const factor = const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera (sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor ? orthoFactor
: perspScale(sceneInfra.camControls.camera, parent)) / : perspScale(sceneInfra.camControls.camera, parent)) /
sceneInfra._baseUnitMultiplier sceneInfra._baseUnitMultiplier
let update: SegmentUtils['update'] | null = null
if (parent.name === STRAIGHT_SEGMENT) { if (parent.name === STRAIGHT_SEGMENT) {
this.updateStraightSegment({ update = segmentUtils.straight.update
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) { } else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
this.updateTangentialArcToSegment({ update = segmentUtils.tangentialArcTo.update
}
update &&
update({
prevSegment: parent.userData.prevSegment, prevSegment: parent.userData.prevSegment,
from: parent.userData.from, input,
to: parent.userData.to,
group: parent, group: parent,
scale: factor, scale: factor,
sceneInfra,
}) })
}
return return
} }
editorManager.setHighlightRange([[0, 0]]) editorManager.setHighlightRange([[0, 0]])
@ -1613,27 +1372,30 @@ export class SceneEntities {
if (parent) { if (parent) {
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const input = {
type: 'straight-segment',
from: parent.userData.from,
to: parent.userData.to,
} as const
const factor = const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera (sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor ? orthoFactor
: perspScale(sceneInfra.camControls.camera, parent)) / : perspScale(sceneInfra.camControls.camera, parent)) /
sceneInfra._baseUnitMultiplier sceneInfra._baseUnitMultiplier
let update: SegmentUtils['update'] | null = null
if (parent.name === STRAIGHT_SEGMENT) { if (parent.name === STRAIGHT_SEGMENT) {
this.updateStraightSegment({ update = segmentUtils.straight.update
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) { } else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
this.updateTangentialArcToSegment({ update = segmentUtils.tangentialArcTo.update
}
update &&
update({
prevSegment: parent.userData.prevSegment, prevSegment: parent.userData.prevSegment,
from: parent.userData.from, input,
to: parent.userData.to,
group: parent, group: parent,
scale: factor, scale: factor,
sceneInfra,
}) })
}
} }
const isSelected = parent?.userData?.isSelected const isSelected = parent?.userData?.isSelected
colorSegment( colorSegment(

View File

@ -26,6 +26,7 @@ import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
import { import {
EXTRA_SEGMENT_HANDLE, EXTRA_SEGMENT_HANDLE,
EXTRA_SEGMENT_OFFSET_PX, EXTRA_SEGMENT_OFFSET_PX,
HIDE_HOVER_SEGMENT_LENGTH,
HIDE_SEGMENT_LENGTH, HIDE_SEGMENT_LENGTH,
PROFILE_START, PROFILE_START,
SEGMENT_WIDTH_PX, SEGMENT_WIDTH_PX,
@ -35,18 +36,448 @@ import {
TANGENTIAL_ARC_TO_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT_BODY, TANGENTIAL_ARC_TO_SEGMENT_BODY,
TANGENTIAL_ARC_TO__SEGMENT_DASH, TANGENTIAL_ARC_TO__SEGMENT_DASH,
getParentGroup,
} from './sceneEntities' } from './sceneEntities'
import { getTangentPointFromPreviousArc } from 'lib/utils2d' import { getTangentPointFromPreviousArc } from 'lib/utils2d'
import { import {
ARROWHEAD, ARROWHEAD,
SceneInfra,
SEGMENT_LENGTH_LABEL, SEGMENT_LENGTH_LABEL,
SEGMENT_LENGTH_LABEL_OFFSET_PX, SEGMENT_LENGTH_LABEL_OFFSET_PX,
SEGMENT_LENGTH_LABEL_TEXT, SEGMENT_LENGTH_LABEL_TEXT,
} from './sceneInfra' } from './sceneInfra'
import { Themes, getThemeColorForThreeJs } from 'lib/theme' import { Themes, getThemeColorForThreeJs } from 'lib/theme'
import { roundOff } from 'lib/utils' import { normaliseAngle, roundOff } from 'lib/utils'
import { SegmentOverlayPayload } from 'machines/modelingMachine'
import { SegmentInputs } from 'lang/std/stdTypes'
import { err } from 'lib/trap'
export function profileStart({ interface CreateSegmentArgs {
input: SegmentInputs
prevSegment: SketchGroup['value'][number]
id: string
pathToNode: PathToNode
isDraftSegment?: boolean
scale?: number
callExpName: string
texture: Texture
theme: Themes
isSelected?: boolean
sceneInfra: SceneInfra
}
interface UpdateSegmentArgs {
input: SegmentInputs
prevSegment: SketchGroup['value'][number]
group: Group
sceneInfra: SceneInfra
scale?: number
}
interface CreateSegmentResult {
group: Group
updateOverlaysCallback: () => SegmentOverlayPayload | null
}
export interface SegmentUtils {
/**
* the init is responsible for adding all of the correct entities to the group with important details like `mesh.name = ...`
* as these act like handles later
*
* It's **Not** responsible for doing all calculations to size and position the entities as this would be duplicated in the update function
* Which should instead be called at the end of the init function
*/
init: (args: CreateSegmentArgs) => CreateSegmentResult | Error
/**
* The update function is responsible for updating the group with the correct size and position of the entities
* It should be called at the end of the init function and return a callback that can be used to update the overlay
*
* It returns a callback for updating the overlays, this is so the overlays do not have to update at the same pace threeJs does
* This is useful for performance reasons
*/
update: (
args: UpdateSegmentArgs
) => CreateSegmentResult['updateOverlaysCallback'] | Error
}
class StraightSegment implements SegmentUtils {
init: SegmentUtils['init'] = ({
input,
id,
pathToNode,
isDraftSegment,
scale = 1,
callExpName,
texture,
theme,
isSelected = false,
sceneInfra,
prevSegment,
}) => {
if (input.type !== 'straight-segment')
return new Error('Invalid segment type')
const { from, to } = input
const baseColor =
callExpName === 'close' ? 0x444444 : getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
const meshType = isDraftSegment
? STRAIGHT_SEGMENT_DASH
: STRAIGHT_SEGMENT_BODY
const segmentGroup = new Group()
const shape = new Shape()
const line = new LineCurve3(
new Vector3(from[0], from[1], 0),
new Vector3(to[0], to[1], 0)
)
const geometry = new ExtrudeGeometry(shape, {
steps: 2,
bevelEnabled: false,
extrudePath: line,
})
const body = new MeshBasicMaterial({ color })
const mesh = new Mesh(geometry, body)
mesh.userData.type = meshType
mesh.name = meshType
segmentGroup.name = STRAIGHT_SEGMENT
segmentGroup.userData = {
type: STRAIGHT_SEGMENT,
id,
from,
to,
pathToNode,
isSelected,
callExpName,
baseColor,
}
// All segment types get an extra segment handle,
// Which is a little plus sign that appears at the origin of the segment
// and can be dragged to insert a new segment
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
// Segment decorators that only apply to non-close segments
if (callExpName !== 'close') {
// an arrowhead that appears at the end of the segment
const arrowGroup = createArrowhead(scale, theme, color)
// A length indicator that appears at the midpoint of the segment
const lengthIndicatorGroup = createLengthIndicator({
from,
to,
scale,
})
segmentGroup.add(arrowGroup)
segmentGroup.add(lengthIndicatorGroup)
}
segmentGroup.add(mesh, extraSegmentGroup)
let updateOverlaysCallback = this.update({
prevSegment,
input,
group: segmentGroup,
scale,
sceneInfra,
})
if (err(updateOverlaysCallback)) return updateOverlaysCallback
return {
group: segmentGroup,
updateOverlaysCallback,
}
}
update: SegmentUtils['update'] = ({
input,
group,
scale = 1,
sceneInfra,
}) => {
if (input.type !== 'straight-segment')
return new Error('Invalid segment type')
const { from, to } = input
group.userData.from = from
group.userData.to = to
const shape = new Shape()
shape.moveTo(0, (-SEGMENT_WIDTH_PX / 2) * scale) // The width of the line in px (2.4px in this case)
shape.lineTo(0, (SEGMENT_WIDTH_PX / 2) * scale)
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const labelGroup = group.getObjectByName(SEGMENT_LENGTH_LABEL) as Group
const length = Math.sqrt(
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
)
const pxLength = length / scale
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
const hoveredParent =
sceneInfra.hoveredObject &&
getParentGroup(sceneInfra.hoveredObject, [STRAIGHT_SEGMENT])
let isHandlesVisible = !shouldHideIdle
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
isHandlesVisible = !shouldHideHover
}
if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0)
const dir = new Vector3()
.subVectors(
new Vector3(to[0], to[1], 0),
new Vector3(from[0], from[1], 0)
)
.normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = isHandlesVisible
}
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (extraSegmentGroup) {
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
.normalize()
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
extraSegmentGroup.position.set(
from[0] + offsetFromBase.x,
from[1] + offsetFromBase.y,
0
)
extraSegmentGroup.scale.set(scale, scale, scale)
extraSegmentGroup.visible = isHandlesVisible
}
if (labelGroup) {
const labelWrapper = labelGroup.getObjectByName(
SEGMENT_LENGTH_LABEL_TEXT
) as CSS2DObject
const labelWrapperElem = labelWrapper.element as HTMLDivElement
const label = labelWrapperElem.children[0] as HTMLParagraphElement
label.innerText = `${roundOff(length)}`
label.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
const slope = (to[1] - from[1]) / (to[0] - from[0])
let slopeAngle = ((Math.atan(slope) * 180) / Math.PI) * -1
label.style.setProperty('--degree', `${slopeAngle}deg`)
label.style.setProperty('--x', `0px`)
label.style.setProperty('--y', `0px`)
labelWrapper.position.set((from[0] + to[0]) / 2, (from[1] + to[1]) / 2, 0)
labelGroup.visible = isHandlesVisible
}
const straightSegmentBody = group.children.find(
(child) => child.userData.type === STRAIGHT_SEGMENT_BODY
) as Mesh
if (straightSegmentBody) {
const line = new LineCurve3(
new Vector3(from[0], from[1], 0),
new Vector3(to[0], to[1], 0)
)
straightSegmentBody.geometry = new ExtrudeGeometry(shape, {
steps: 2,
bevelEnabled: false,
extrudePath: line,
})
}
const straightSegmentBodyDashed = group.children.find(
(child) => child.userData.type === STRAIGHT_SEGMENT_DASH
) as Mesh
if (straightSegmentBodyDashed) {
straightSegmentBodyDashed.geometry = dashedStraight(
from,
to,
shape,
scale
)
}
return () =>
sceneInfra.updateOverlayDetails({
arrowGroup,
group,
isHandlesVisible,
from,
to,
})
}
}
class TangentialArcToSegment implements SegmentUtils {
init: SegmentUtils['init'] = ({
prevSegment,
input,
id,
pathToNode,
isDraftSegment,
scale = 1,
texture,
theme,
isSelected,
sceneInfra,
}) => {
if (input.type !== 'straight-segment')
return new Error('Invalid segment type')
const { from, to } = input
const meshName = isDraftSegment
? TANGENTIAL_ARC_TO__SEGMENT_DASH
: TANGENTIAL_ARC_TO_SEGMENT_BODY
const group = new Group()
const geometry = createArcGeometry({
center: [0, 0],
radius: 1,
startAngle: 0,
endAngle: 1,
ccw: true,
isDashed: isDraftSegment,
scale,
})
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
const body = new MeshBasicMaterial({ color })
const mesh = new Mesh(geometry, body)
const arrowGroup = createArrowhead(scale, theme, color)
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
group.name = TANGENTIAL_ARC_TO_SEGMENT
mesh.userData.type = meshName
mesh.name = meshName
group.userData = {
type: TANGENTIAL_ARC_TO_SEGMENT,
id,
from,
to,
prevSegment,
pathToNode,
isSelected,
baseColor,
}
group.add(mesh, arrowGroup, extraSegmentGroup)
const updateOverlaysCallback = this.update({
prevSegment,
input,
group,
scale,
sceneInfra,
})
if (err(updateOverlaysCallback)) return updateOverlaysCallback
return {
group,
updateOverlaysCallback,
}
}
update: SegmentUtils['update'] = ({
prevSegment,
input,
group,
scale = 1,
sceneInfra,
}) => {
if (input.type !== 'straight-segment')
return new Error('Invalid segment type')
const { from, to } = input
group.userData.from = from
group.userData.to = to
group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
const previousPoint =
prevSegment?.type === 'TangentialArcTo'
? getTangentPointFromPreviousArc(
prevSegment.center,
prevSegment.ccw,
prevSegment.to
)
: prevSegment.from
const arcInfo = getTangentialArcToInfo({
arcStartPoint: from,
arcEndPoint: to,
tanPreviousPoint: previousPoint,
obtuse: true,
})
const pxLength = arcInfo.arcLength / scale
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
const hoveredParent =
sceneInfra?.hoveredObject &&
getParentGroup(sceneInfra.hoveredObject, [TANGENTIAL_ARC_TO_SEGMENT])
let isHandlesVisible = !shouldHideIdle
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
isHandlesVisible = !shouldHideHover
}
if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0)
const arrowheadAngle =
arcInfo.endAngle + (Math.PI / 2) * (arcInfo.ccw ? 1 : -1)
arrowGroup.quaternion.setFromUnitVectors(
new Vector3(0, 1, 0),
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
)
arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = isHandlesVisible
}
if (extraSegmentGroup) {
const circumferenceInPx = (2 * Math.PI * arcInfo.radius) / scale
const extraSegmentAngleDelta =
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
const extraSegmentAngle =
arcInfo.startAngle + (arcInfo.ccw ? 1 : -1) * extraSegmentAngleDelta
const extraSegmentOffset = new Vector2(
Math.cos(extraSegmentAngle) * arcInfo.radius,
Math.sin(extraSegmentAngle) * arcInfo.radius
)
extraSegmentGroup.position.set(
arcInfo.center[0] + extraSegmentOffset.x,
arcInfo.center[1] + extraSegmentOffset.y,
0
)
extraSegmentGroup.scale.set(scale, scale, scale)
extraSegmentGroup.visible = isHandlesVisible
}
const tangentialArcToSegmentBody = group.children.find(
(child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY
) as Mesh
if (tangentialArcToSegmentBody) {
const newGeo = createArcGeometry({ ...arcInfo, scale })
tangentialArcToSegmentBody.geometry = newGeo
}
const tangentialArcToSegmentBodyDashed = group.getObjectByName(
TANGENTIAL_ARC_TO__SEGMENT_DASH
)
if (tangentialArcToSegmentBodyDashed instanceof Mesh) {
tangentialArcToSegmentBodyDashed.geometry = createArcGeometry({
...arcInfo,
isDashed: true,
scale,
})
}
const angle = normaliseAngle(
(arcInfo.endAngle * 180) / Math.PI + (arcInfo.ccw ? 90 : -90)
)
return () =>
sceneInfra.updateOverlayDetails({
arrowGroup,
group,
isHandlesVisible,
from,
to,
angle,
})
}
}
export function createProfileStartHandle({
from, from,
id, id,
pathToNode, pathToNode,
@ -85,127 +516,6 @@ export function profileStart({
return group return group
} }
export function straightSegment({
from,
to,
id,
pathToNode,
isDraftSegment,
scale = 1,
callExpName,
texture,
theme,
isSelected = false,
}: {
from: Coords2d
to: Coords2d
id: string
pathToNode: PathToNode
isDraftSegment?: boolean
scale?: number
callExpName: string
texture: Texture
theme: Themes
isSelected?: boolean
}): Group {
const segmentGroup = new Group()
const shape = new Shape()
shape.moveTo(0, (-SEGMENT_WIDTH_PX / 2) * scale)
shape.lineTo(0, (SEGMENT_WIDTH_PX / 2) * scale)
let geometry
if (isDraftSegment) {
geometry = dashedStraight(from, to, shape, scale)
} else {
const line = new LineCurve3(
new Vector3(from[0], from[1], 0),
new Vector3(to[0], to[1], 0)
)
geometry = new ExtrudeGeometry(shape, {
steps: 2,
bevelEnabled: false,
extrudePath: line,
})
}
const baseColor =
callExpName === 'close' ? 0x444444 : getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
const body = new MeshBasicMaterial({ color })
const mesh = new Mesh(geometry, body)
mesh.userData.type = isDraftSegment
? STRAIGHT_SEGMENT_DASH
: STRAIGHT_SEGMENT_BODY
mesh.name = STRAIGHT_SEGMENT_BODY
segmentGroup.userData = {
type: STRAIGHT_SEGMENT,
id,
from,
to,
pathToNode,
isSelected,
callExpName,
baseColor,
}
segmentGroup.name = STRAIGHT_SEGMENT
segmentGroup.add(mesh)
const length = Math.sqrt(
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
)
const pxLength = length / scale
const shouldHide = pxLength < HIDE_SEGMENT_LENGTH
// All segment types get an extra segment handle,
// Which is a little plus sign that appears at the origin of the segment
// and can be dragged to insert a new segment
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
const directionVector = new Vector2(
to[0] - from[0],
to[1] - from[1]
).normalize()
const offsetFromBase = directionVector.multiplyScalar(
EXTRA_SEGMENT_OFFSET_PX * scale
)
extraSegmentGroup.position.set(
from[0] + offsetFromBase.x,
from[1] + offsetFromBase.y,
0
)
extraSegmentGroup.visible = !shouldHide
segmentGroup.add(extraSegmentGroup)
// Segment decorators that only apply to non-close segments
if (callExpName !== 'close') {
// an arrowhead that appears at the end of the segment
const arrowGroup = createArrowhead(scale, theme, color)
arrowGroup.position.set(to[0], to[1], 0)
const dir = new Vector3()
.subVectors(
new Vector3(to[0], to[1], 0),
new Vector3(from[0], from[1], 0)
)
.normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
arrowGroup.visible = !shouldHide
segmentGroup.add(arrowGroup)
// A length indicator that appears at the midpoint of the segment
const lengthIndicatorGroup = createLengthIndicator({
from,
to,
scale,
length,
})
segmentGroup.add(lengthIndicatorGroup)
}
return segmentGroup
}
function createArrowhead(scale = 1, theme: Themes, color?: number): Group { function createArrowhead(scale = 1, theme: Themes, color?: number): Group {
const baseColor = getThemeColorForThreeJs(theme) const baseColor = getThemeColorForThreeJs(theme)
const arrowMaterial = new MeshBasicMaterial({ const arrowMaterial = new MeshBasicMaterial({
@ -267,12 +577,12 @@ function createLengthIndicator({
from, from,
to, to,
scale, scale,
length, length = 0.1,
}: { }: {
from: Coords2d from: Coords2d
to: Coords2d to: Coords2d
scale: number scale: number
length: number length?: number
}) { }) {
const lengthIndicatorGroup = new Group() const lengthIndicatorGroup = new Group()
lengthIndicatorGroup.name = SEGMENT_LENGTH_LABEL lengthIndicatorGroup.name = SEGMENT_LENGTH_LABEL
@ -300,111 +610,6 @@ function createLengthIndicator({
return lengthIndicatorGroup return lengthIndicatorGroup
} }
export function tangentialArcToSegment({
prevSegment,
from,
to,
id,
pathToNode,
isDraftSegment,
scale = 1,
texture,
theme,
isSelected,
}: {
prevSegment: SketchGroup['value'][number]
from: Coords2d
to: Coords2d
id: string
pathToNode: PathToNode
isDraftSegment?: boolean
scale?: number
texture: Texture
theme: Themes
isSelected?: boolean
}): Group {
const group = new Group()
const previousPoint =
prevSegment?.type === 'TangentialArcTo'
? getTangentPointFromPreviousArc(
prevSegment.center,
prevSegment.ccw,
prevSegment.to
)
: prevSegment.from
const { center, radius, startAngle, endAngle, ccw, arcLength } =
getTangentialArcToInfo({
arcStartPoint: from,
arcEndPoint: to,
tanPreviousPoint: previousPoint,
obtuse: true,
})
const geometry = createArcGeometry({
center,
radius,
startAngle,
endAngle,
ccw,
isDashed: isDraftSegment,
scale,
})
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
const body = new MeshBasicMaterial({ color })
const mesh = new Mesh(geometry, body)
mesh.userData.type = isDraftSegment
? TANGENTIAL_ARC_TO__SEGMENT_DASH
: TANGENTIAL_ARC_TO_SEGMENT_BODY
group.userData = {
type: TANGENTIAL_ARC_TO_SEGMENT,
id,
from,
to,
prevSegment,
pathToNode,
isSelected,
baseColor,
}
group.name = TANGENTIAL_ARC_TO_SEGMENT
const arrowGroup = createArrowhead(scale, theme, color)
arrowGroup.position.set(to[0], to[1], 0)
const arrowheadAngle = endAngle + (Math.PI / 2) * (ccw ? 1 : -1)
arrowGroup.quaternion.setFromUnitVectors(
new Vector3(0, 1, 0),
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
)
const pxLength = arcLength / scale
const shouldHide = pxLength < HIDE_SEGMENT_LENGTH
arrowGroup.visible = !shouldHide
const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme)
const circumferenceInPx = (2 * Math.PI * radius) / scale
const extraSegmentAngleDelta =
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
const extraSegmentAngle = startAngle + (ccw ? 1 : -1) * extraSegmentAngleDelta
const extraSegmentOffset = new Vector2(
Math.cos(extraSegmentAngle) * radius,
Math.sin(extraSegmentAngle) * radius
)
extraSegmentGroup.position.set(
center[0] + extraSegmentOffset.x,
center[1] + extraSegmentOffset.y,
0
)
extraSegmentGroup.visible = !shouldHide
group.add(mesh, arrowGroup, extraSegmentGroup)
return group
}
export function createArcGeometry({ export function createArcGeometry({
center, center,
radius, radius,
@ -579,3 +784,8 @@ export function dashedStraight(
geo.userData.type = 'dashed' geo.userData.type = 'dashed'
return geo return geo
} }
export const segmentUtils = {
straight: new StraightSegment(),
tangentialArcTo: new TangentialArcToSegment(),
} as const

View File

@ -204,6 +204,7 @@ export const ModelingMachineProvider = ({
pathToNodeString, pathToNodeString,
}, },
}) })
// overlay timeout
}, 800) as unknown as number }, 800) as unknown as number
return { return {
...context.segmentHoverMap, ...context.segmentHoverMap,

View File

@ -10,10 +10,10 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap, PathToNodeMap,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { TransformInfo } from 'lang/std/stdTypes'
export function equalAngleInfo({ export function equalAngleInfo({
selectionRanges, selectionRanges,

View File

@ -10,8 +10,8 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap, PathToNodeMap,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'

View File

@ -9,8 +9,8 @@ import {
PathToNodeMap, PathToNodeMap,
getTransformInfos, getTransformInfos,
transformAstSketchLines, transformAstSketchLines,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'

View File

@ -1,6 +1,6 @@
import { toolTips } from 'lang/langHelpers' import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { BinaryPart, Program, Expr, VariableDeclarator } from '../../lang/wasm' import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
import { import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
getNodeFromPath, getNodeFromPath,
@ -11,8 +11,9 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap, PathToNodeMap,
TransformInfo, isExprBinaryPart,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst' import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'
@ -178,11 +179,9 @@ export async function applyConstraintIntersect({
} }
} }
// transform again but forcing certain values // transform again but forcing certain values
const finalValue = removeDoubleNegatives( if (!isExprBinaryPart(valueNode))
valueNode as BinaryPart, return Promise.reject('Invalid valueNode, is not a BinaryPart')
sign, const finalValue = removeDoubleNegatives(valueNode, sign, variableName)
variableName
)
const transform2 = transformSecondarySketchLinesTagFirst({ const transform2 = transformSecondarySketchLinesTagFirst({
ast: kclManager.ast, ast: kclManager.ast,
selectionRanges: forcedSelectionRanges, selectionRanges: forcedSelectionRanges,

View File

@ -9,8 +9,8 @@ import {
PathToNodeMap, PathToNodeMap,
getRemoveConstraintsTransforms, getRemoveConstraintsTransforms,
transformAstSketchLines, transformAstSketchLines,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'

View File

@ -1,6 +1,6 @@
import { toolTips } from 'lang/langHelpers' import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { BinaryPart, Program, Expr } from '../../lang/wasm' import { Program, Expr } from '../../lang/wasm'
import { import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
getNodeFromPath, getNodeFromPath,
@ -9,8 +9,9 @@ import {
getTransformInfos, getTransformInfos,
transformAstSketchLines, transformAstSketchLines,
PathToNodeMap, PathToNodeMap,
TransformInfo, isExprBinaryPart,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { import {
SetAngleLengthModal, SetAngleLengthModal,
createSetAngleLengthModal, createSetAngleLengthModal,
@ -121,11 +122,9 @@ export async function applyConstraintAbsDistance({
value: forceVal, value: forceVal,
valueName: constraint === 'yAbs' ? 'yDis' : 'xDis', valueName: constraint === 'yAbs' ? 'yDis' : 'xDis',
}) })
let finalValue = removeDoubleNegatives( if (!isExprBinaryPart(valueNode))
valueNode as BinaryPart, return Promise.reject('Invalid valueNode, is not a BinaryPart')
sign, let finalValue = removeDoubleNegatives(valueNode, sign, variableName)
variableName
)
const transform2 = transformAstSketchLines({ const transform2 = transformAstSketchLines({
ast: structuredClone(kclManager.ast), ast: structuredClone(kclManager.ast),

View File

@ -1,6 +1,6 @@
import { toolTips } from 'lang/langHelpers' import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { BinaryPart, Program, Expr, VariableDeclarator } from '../../lang/wasm' import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
import { import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
getNodeFromPath, getNodeFromPath,
@ -10,8 +10,9 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap, PathToNodeMap,
TransformInfo, isExprBinaryPart,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst' import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'
@ -133,11 +134,9 @@ export async function applyConstraintAngleBetween({
} }
} }
const finalValue = removeDoubleNegatives( if (!isExprBinaryPart(valueNode))
valueNode as BinaryPart, return Promise.reject('Invalid valueNode, is not a BinaryPart')
sign, const finalValue = removeDoubleNegatives(valueNode, sign, variableName)
variableName
)
// transform again but forcing certain values // transform again but forcing certain values
const transformed2 = transformSecondarySketchLinesTagFirst({ const transformed2 = transformSecondarySketchLinesTagFirst({
ast: kclManager.ast, ast: kclManager.ast,

View File

@ -1,5 +1,5 @@
import { toolTips } from 'lang/langHelpers' import { toolTips } from 'lang/langHelpers'
import { BinaryPart, Program, Expr, VariableDeclarator } from '../../lang/wasm' import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
import { import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
getNodeFromPath, getNodeFromPath,
@ -9,8 +9,9 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap, PathToNodeMap,
TransformInfo, isExprBinaryPart,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst' import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'
@ -139,9 +140,11 @@ export async function applyConstraintHorzVertDistance({
pathToNodeMap, pathToNodeMap,
} }
} else { } else {
if (!isExprBinaryPart(valueNode))
return Promise.reject('Invalid valueNode, is not a BinaryPart')
let finalValue = isAlign let finalValue = isAlign
? createLiteral(0) ? createLiteral(0)
: removeDoubleNegatives(valueNode as BinaryPart, sign, variableName) : removeDoubleNegatives(valueNode, sign, variableName)
// transform again but forcing certain values // transform again but forcing certain values
const transformed = transformSecondarySketchLinesTagFirst({ const transformed = transformSecondarySketchLinesTagFirst({
ast: kclManager.ast, ast: kclManager.ast,

View File

@ -1,6 +1,6 @@
import { toolTips } from 'lang/langHelpers' import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { BinaryPart, Program, Expr } from '../../lang/wasm' import { Program, Expr } from '../../lang/wasm'
import { import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
getNodeFromPath, getNodeFromPath,
@ -8,9 +8,10 @@ import {
import { import {
PathToNodeMap, PathToNodeMap,
getTransformInfos, getTransformInfos,
isExprBinaryPart,
transformAstSketchLines, transformAstSketchLines,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { import {
SetAngleLengthModal, SetAngleLengthModal,
createSetAngleLengthModal, createSetAngleLengthModal,
@ -125,12 +126,9 @@ export async function applyConstraintAngleLength({
valueName: angleOrLength === 'setAngle' ? 'angle' : 'length', valueName: angleOrLength === 'setAngle' ? 'angle' : 'length',
shouldCreateVariable: true, shouldCreateVariable: true,
}) })
if (!isExprBinaryPart(valueNode))
let finalValue = removeDoubleNegatives( return Promise.reject('Invalid valueNode, is not a BinaryPart')
valueNode as BinaryPart, let finalValue = removeDoubleNegatives(valueNode, sign, variableName)
sign,
variableName
)
if ( if (
isReferencingYAxisAngle || isReferencingYAxisAngle ||
(isReferencingXAxisAngle && calcIdentifier.name !== 'ZERO') (isReferencingXAxisAngle && calcIdentifier.name !== 'ZERO')

View File

@ -26,9 +26,6 @@ export type ToolTip =
| 'tangentialArcTo' | 'tangentialArcTo'
export const toolTips = [ export const toolTips = [
'sketch_line',
'move',
// original tooltips
'line', 'line',
'lineTo', 'lineTo',
'angledLine', 'angledLine',
@ -42,7 +39,7 @@ export const toolTips = [
'yLineTo', 'yLineTo',
'angledLineThatIntersects', 'angledLineThatIntersects',
'tangentialArcTo', 'tangentialArcTo',
] as any as ToolTip[] ]
export async function executeAst({ export async function executeAst({
ast, ast,

View File

@ -20,6 +20,7 @@ import {
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst' import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { SimplifiedArgDetails } from './std/stdTypes'
beforeAll(async () => { beforeAll(async () => {
await initPromise await initPromise
@ -627,7 +628,7 @@ describe('Testing removeSingleConstraintInfo', () => {
'offset', 'offset',
], ],
['tangentialArcTo([3.14 + 0, 13.14], %)', 'arrayIndex', 1], ['tangentialArcTo([3.14 + 0, 13.14], %)', 'arrayIndex', 1],
])('stdlib fn: %s', async (expectedFinish, key, value) => { ] as const)('stdlib fn: %s', async (expectedFinish, key, value) => {
const ast = parse(code) const ast = parse(code)
if (err(ast)) throw ast if (err(ast)) throw ast
@ -638,11 +639,27 @@ describe('Testing removeSingleConstraintInfo', () => {
code.indexOf(lineOfInterest) + lineOfInterest.length, code.indexOf(lineOfInterest) + lineOfInterest.length,
] ]
const pathToNode = getNodePathFromSourceRange(ast, range) const pathToNode = getNodePathFromSourceRange(ast, range)
let argPosition: SimplifiedArgDetails
if (key === 'arrayIndex' && typeof value === 'number') {
argPosition = {
type: 'arrayItem',
index: value === 0 ? 0 : 1,
}
} else if (key === 'objectProperty' && typeof value === 'string') {
argPosition = {
type: 'objectProperty',
key: value,
}
} else if (key === '') {
argPosition = {
type: 'singleValue',
}
} else {
throw new Error('argPosition is undefined')
}
const mod = removeSingleConstraintInfo( const mod = removeSingleConstraintInfo(
{ pathToNode,
pathToCallExp: pathToNode, argPosition,
[key]: value,
},
ast, ast,
programMemory programMemory
) )
@ -675,12 +692,24 @@ describe('Testing removeSingleConstraintInfo', () => {
code.indexOf(lineOfInterest) + 1, code.indexOf(lineOfInterest) + 1,
code.indexOf(lineOfInterest) + lineOfInterest.length, code.indexOf(lineOfInterest) + lineOfInterest.length,
] ]
let argPosition: SimplifiedArgDetails
if (key === 'arrayIndex' && typeof value === 'number') {
argPosition = {
type: 'arrayItem',
index: value === 0 ? 0 : 1,
}
} else if (key === 'objectProperty' && typeof value === 'string') {
argPosition = {
type: 'objectProperty',
key: value,
}
} else {
throw new Error('argPosition is undefined')
}
const pathToNode = getNodePathFromSourceRange(ast, range) const pathToNode = getNodePathFromSourceRange(ast, range)
const mod = removeSingleConstraintInfo( const mod = removeSingleConstraintInfo(
{ pathToNode,
pathToCallExp: pathToNode, argPosition,
[key]: value,
},
ast, ast,
programMemory programMemory
) )

View File

@ -38,7 +38,7 @@ import {
import { DefaultPlaneStr } from 'clientSideScene/sceneEntities' import { DefaultPlaneStr } from 'clientSideScene/sceneEntities'
import { isOverlap, roundOff } from 'lib/utils' import { isOverlap, roundOff } from 'lib/utils'
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants' import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
import { ConstrainInfo } from './std/stdTypes' import { SimplifiedArgDetails } from './std/stdTypes'
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator' import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
@ -799,15 +799,10 @@ export function deleteSegmentFromPipeExpression(
) )
if (!constraintInfo) return if (!constraintInfo) return
const input = makeRemoveSingleConstraintInput( if (!constraintInfo.argPosition) return
constraintInfo.argPosition,
callExp.shallowPath
)
if (!input) return
const transform = removeSingleConstraintInfo( const transform = removeSingleConstraintInfo(
{ callExp.shallowPath,
...input, constraintInfo.argPosition,
},
_modifiedAst, _modifiedAst,
programMemory programMemory
) )
@ -834,37 +829,9 @@ export function deleteSegmentFromPipeExpression(
return _modifiedAst return _modifiedAst
} }
export function makeRemoveSingleConstraintInput(
argPosition: ConstrainInfo['argPosition'],
pathToNode: PathToNode
): Parameters<typeof removeSingleConstraintInfo>[0] | false {
return argPosition?.type === 'singleValue'
? {
pathToCallExp: pathToNode,
}
: argPosition?.type === 'arrayItem'
? {
pathToCallExp: pathToNode,
arrayIndex: argPosition.index,
}
: argPosition?.type === 'objectProperty'
? {
pathToCallExp: pathToNode,
objectProperty: argPosition.key,
}
: false
}
export function removeSingleConstraintInfo( export function removeSingleConstraintInfo(
{ pathToCallExp: PathToNode,
pathToCallExp, argDetails: SimplifiedArgDetails,
arrayIndex,
objectProperty,
}: {
pathToCallExp: PathToNode
arrayIndex?: number
objectProperty?: string
},
ast: Program, ast: Program,
programMemory: ProgramMemory programMemory: ProgramMemory
): ):
@ -875,8 +842,7 @@ export function removeSingleConstraintInfo(
| false { | false {
const transform = removeSingleConstraint({ const transform = removeSingleConstraint({
pathToCallExp, pathToCallExp,
arrayIndex, inputDetails: argDetails,
objectProperty,
ast, ast,
}) })
if (!transform) return false if (!transform) return false

View File

@ -16,6 +16,7 @@ import {
VariableDeclaration, VariableDeclaration,
VariableDeclarator, VariableDeclarator,
sketchGroupFromKclValue, sketchGroupFromKclValue,
ObjectExpression,
} from './wasm' } from './wasm'
import { createIdentifier, splitPathAtLastIndex } from './modifyAst' import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints' import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
@ -934,3 +935,12 @@ export function hasExtrudableGeometry(ast: Program) {
}) })
return Object.keys(theMap).length > 0 return Object.keys(theMap).length > 0
} }
export function getObjExprProperty(
node: ObjectExpression,
propName: string
): { expr: Expr; index: number } | null {
const index = node.properties.findIndex(({ key }) => key.name === propName)
if (index === -1) return null
return { expr: node.properties[index].value, index }
}

View File

@ -123,8 +123,11 @@ describe('testing changeSketchArguments', () => {
ast, ast,
programMemory, programMemory,
[sourceStart, sourceStart + lineToChange.length], [sourceStart, sourceStart + lineToChange.length],
[2, 3], {
[0, 0] type: 'straight-segment',
from: [0, 0],
to: [2, 3],
}
) )
if (err(changeSketchArgsRetVal)) return changeSketchArgsRetVal if (err(changeSketchArgsRetVal)) return changeSketchArgsRetVal
expect(recast(changeSketchArgsRetVal.modifiedAst)).toBe(expectedCode) expect(recast(changeSketchArgsRetVal.modifiedAst)).toBe(expectedCode)
@ -150,8 +153,11 @@ const mySketch001 = startSketchOn('XY')
const newSketchLnRetVal = addNewSketchLn({ const newSketchLnRetVal = addNewSketchLn({
node: ast, node: ast,
programMemory, programMemory,
to: [2, 3], input: {
from: [0, 0], type: 'straight-segment',
from: [0, 0],
to: [2, 3],
},
fnName: 'lineTo', fnName: 'lineTo',
pathToNode: [ pathToNode: [
['body', ''], ['body', ''],

View File

@ -9,7 +9,6 @@ import {
CallExpression, CallExpression,
VariableDeclarator, VariableDeclarator,
Expr, Expr,
Literal,
VariableDeclaration, VariableDeclaration,
Identifier, Identifier,
sketchGroupFromKclValue, sketchGroupFromKclValue,
@ -20,7 +19,6 @@ import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
} from 'lang/queryAst' } from 'lang/queryAst'
import { import {
LineInputsType,
isLiteralArrayOrStatic, isLiteralArrayOrStatic,
isNotLiteralArrayOrStatic, isNotLiteralArrayOrStatic,
} from 'lang/std/sketchcombos' } from 'lang/std/sketchcombos'
@ -29,15 +27,15 @@ import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
import { import {
SketchLineHelper, SketchLineHelper,
TransformCallback,
ConstrainInfo, ConstrainInfo,
RawValues,
ArrayItemInput, ArrayItemInput,
ObjectPropertyInput, ObjectPropertyInput,
SingleValueInput, SingleValueInput,
VarValueKeys,
ArrayOrObjItemInput,
AddTagInfo, AddTagInfo,
SegmentInputs,
SimplifiedArgDetails,
RawArgs,
CreatedSketchExprResult,
} from 'lang/std/stdTypes' } from 'lang/std/stdTypes'
import { import {
@ -56,6 +54,10 @@ import { err } from 'lib/trap'
import { perpendicularDistance } from 'sketch-helpers' import { perpendicularDistance } from 'sketch-helpers'
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator' import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
const STRAIGHT_SEGMENT_ERR = new Error(
'Invalid input, expected "straight-segment"'
)
export type Coords2d = [number, number] export type Coords2d = [number, number]
export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d { export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
@ -109,6 +111,7 @@ type AbbreviatedInput =
| ArrayItemInput<any>['index'] | ArrayItemInput<any>['index']
| ObjectPropertyInput<any>['key'] | ObjectPropertyInput<any>['key']
| SingleValueInput<any>['type'] | SingleValueInput<any>['type']
| SimplifiedArgDetails
| undefined | undefined
const constrainInfo = ( const constrainInfo = (
@ -157,8 +160,12 @@ const commonConstraintInfoHelper = (
const firstArg = callExp.arguments?.[0] const firstArg = callExp.arguments?.[0]
const isArr = firstArg.type === 'ArrayExpression' const isArr = firstArg.type === 'ArrayExpression'
if (!isArr && firstArg.type !== 'ObjectExpression') return [] if (!isArr && firstArg.type !== 'ObjectExpression') return []
const pipeExpressionIndex = pathToNode.findIndex(
([_, nodeName]) => nodeName === 'PipeExpression'
)
const pathToBase = pathToNode.slice(0, pipeExpressionIndex + 2)
const pathToArrayExpression: PathToNode = [ const pathToArrayExpression: PathToNode = [
...pathToNode, ...pathToBase,
['arguments', 'CallExpression'], ['arguments', 'CallExpression'],
[0, 'index'], [0, 'index'],
isArr isArr
@ -270,45 +277,6 @@ const horzVertConstraintInfoHelper = (
] ]
} }
function arrayRawValuesHelper(a: Array<[Literal, LineInputsType]>): RawValues {
return a.map(
([literal, argType], index): ArrayItemInput<Literal> => ({
type: 'arrayItem',
index: index === 0 ? 0 : 1,
argType,
value: literal,
})
)
}
function arrOrObjectRawValuesHelper(
a: Array<[Literal, LineInputsType, VarValueKeys]>
): RawValues {
return a.map(
([literal, argType, key], index): ArrayOrObjItemInput<Literal> => ({
type: 'arrayOrObjItem',
// key: argType,w
index: index === 0 ? 0 : 1,
key,
argType,
value: literal,
})
)
}
function singleRawValueHelper(
literal: Literal,
argType: LineInputsType
): RawValues {
return [
{
type: 'singleValue',
argType,
value: literal,
},
]
}
function getTag(index = 2): SketchLineHelper['getTag'] { function getTag(index = 2): SketchLineHelper['getTag'] {
return (callExp: CallExpression) => { return (callExp: CallExpression) => {
if (callExp.type !== 'CallExpression') if (callExp.type !== 'CallExpression')
@ -322,14 +290,9 @@ function getTag(index = 2): SketchLineHelper['getTag'] {
} }
export const lineTo: SketchLineHelper = { export const lineTo: SketchLineHelper = {
add: ({ add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
node, if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
pathToNode, const to = segmentInput.to
to,
createCallback,
replaceExisting,
referencedSegment,
}) => {
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<PipeExpression>( const nodeMeta = getNodeFromPath<PipeExpression>(
_node, _node,
@ -349,15 +312,23 @@ export const lineTo: SketchLineHelper = {
createPipeSubstitution(), createPipeSubstitution(),
]) ])
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
if (replaceExisting && createCallback) { if (replaceExistingCallback) {
const { callExp, valueUsedInTransform } = createCallback( const result = replaceExistingCallback([
newVals, {
arrayRawValuesHelper([ type: 'arrayItem',
[createLiteral(roundOff(to[0], 2)), 'xAbsolute'], index: 0,
[createLiteral(roundOff(to[1], 2)), 'yAbsolute'], argType: 'xAbsolute',
]), expr: createLiteral(roundOff(to[0], 2)),
referencedSegment },
) {
type: 'arrayItem',
index: 1,
argType: 'yAbsolute',
expr: createLiteral(roundOff(to[1], 2)),
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
pipe.body[callIndex] = callExp pipe.body[callIndex] = callExp
return { return {
modifiedAst: _node, modifiedAst: _node,
@ -372,7 +343,9 @@ export const lineTo: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
updateArgs: ({ node, pathToNode, to }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -407,13 +380,12 @@ export const line: SketchLineHelper = {
node, node,
previousProgramMemory, previousProgramMemory,
pathToNode, pathToNode,
to, segmentInput,
from, replaceExistingCallback,
replaceExisting,
referencedSegment,
createCallback,
spliceBetween, spliceBetween,
}) => { }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { from, to } = segmentInput
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<PipeExpression | CallExpression>( const nodeMeta = getNodeFromPath<PipeExpression | CallExpression>(
_node, _node,
@ -433,7 +405,11 @@ export const line: SketchLineHelper = {
const newXVal = createLiteral(roundOff(to[0] - from[0], 2)) const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
const newYVal = createLiteral(roundOff(to[1] - from[1], 2)) const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
if (spliceBetween && !createCallback && pipe.type === 'PipeExpression') { if (
spliceBetween &&
!replaceExistingCallback &&
pipe.type === 'PipeExpression'
) {
const callExp = createCallExpression('line', [ const callExp = createCallExpression('line', [
createArrayExpression([newXVal, newYVal]), createArrayExpression([newXVal, newYVal]),
createPipeSubstitution(), createPipeSubstitution(),
@ -456,16 +432,24 @@ export const line: SketchLineHelper = {
} }
} }
if (replaceExisting && createCallback && pipe.type !== 'CallExpression') { if (replaceExistingCallback && pipe.type !== 'CallExpression') {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback( const result = replaceExistingCallback([
[newXVal, newYVal], {
arrayRawValuesHelper([ type: 'arrayItem',
[createLiteral(roundOff(to[0] - from[0], 2)), 'xRelative'], index: 0,
[createLiteral(roundOff(to[1] - from[1], 2)), 'yRelative'], argType: 'xRelative',
]), expr: createLiteral(roundOff(to[0] - from[0], 2)),
referencedSegment },
) {
type: 'arrayItem',
index: 1,
argType: 'yRelative',
expr: createLiteral(roundOff(to[1] - from[1], 2)),
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
pipe.body[callIndex] = callExp pipe.body[callIndex] = callExp
return { return {
modifiedAst: _node, modifiedAst: _node,
@ -496,7 +480,9 @@ export const line: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
updateArgs: ({ node, pathToNode, to, from }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -530,7 +516,9 @@ export const line: SketchLineHelper = {
} }
export const xLineTo: SketchLineHelper = { export const xLineTo: SketchLineHelper = {
add: ({ node, pathToNode, to, replaceExisting, createCallback }) => { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to } = segmentInput
const _node = { ...node } const _node = { ...node }
const getNode = getNodeFromPathCurry(_node, pathToNode) const getNode = getNodeFromPathCurry(_node, pathToNode)
const _node1 = getNode<PipeExpression>('PipeExpression') const _node1 = getNode<PipeExpression>('PipeExpression')
@ -539,12 +527,17 @@ export const xLineTo: SketchLineHelper = {
const newVal = createLiteral(roundOff(to[0], 2)) const newVal = createLiteral(roundOff(to[0], 2))
if (replaceExisting && createCallback) { if (replaceExistingCallback) {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback( const result = replaceExistingCallback([
[newVal, newVal], {
singleRawValueHelper(newVal, 'xAbsolute') type: 'singleValue',
) argType: 'xAbsolute',
expr: createLiteral(roundOff(to[0], 2)),
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
pipe.body[callIndex] = callExp pipe.body[callIndex] = callExp
return { return {
modifiedAst: _node, modifiedAst: _node,
@ -562,7 +555,9 @@ export const xLineTo: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
updateArgs: ({ node, pathToNode, to }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -591,7 +586,9 @@ export const xLineTo: SketchLineHelper = {
} }
export const yLineTo: SketchLineHelper = { export const yLineTo: SketchLineHelper = {
add: ({ node, pathToNode, to, replaceExisting, createCallback }) => { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to } = segmentInput
const _node = { ...node } const _node = { ...node }
const getNode = getNodeFromPathCurry(_node, pathToNode) const getNode = getNodeFromPathCurry(_node, pathToNode)
const _node1 = getNode<PipeExpression>('PipeExpression') const _node1 = getNode<PipeExpression>('PipeExpression')
@ -600,12 +597,17 @@ export const yLineTo: SketchLineHelper = {
const newVal = createLiteral(roundOff(to[1], 2)) const newVal = createLiteral(roundOff(to[1], 2))
if (replaceExisting && createCallback) { if (replaceExistingCallback) {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback( const result = replaceExistingCallback([
[newVal, newVal], {
singleRawValueHelper(newVal, 'yAbsolute') type: 'singleValue',
) argType: 'yAbsolute',
expr: newVal,
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
pipe.body[callIndex] = callExp pipe.body[callIndex] = callExp
return { return {
modifiedAst: _node, modifiedAst: _node,
@ -623,7 +625,9 @@ export const yLineTo: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
updateArgs: ({ node, pathToNode, to, from }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -652,7 +656,9 @@ export const yLineTo: SketchLineHelper = {
} }
export const xLine: SketchLineHelper = { export const xLine: SketchLineHelper = {
add: ({ node, pathToNode, to, from, replaceExisting, createCallback }) => { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { from, to } = segmentInput
const _node = { ...node } const _node = { ...node }
const getNode = getNodeFromPathCurry(_node, pathToNode) const getNode = getNodeFromPathCurry(_node, pathToNode)
const _node1 = getNode<PipeExpression>('PipeExpression') const _node1 = getNode<PipeExpression>('PipeExpression')
@ -660,14 +666,18 @@ export const xLine: SketchLineHelper = {
const { node: pipe } = _node1 const { node: pipe } = _node1
const newVal = createLiteral(roundOff(to[0] - from[0], 2)) const newVal = createLiteral(roundOff(to[0] - from[0], 2))
const firstArg = newVal
if (replaceExisting && createCallback) { if (replaceExistingCallback) {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback( const result = replaceExistingCallback([
[firstArg, firstArg], {
singleRawValueHelper(firstArg, 'xRelative') type: 'singleValue',
) argType: 'xRelative',
expr: newVal,
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
pipe.body[callIndex] = callExp pipe.body[callIndex] = callExp
return { return {
modifiedAst: _node, modifiedAst: _node,
@ -677,13 +687,15 @@ export const xLine: SketchLineHelper = {
} }
const newLine = createCallExpression('xLine', [ const newLine = createCallExpression('xLine', [
firstArg, newVal,
createPipeSubstitution(), createPipeSubstitution(),
]) ])
pipe.body = [...pipe.body, newLine] pipe.body = [...pipe.body, newLine]
return { modifiedAst: _node, pathToNode } return { modifiedAst: _node, pathToNode }
}, },
updateArgs: ({ node, pathToNode, to, from }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -712,19 +724,26 @@ export const xLine: SketchLineHelper = {
} }
export const yLine: SketchLineHelper = { export const yLine: SketchLineHelper = {
add: ({ node, pathToNode, to, from, replaceExisting, createCallback }) => { add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { from, to } = segmentInput
const _node = { ...node } const _node = { ...node }
const getNode = getNodeFromPathCurry(_node, pathToNode) const getNode = getNodeFromPathCurry(_node, pathToNode)
const _node1 = getNode<PipeExpression>('PipeExpression') const _node1 = getNode<PipeExpression>('PipeExpression')
if (err(_node1)) return _node1 if (err(_node1)) return _node1
const { node: pipe } = _node1 const { node: pipe } = _node1
const newVal = createLiteral(roundOff(to[1] - from[1], 2)) const newVal = createLiteral(roundOff(to[1] - from[1], 2))
if (replaceExisting && createCallback) { if (replaceExistingCallback) {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback( const result = replaceExistingCallback([
[newVal, newVal], {
singleRawValueHelper(newVal, 'yRelative') type: 'singleValue',
) argType: 'yRelative',
expr: newVal,
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
pipe.body[callIndex] = callExp pipe.body[callIndex] = callExp
return { return {
modifiedAst: _node, modifiedAst: _node,
@ -740,7 +759,9 @@ export const yLine: SketchLineHelper = {
pipe.body = [...pipe.body, newLine] pipe.body = [...pipe.body, newLine]
return { modifiedAst: _node, pathToNode } return { modifiedAst: _node, pathToNode }
}, },
updateArgs: ({ node, pathToNode, to, from }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -769,14 +790,9 @@ export const yLine: SketchLineHelper = {
} }
export const tangentialArcTo: SketchLineHelper = { export const tangentialArcTo: SketchLineHelper = {
add: ({ add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
node, if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
pathToNode, const { to } = segmentInput
to,
createCallback,
replaceExisting,
referencedSegment,
}) => {
const _node = { ...node } const _node = { ...node }
const getNode = getNodeFromPathCurry(_node, pathToNode) const getNode = getNodeFromPathCurry(_node, pathToNode)
const _node1 = getNode<PipeExpression | CallExpression>('PipeExpression') const _node1 = getNode<PipeExpression | CallExpression>('PipeExpression')
@ -793,16 +809,24 @@ export const tangentialArcTo: SketchLineHelper = {
const toX = createLiteral(roundOff(to[0], 2)) const toX = createLiteral(roundOff(to[0], 2))
const toY = createLiteral(roundOff(to[1], 2)) const toY = createLiteral(roundOff(to[1], 2))
if (replaceExisting && createCallback && pipe.type !== 'CallExpression') { if (replaceExistingCallback && pipe.type !== 'CallExpression') {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback( const result = replaceExistingCallback([
[toX, toY], {
arrayRawValuesHelper([ type: 'arrayItem',
[createLiteral(roundOff(to[0], 2)), 'xAbsolute'], index: 0,
[createLiteral(roundOff(to[1], 2)), 'yAbsolute'], argType: 'xRelative',
]), expr: toX,
referencedSegment },
) {
type: 'arrayItem',
index: 1,
argType: 'yAbsolute',
expr: toY,
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
pipe.body[callIndex] = callExp pipe.body[callIndex] = callExp
return { return {
modifiedAst: _node, modifiedAst: _node,
@ -832,7 +856,9 @@ export const tangentialArcTo: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
updateArgs: ({ node, pathToNode, to, from }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -900,15 +926,9 @@ export const tangentialArcTo: SketchLineHelper = {
}, },
} }
export const angledLine: SketchLineHelper = { export const angledLine: SketchLineHelper = {
add: ({ add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
node, if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
pathToNode, const { from, to } = segmentInput
to,
from,
createCallback,
replaceExisting,
referencedSegment,
}) => {
const _node = { ...node } const _node = { ...node }
const getNode = getNodeFromPathCurry(_node, pathToNode) const getNode = getNodeFromPathCurry(_node, pathToNode)
const _node1 = getNode<PipeExpression>('PipeExpression') const _node1 = getNode<PipeExpression>('PipeExpression')
@ -922,16 +942,26 @@ export const angledLine: SketchLineHelper = {
createPipeSubstitution(), createPipeSubstitution(),
]) ])
if (replaceExisting && createCallback) { if (replaceExistingCallback) {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback( const result = replaceExistingCallback([
[newAngleVal, newLengthVal], {
arrOrObjectRawValuesHelper([ type: 'arrayOrObjItem',
[newAngleVal, 'angle', 'angle'], index: 0,
[newLengthVal, 'length', 'length'], key: 'angle',
]), argType: 'angle',
referencedSegment expr: newAngleVal,
) },
{
type: 'arrayOrObjItem',
index: 1,
key: 'length',
argType: 'length',
expr: newLengthVal,
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
pipe.body[callIndex] = callExp pipe.body[callIndex] = callExp
return { return {
modifiedAst: _node, modifiedAst: _node,
@ -946,7 +976,9 @@ export const angledLine: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
updateArgs: ({ node, pathToNode, to, from }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -988,11 +1020,11 @@ export const angledLineOfXLength: SketchLineHelper = {
node, node,
previousProgramMemory, previousProgramMemory,
pathToNode, pathToNode,
to, segmentInput,
from, replaceExistingCallback,
createCallback,
replaceExisting,
}) => { }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { from, to } = segmentInput
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<PipeExpression>( const nodeMeta = getNodeFromPath<PipeExpression>(
_node, _node,
@ -1019,20 +1051,34 @@ export const angledLineOfXLength: SketchLineHelper = {
} }
const angle = createLiteral(roundOff(getAngle(from, to), 0)) const angle = createLiteral(roundOff(getAngle(from, to), 0))
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1) const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
const newLine = createCallback let newLine: Expr
? createCallback( if (replaceExistingCallback) {
[angle, xLength], const result = replaceExistingCallback([
arrOrObjectRawValuesHelper([ {
[angle, 'angle', 'angle'], type: 'arrayOrObjItem',
[xLength, 'xRelative', 'length'], index: 0,
]) key: 'angle',
).callExp argType: 'angle',
: createCallExpression('angledLineOfXLength', [ expr: angle,
createArrayExpression([angle, xLength]), },
createPipeSubstitution(), {
]) type: 'arrayOrObjItem',
index: 1,
key: 'length',
argType: 'xRelative',
expr: xLength,
},
])
if (err(result)) return result
newLine = result.callExp
} else {
newLine = createCallExpression('angledLineOfXLength', [
createArrayExpression([angle, xLength]),
createPipeSubstitution(),
])
}
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
if (replaceExisting) { if (replaceExistingCallback) {
pipe.body[callIndex] = newLine pipe.body[callIndex] = newLine
} else { } else {
pipe.body = [...pipe.body, newLine] pipe.body = [...pipe.body, newLine]
@ -1042,7 +1088,9 @@ export const angledLineOfXLength: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
updateArgs: ({ node, pathToNode, to, from }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -1088,11 +1136,11 @@ export const angledLineOfYLength: SketchLineHelper = {
node, node,
previousProgramMemory, previousProgramMemory,
pathToNode, pathToNode,
to, segmentInput,
from, replaceExistingCallback,
createCallback,
replaceExisting,
}) => { }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { from, to } = segmentInput
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<PipeExpression>( const nodeMeta = getNodeFromPath<PipeExpression>(
_node, _node,
@ -1117,20 +1165,34 @@ export const angledLineOfYLength: SketchLineHelper = {
const angle = createLiteral(roundOff(getAngle(from, to), 0)) const angle = createLiteral(roundOff(getAngle(from, to), 0))
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1) const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
const newLine = createCallback let newLine: Expr
? createCallback( if (replaceExistingCallback) {
[angle, yLength], const result = replaceExistingCallback([
arrOrObjectRawValuesHelper([ {
[angle, 'angle', 'angle'], type: 'arrayOrObjItem',
[yLength, 'yRelative', 'length'], index: 0,
]) key: 'angle',
).callExp argType: 'angle',
: createCallExpression('angledLineOfYLength', [ expr: angle,
createArrayExpression([angle, yLength]), },
createPipeSubstitution(), {
]) type: 'arrayOrObjItem',
index: 1,
key: 'length',
argType: 'yRelative',
expr: yLength,
},
])
if (err(result)) return result
newLine = result.callExp
} else {
newLine = createCallExpression('angledLineOfYLength', [
createArrayExpression([angle, yLength]),
createPipeSubstitution(),
])
}
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
if (replaceExisting) { if (replaceExistingCallback) {
pipe.body[callIndex] = newLine pipe.body[callIndex] = newLine
} else { } else {
pipe.body = [...pipe.body, newLine] pipe.body = [...pipe.body, newLine]
@ -1140,7 +1202,9 @@ export const angledLineOfYLength: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
updateArgs: ({ node, pathToNode, to, from }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -1182,15 +1246,9 @@ export const angledLineOfYLength: SketchLineHelper = {
} }
export const angledLineToX: SketchLineHelper = { export const angledLineToX: SketchLineHelper = {
add: ({ add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
node, if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
pathToNode, const { from, to } = segmentInput
to,
from,
createCallback,
replaceExisting,
referencedSegment,
}) => {
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<PipeExpression>( const nodeMeta = getNodeFromPath<PipeExpression>(
_node, _node,
@ -1202,15 +1260,25 @@ export const angledLineToX: SketchLineHelper = {
const { node: pipe } = nodeMeta const { node: pipe } = nodeMeta
const angle = createLiteral(roundOff(getAngle(from, to), 0)) const angle = createLiteral(roundOff(getAngle(from, to), 0))
const xArg = createLiteral(roundOff(to[0], 2)) const xArg = createLiteral(roundOff(to[0], 2))
if (replaceExisting && createCallback) { if (replaceExistingCallback) {
const { callExp, valueUsedInTransform } = createCallback( const result = replaceExistingCallback([
[angle, xArg], {
arrOrObjectRawValuesHelper([ type: 'arrayOrObjItem',
[angle, 'angle', 'angle'], index: 0,
[xArg, 'xAbsolute', 'to'], key: 'angle',
]), argType: 'angle',
referencedSegment expr: angle,
) },
{
type: 'arrayOrObjItem',
index: 1,
key: 'to',
argType: 'xAbsolute',
expr: xArg,
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
pipe.body[callIndex] = callExp pipe.body[callIndex] = callExp
return { return {
@ -1230,7 +1298,9 @@ export const angledLineToX: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
updateArgs: ({ node, pathToNode, to, from }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -1270,15 +1340,9 @@ export const angledLineToX: SketchLineHelper = {
} }
export const angledLineToY: SketchLineHelper = { export const angledLineToY: SketchLineHelper = {
add: ({ add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
node, if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
pathToNode, const { from, to } = segmentInput
to,
from,
createCallback,
replaceExisting,
referencedSegment,
}) => {
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<PipeExpression>( const nodeMeta = getNodeFromPath<PipeExpression>(
_node, _node,
@ -1292,15 +1356,25 @@ export const angledLineToY: SketchLineHelper = {
const angle = createLiteral(roundOff(getAngle(from, to), 0)) const angle = createLiteral(roundOff(getAngle(from, to), 0))
const yArg = createLiteral(roundOff(to[1], 2)) const yArg = createLiteral(roundOff(to[1], 2))
if (replaceExisting && createCallback) { if (replaceExistingCallback) {
const { callExp, valueUsedInTransform } = createCallback( const result = replaceExistingCallback([
[angle, yArg], {
arrOrObjectRawValuesHelper([ type: 'arrayOrObjItem',
[angle, 'angle', 'angle'], index: 0,
[yArg, 'yAbsolute', 'to'], key: 'angle',
]), argType: 'angle',
referencedSegment expr: angle,
) },
{
type: 'arrayOrObjItem',
index: 1,
key: 'to',
argType: 'yAbsolute',
expr: yArg,
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
pipe.body[callIndex] = callExp pipe.body[callIndex] = callExp
return { return {
@ -1320,7 +1394,9 @@ export const angledLineToY: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
updateArgs: ({ node, pathToNode, to, from }) => { updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -1363,12 +1439,12 @@ export const angledLineThatIntersects: SketchLineHelper = {
add: ({ add: ({
node, node,
pathToNode, pathToNode,
to, segmentInput,
from, replaceExistingCallback,
createCallback,
replaceExisting,
referencedSegment, referencedSegment,
}) => { }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { from, to } = segmentInput
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<PipeExpression>( const nodeMeta = getNodeFromPath<PipeExpression>(
_node, _node,
@ -1395,24 +1471,23 @@ export const angledLineThatIntersects: SketchLineHelper = {
) )
) )
if (replaceExisting && createCallback) { if (replaceExistingCallback) {
const { callExp, valueUsedInTransform } = createCallback( const result = replaceExistingCallback([
[angle, offset], {
[ type: 'objectProperty',
{ key: 'angle',
type: 'objectProperty', argType: 'angle',
key: 'angle', expr: angle,
value: angle, },
argType: 'angle', {
}, type: 'objectProperty',
{ key: 'offset',
type: 'objectProperty', argType: 'intersectionOffset',
key: 'offset', expr: offset,
value: offset, },
argType: 'intersectionOffset', ])
}, if (err(result)) return result
] const { callExp, valueUsedInTransform } = result
)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
pipe.body[callIndex] = callExp pipe.body[callIndex] = callExp
return { return {
@ -1423,7 +1498,9 @@ export const angledLineThatIntersects: SketchLineHelper = {
} }
return new Error('not implemented') return new Error('not implemented')
}, },
updateArgs: ({ node, pathToNode, to, from, previousProgramMemory }) => { updateArgs: ({ node, pathToNode, input, previousProgramMemory }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta if (err(nodeMeta)) return nodeMeta
@ -1559,8 +1636,10 @@ export const angledLineThatIntersects: SketchLineHelper = {
export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({ export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
node, node,
pathToNode, pathToNode,
to, input,
}) => { }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to } = input
const _node = { ...node } const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode) const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
if (err(nodeMeta)) { if (err(nodeMeta)) {
@ -1615,8 +1694,7 @@ export function changeSketchArguments(
node: Program, node: Program,
programMemory: ProgramMemory, programMemory: ProgramMemory,
sourceRange: SourceRange, sourceRange: SourceRange,
args: [number, number], input: SegmentInputs
from: [number, number]
): { modifiedAst: Program; pathToNode: PathToNode } | Error { ): { modifiedAst: Program; pathToNode: PathToNode } | Error {
const _node = { ...node } const _node = { ...node }
const thePath = getNodePathFromSourceRange(_node, sourceRange) const thePath = getNodePathFromSourceRange(_node, sourceRange)
@ -1635,8 +1713,7 @@ export function changeSketchArguments(
node: _node, node: _node,
previousProgramMemory: programMemory, previousProgramMemory: programMemory,
pathToNode: shallowPath, pathToNode: shallowPath,
to: args, input,
from,
}) })
} }
@ -1684,8 +1761,7 @@ export function compareVec2Epsilon2(
interface CreateLineFnCallArgs { interface CreateLineFnCallArgs {
node: Program node: Program
programMemory: ProgramMemory programMemory: ProgramMemory
to: [number, number] input: SegmentInputs
from: [number, number]
fnName: ToolTip fnName: ToolTip
pathToNode: PathToNode pathToNode: PathToNode
spliceBetween?: boolean spliceBetween?: boolean
@ -1694,10 +1770,9 @@ interface CreateLineFnCallArgs {
export function addNewSketchLn({ export function addNewSketchLn({
node: _node, node: _node,
programMemory: previousProgramMemory, programMemory: previousProgramMemory,
to,
fnName, fnName,
pathToNode, pathToNode,
from, input: segmentInput,
spliceBetween = false, spliceBetween = false,
}: CreateLineFnCallArgs): }: CreateLineFnCallArgs):
| { | {
@ -1721,9 +1796,7 @@ export function addNewSketchLn({
node, node,
previousProgramMemory, previousProgramMemory,
pathToNode, pathToNode,
to, segmentInput,
from,
replaceExisting: false,
spliceBetween, spliceBetween,
}) })
} }
@ -1784,18 +1857,16 @@ export function replaceSketchLine({
programMemory, programMemory,
pathToNode: _pathToNode, pathToNode: _pathToNode,
fnName, fnName,
to, segmentInput,
from, replaceExistingCallback,
createCallback,
referencedSegment, referencedSegment,
}: { }: {
node: Program node: Program
programMemory: ProgramMemory programMemory: ProgramMemory
pathToNode: PathToNode pathToNode: PathToNode
fnName: ToolTip fnName: ToolTip
to: [number, number] segmentInput: SegmentInputs
from: [number, number] replaceExistingCallback: (rawArgs: RawArgs) => CreatedSketchExprResult | Error
createCallback: TransformCallback
referencedSegment?: Path referencedSegment?: Path
}): }):
| { | {
@ -1805,7 +1876,7 @@ export function replaceSketchLine({
} }
| Error { | Error {
if (![...toolTips, 'intersect'].includes(fnName)) { if (![...toolTips, 'intersect'].includes(fnName)) {
return new Error('not a tooltip') return new Error(`The following function name is not tooltip: ${fnName}`)
} }
const _node = { ...node } const _node = { ...node }
@ -1815,10 +1886,8 @@ export function replaceSketchLine({
previousProgramMemory: programMemory, previousProgramMemory: programMemory,
pathToNode: _pathToNode, pathToNode: _pathToNode,
referencedSegment, referencedSegment,
to, segmentInput,
from, replaceExistingCallback,
replaceExisting: true,
createCallback,
}) })
if (err(addRetVal)) return addRetVal if (err(addRetVal)) return addRetVal
@ -1826,13 +1895,16 @@ export function replaceSketchLine({
return { modifiedAst, valueUsedInTransform, pathToNode } return { modifiedAst, valueUsedInTransform, pathToNode }
} }
export function addTagForSketchOnFace(a: AddTagInfo, expressionName: string) { export function addTagForSketchOnFace(
tagInfo: AddTagInfo,
expressionName: string
) {
if (expressionName === 'close') { if (expressionName === 'close') {
return addTag(1)(a) return addTag(1)(tagInfo)
} }
if (expressionName in sketchLineHelperMap) { if (expressionName in sketchLineHelperMap) {
const { addTag } = sketchLineHelperMap[expressionName] const { addTag } = sketchLineHelperMap[expressionName]
return addTag(a) return addTag(tagInfo)
} }
return new Error(`"${expressionName}" is not a sketch line helper`) return new Error(`"${expressionName}" is not a sketch line helper`)
} }

View File

@ -43,6 +43,17 @@ export function getSketchSegmentFromSourceRange(
index: number index: number
} }
| Error { | Error {
const lineIndex = sketchGroup.value.findIndex(
({ __geoMeta: { sourceRange } }: Path) =>
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
)
const line = sketchGroup.value[lineIndex]
if (line) {
return {
segment: line,
index: lineIndex,
}
}
const startSourceRange = sketchGroup.start?.__geoMeta.sourceRange const startSourceRange = sketchGroup.start?.__geoMeta.sourceRange
if ( if (
startSourceRange && startSourceRange &&
@ -51,17 +62,7 @@ export function getSketchSegmentFromSourceRange(
sketchGroup.start sketchGroup.start
) )
return { segment: { ...sketchGroup.start, type: 'Base' }, index: -1 } return { segment: { ...sketchGroup.start, type: 'Base' }, index: -1 }
return new Error('could not find matching segment')
const lineIndex = sketchGroup.value.findIndex(
({ __geoMeta: { sourceRange } }: Path) =>
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
)
const line = sketchGroup.value[lineIndex]
if (!line) return new Error('could not find matching line')
return {
segment: line,
index: lineIndex,
}
} }
export function isSketchVariablesLinked( export function isSketchVariablesLinked(

File diff suppressed because it is too large Load Diff

View File

@ -8,23 +8,10 @@ import {
PathToNode, PathToNode,
CallExpression, CallExpression,
Literal, Literal,
BinaryPart,
} from '../wasm' } from '../wasm'
import { EngineCommandManager } from './engineConnection'
import { LineInputsType } from './sketchcombos' import { LineInputsType } from './sketchcombos'
export interface InternalFirstArg {
programMemory: ProgramMemory
name?: string
sourceRange: SourceRange
engineCommandManager: EngineCommandManager
code: string
}
export interface PathReturn {
programMemory: ProgramMemory
currentPath: Path
}
export interface ModifyAstBase { export interface ModifyAstBase {
node: Program node: Program
// TODO #896: Remove ProgramMemory from this interface // TODO #896: Remove ProgramMemory from this interface
@ -37,76 +24,162 @@ export interface AddTagInfo {
pathToNode: PathToNode pathToNode: PathToNode
} }
interface addCall extends ModifyAstBase { /** Inputs for all straight segments, to and from are absolute values, as this gives a
to: [number, number] * consistent base that can be converted to all of the line, angledLine, etc segment types
* One notable exception to "straight segment" is that tangentialArcTo is included in this
* Input type since it too only takes x-y values and is able to get extra info it needs
* to be tangential from the previous segment */
interface StraightSegmentInput {
type: 'straight-segment'
from: [number, number] from: [number, number]
to: [number, number]
}
/**
* SegmentInputs is a union type that can be either a StraightSegmentInput or an ArcSegmentInput.
*
* - StraightSegmentInput: Represents a straight segment with a starting point (from) and an ending point (to).
* - ArcSegmentInput: Represents an arc segment with a starting point (from), a center point, and a radius.
*/
export type SegmentInputs = StraightSegmentInput // TODO ArcSegmentInput
/**
* Interface for adding or replacing a sketch stblib call expression to a sketch.
* Replacing normally means adding or removing a constraint
*
* @property segmentInput - The input segment data, which can be either a straight segment or an arc segment.
* @property replaceExistingCallback - An optional callback function to replace an existing call expression,
* if not provided, a new call expression will be added using segMentInput values.
* @property referencedSegment - An optional path to a referenced segment.
* @property spliceBetween=false - Defaults to false. Normal behavior is to add a new callExpression to the end of the pipeExpression.
*/
interface addCall extends ModifyAstBase {
segmentInput: SegmentInputs
replaceExistingCallback?: (
rawArgs: RawArgs
) => CreatedSketchExprResult | Error
referencedSegment?: Path referencedSegment?: Path
replaceExisting?: boolean
createCallback?: TransformCallback // TODO: #29 probably should not be optional
/// defaults to false, normal behavior is to add a new callExpression to the end of the pipeExpression
spliceBetween?: boolean spliceBetween?: boolean
} }
interface updateArgs extends ModifyAstBase { interface updateArgs extends ModifyAstBase {
from: [number, number] input: SegmentInputs
to: [number, number]
} }
export type VarValueKeys = 'angle' | 'offset' | 'length' | 'to' | 'intersectTag' export type InputArgKeys = 'angle' | 'offset' | 'length' | 'to' | 'intersectTag'
export interface SingleValueInput<T> { export interface SingleValueInput<T> {
type: 'singleValue' type: 'singleValue'
argType: LineInputsType argType: LineInputsType
value: T expr: T
} }
export interface ArrayItemInput<T> { export interface ArrayItemInput<T> {
type: 'arrayItem' type: 'arrayItem'
index: 0 | 1 index: 0 | 1
argType: LineInputsType argType: LineInputsType
value: T expr: T
} }
export interface ObjectPropertyInput<T> { export interface ObjectPropertyInput<T> {
type: 'objectProperty' type: 'objectProperty'
key: VarValueKeys key: InputArgKeys
argType: LineInputsType argType: LineInputsType
value: T expr: T
} }
export interface ArrayOrObjItemInput<T> { interface ArrayOrObjItemInput<T> {
type: 'arrayOrObjItem' type: 'arrayOrObjItem'
key: VarValueKeys key: InputArgKeys
index: 0 | 1 index: 0 | 1
argType: LineInputsType argType: LineInputsType
value: T expr: T
} }
export type _VarValue<T> = interface ArrayInObject<T> {
type: 'arrayInObject'
key: InputArgKeys
argType: LineInputsType
index: 0 | 1
expr: T
}
type _InputArg<T> =
| SingleValueInput<T> | SingleValueInput<T>
| ArrayItemInput<T> | ArrayItemInput<T>
| ObjectPropertyInput<T> | ObjectPropertyInput<T>
| ArrayOrObjItemInput<T> | ArrayOrObjItemInput<T>
| ArrayInObject<T>
export type VarValue = _VarValue<Expr> /**
export type RawValue = _VarValue<Literal> * {@link RawArg.expr} is the current expression for each of the args for a segment
* i.e. if the expression is 5 + 6, {@link RawArg.expr} will be that binary expression
*
* Other properties on this type describe how the args are defined for this particular segment
* i.e. line uses [x, y] style inputs, while angledLine uses either [angle, length] or {angle, length}
* and circle uses {center: [x, y], radius: number}
* Which is why a union type is used that can be type narrowed using the {@link RawArg.type} property
* {@link RawArg.expr} is common to all of these types
*/
export type InputArg = _InputArg<Expr>
export type VarValues = Array<VarValue> /**
export type RawValues = Array<RawValue> * {@link RawArg.expr} is the literal equivalent of whatever current expression is
* i.e. if the expression is 5 + 6, the literal would be 11
* but of course works for expressions like myVar + someFn() etc too
* This is useful in cases where we want to "un-constrain" inputs to segments
*/
type RawArg = _InputArg<Literal>
type SimplifiedVarValue = export type InputArgs = Array<InputArg>
| {
type: 'singleValue'
}
| { type: 'arrayItem'; index: 0 | 1 }
| { type: 'objectProperty'; key: VarValueKeys }
export type TransformCallback = ( /**
args: [Expr, Expr], * The literal equivalent of whatever current expression is
literalValues: RawValues, * i.e. if the expression is 5 + 6, the literal would be 11
referencedSegment?: Path * but of course works for expressions like myVar + someFn() etc too
) => { * This is useful in cases where we want to "un-constrain" inputs to segments
*/
export type RawArgs = Array<RawArg>
/**
* Serves the same role as {@link InputArg} on {@link RawArg}
* but without the {@link RawArg.expr} property, since it is not needed
* when we only need to know where there arg is.
*/
export type SimplifiedArgDetails =
| Omit<SingleValueInput<null>, 'expr' | 'argType'>
| Omit<ArrayItemInput<null>, 'expr' | 'argType'>
| Omit<ObjectPropertyInput<null>, 'expr' | 'argType'>
| Omit<ArrayOrObjItemInput<null>, 'expr' | 'argType'>
| Omit<ArrayInObject<null>, 'expr' | 'argType'>
/**
* Represents the result of creating a sketch expression (line, tangentialArcTo, angledLine, circle, etc.).
*
* @property {Expr} callExp - This is the main result; recasting the expression should give the user the new function call.
* @property {number} [valueUsedInTransform] - Aside from `callExp`, we also return the number used in the transform, which is useful for constraints.
* For example, when adding a "horizontal distance" constraint, we don't want the segments to move, just constrain them in place.
* So the second segment will probably be something like `lineTo([segEndX($firstSegTag) + someLiteral, 123], %)` where `someLiteral` is
* the value of the current horizontal distance, That is we calculate the value needed to constrain the second segment without it moving.
* We can run the ast-mod to get this constraint `valueUsedInTransform` without applying the mod so that we can surface this to the user in a modal.
* We show them the modal where they can specify the distance they want to constrain to.
* We pre-fill this with the current value `valueUsedInTransform`, which they can accept or change, and we'll use that to apply the final ast-mod.
*/
export interface CreatedSketchExprResult {
callExp: Expr callExp: Expr
valueUsedInTransform?: number valueUsedInTransform?: number
} }
export type CreateStdLibSketchCallExpr = (args: {
inputs: InputArgs
rawArgs: RawArgs
referenceSegName: string
tag?: Expr
forceValueUsedInTransform?: BinaryPart
referencedSegment?: Path
}) => CreatedSketchExprResult | Error
export type TransformInfo = {
tooltip: ToolTip
createNode: CreateStdLibSketchCallExpr
}
export interface ConstrainInfo { export interface ConstrainInfo {
stdLibFnName: ToolTip stdLibFnName: ToolTip
type: LineInputsType | 'vertical' | 'horizontal' | 'tangentialWithPrevious' type: LineInputsType | 'vertical' | 'horizontal' | 'tangentialWithPrevious'
@ -115,7 +188,7 @@ export interface ConstrainInfo {
pathToNode: PathToNode pathToNode: PathToNode
value: string value: string
calculatedValue?: any calculatedValue?: any
argPosition?: SimplifiedVarValue argPosition?: SimplifiedArgDetails
} }
export interface SketchLineHelper { export interface SketchLineHelper {

View File

@ -20,10 +20,8 @@ import {
} from 'lang/queryAst' } from 'lang/queryAst'
import { CommandArgument } from './commandTypes' import { CommandArgument } from './commandTypes'
import { import {
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
getParentGroup, getParentGroup,
PROFILE_START, SEGMENT_BODIES_PLUS_PROFILE_START,
} from 'clientSideScene/sceneEntities' } from 'clientSideScene/sceneEntities'
import { Mesh, Object3D, Object3DEventMap } from 'three' import { Mesh, Object3D, Object3DEventMap } from 'three'
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra' import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
@ -162,11 +160,7 @@ export async function getEventForSelectWithPoint({
export function getEventForSegmentSelection( export function getEventForSegmentSelection(
obj: Object3D<Object3DEventMap> obj: Object3D<Object3DEventMap>
): ModelingMachineEvent | null { ): ModelingMachineEvent | null {
const group = getParentGroup(obj, [ const group = getParentGroup(obj, SEGMENT_BODIES_PLUS_PROFILE_START)
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
const axisGroup = getParentGroup(obj, [AXIS_GROUP]) const axisGroup = getParentGroup(obj, [AXIS_GROUP])
if (!group && !axisGroup) return null if (!group && !axisGroup) return null
if (axisGroup?.userData.type === AXIS_GROUP) { if (axisGroup?.userData.type === AXIS_GROUP) {
@ -303,12 +297,7 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
const updated = kclManager.ast const updated = kclManager.ast
Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => { Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => {
if ( if (!SEGMENT_BODIES_PLUS_PROFILE_START.includes(segmentGroup?.name)) return
![STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, PROFILE_START].includes(
segmentGroup?.name
)
)
return
const nodeMeta = getNodeFromPath<CallExpression>( const nodeMeta = getNodeFromPath<CallExpression>(
updated, updated,
segmentGroup.userData.pathToNode, segmentGroup.userData.pathToNode,