From 8610d606f4b746a89e47f07daf5839de9e8dab4e Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Fri, 13 Sep 2024 21:14:14 +1000 Subject: [PATCH] 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 --- src/clientSideScene/ClientSideSceneComp.tsx | 21 +- src/clientSideScene/sceneEntities.ts | 488 ++----- src/clientSideScene/segments.ts | 668 ++++++--- src/components/ModelingMachineProvider.tsx | 1 + src/components/Toolbar/EqualAngle.tsx | 2 +- src/components/Toolbar/EqualLength.tsx | 2 +- src/components/Toolbar/HorzVert.tsx | 2 +- src/components/Toolbar/Intersect.tsx | 13 +- .../Toolbar/RemoveConstrainingValues.tsx | 2 +- src/components/Toolbar/SetAbsDistance.tsx | 13 +- src/components/Toolbar/SetAngleBetween.tsx | 13 +- .../Toolbar/SetHorzVertDistance.tsx | 9 +- src/components/Toolbar/setAngleLength.tsx | 14 +- src/lang/langHelpers.ts | 5 +- src/lang/modifyAst.test.ts | 47 +- src/lang/modifyAst.ts | 48 +- src/lang/queryAst.ts | 10 + src/lang/std/sketch.test.ts | 14 +- src/lang/std/sketch.ts | 612 ++++---- src/lang/std/sketchConstraints.ts | 23 +- src/lang/std/sketchcombos.ts | 1266 +++++++++-------- src/lang/std/stdTypes.ts | 165 ++- src/lib/selections.ts | 17 +- 23 files changed, 1822 insertions(+), 1633 deletions(-) diff --git a/src/clientSideScene/ClientSideSceneComp.tsx b/src/clientSideScene/ClientSideSceneComp.tsx index 9c31dc5f38..7a1c66e753 100644 --- a/src/clientSideScene/ClientSideSceneComp.tsx +++ b/src/clientSideScene/ClientSideSceneComp.tsx @@ -34,13 +34,11 @@ import { CustomIcon, CustomIconName } from 'components/CustomIcon' import { ConstrainInfo } from 'lang/std/stdTypes' import { getConstraintInfo } from 'lang/std/sketch' import { Dialog, Popover, Transition } from '@headlessui/react' -import { LineInputsType } from 'lang/std/sketchcombos' import toast from 'react-hot-toast' import { InstanceProps, create } from 'react-modal-promise' import { executeAst } from 'lang/langHelpers' import { deleteSegmentFromPipeExpression, - makeRemoveSingleConstraintInput, removeSingleConstraintInfo, } from 'lang/modifyAst' import { ActionButton } from 'components/ActionButton' @@ -542,12 +540,10 @@ const ConstraintSymbol = ({ iconName: 'dimension', }, } - const varName = - _type in varNameMap ? varNameMap[_type as LineInputsType].varName : 'var' - const name: CustomIconName = varNameMap[_type as LineInputsType].iconName - const displayName = varNameMap[_type as LineInputsType]?.displayName - const implicitDesc = - varNameMap[_type as LineInputsType]?.implicitConstraintDesc + const varName = varNameMap?.[_type]?.varName || 'var' + const name: CustomIconName = varNameMap[_type].iconName + const displayName = varNameMap[_type]?.displayName + const implicitDesc = varNameMap[_type]?.implicitConstraintDesc const _node = useMemo( () => getNodeFromPath(kclManager.ast, pathToNode), @@ -604,13 +600,10 @@ const ConstraintSymbol = ({ if (trap(_node1)) return Promise.reject(_node1) const shallowPath = _node1.shallowPath - const input = makeRemoveSingleConstraintInput( - argPosition, - shallowPath - ) - if (!input || !context.sketchDetails) return + if (!context.sketchDetails || !argPosition) return const transform = removeSingleConstraintInfo( - input, + shallowPath, + argPosition, kclManager.ast, kclManager.programMemory ) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index ee6ad844d8..1262297717 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -1,10 +1,8 @@ import { BoxGeometry, DoubleSide, - ExtrudeGeometry, Group, Intersection, - LineCurve3, Mesh, MeshBasicMaterial, Object3D, @@ -15,7 +13,6 @@ import { Points, Quaternion, Scene, - Shape, Vector2, Vector3, } from 'three' @@ -27,8 +24,6 @@ import { OnClickCallbackArgs, OnMouseEnterLeaveArgs, RAYCASTABLE_PLANE, - SEGMENT_LENGTH_LABEL, - SEGMENT_LENGTH_LABEL_TEXT, SKETCH_GROUP_SEGMENTS, SKETCH_LAYER, X_AXIS, @@ -37,7 +32,6 @@ import { import { isQuaternionVertical, quaternionFromUpNForward } from './helpers' import { CallExpression, - getTangentialArcToInfo, parse, Path, PathToNode, @@ -61,11 +55,9 @@ import { import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' import { executeAst } from 'lang/langHelpers' import { - createArcGeometry, - dashedStraight, - profileStart, - straightSegment, - tangentialArcToSegment, + createProfileStartHandle, + SegmentUtils, + segmentUtils, } from './segments' import { addCallExpressionsToPipe, @@ -74,13 +66,7 @@ import { changeSketchArguments, updateStartProfileAtArgs, } from 'lang/std/sketch' -import { - isArray, - isOverlap, - normaliseAngle, - roundOff, - throttle, -} from 'lib/utils' +import { isArray, isOverlap, roundOff } from 'lib/utils' import { addStartProfileAt, createArrayExpression, @@ -91,7 +77,6 @@ import { findUniqueName, } from 'lang/modifyAst' import { Selections, getEventForSegmentSelection } from 'lib/selections' -import { getTangentPointFromPreviousArc } from 'lib/utils2d' import { createGridHelper, orthoScale, perspScale } from './helpers' import { Models } from '@kittycad/lib' 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 HIDE_SEGMENT_LENGTH = 75 // 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] @@ -154,37 +144,35 @@ export class SceneEntities { ? orthoFactor : perspScale(sceneInfra.camControls.camera, segment)) / sceneInfra._baseUnitMultiplier + const input = { + type: 'straight-segment', + from: segment.userData.from, + to: segment.userData.to, + } as const + let update: SegmentUtils['update'] | null = null if ( segment.userData.from && segment.userData.to && segment.userData.type === STRAIGHT_SEGMENT ) { - callbacks.push( - this.updateStraightSegment({ - from: segment.userData.from, - to: segment.userData.to, - group: segment, - scale: factor, - }) - ) + update = segmentUtils.straight.update } - if ( segment.userData.from && segment.userData.to && segment.userData.prevSegment && segment.userData.type === TANGENTIAL_ARC_TO_SEGMENT ) { - callbacks.push( - this.updateTangentialArcToSegment({ - prevSegment: segment.userData.prevSegment, - from: segment.userData.from, - to: segment.userData.to, - group: segment, - scale: factor, - }) - ) + update = segmentUtils.tangentialArcTo.update } + const callBack = update?.({ + prevSegment: segment.userData.prevSegment, + input, + group: segment, + scale: factor, + sceneInfra, + }) + callBack && !err(callBack) && callbacks.push(callBack) if (segment.name === PROFILE_START) { segment.scale.set(factor, factor, factor) } @@ -421,7 +409,7 @@ export class SceneEntities { maybeModdedAst, sketchGroup.start.__geoMeta.sourceRange ) - const _profileStart = profileStart({ + const _profileStart = createProfileStartHandle({ from: sketchGroup.start.from, id: sketchGroup.start.__geoMeta.id, pathToNode: segPathToNode, @@ -476,50 +464,31 @@ export class SceneEntities { if (err(_node1)) return const callExpName = _node1.node?.callee?.name - if (segment.type === 'TangentialArcTo') { - seg = tangentialArcToSegment({ - prevSegment: sketchGroup.value[index - 1], + const initSegment = + segment.type === 'TangentialArcTo' + ? segmentUtils.tangentialArcTo.init + : segmentUtils.straight.init + const result = initSegment({ + prevSegment: sketchGroup.value[index - 1], + callExpName, + input: { + type: 'straight-segment', from: segment.from, to: segment.to, - id: segment.__geoMeta.id, - pathToNode: segPathToNode, - isDraftSegment, - scale: factor, - texture: sceneInfra.extraSegmentTexture, - theme: sceneInfra._theme, - isSelected, - }) - callbacks.push( - this.updateTangentialArcToSegment({ - prevSegment: sketchGroup.value[index - 1], - from: segment.from, - to: segment.to, - group: seg, - 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, - }) - ) - } + }, + id: segment.__geoMeta.id, + pathToNode: segPathToNode, + isDraftSegment, + scale: factor, + texture: sceneInfra.extraSegmentTexture, + theme: sceneInfra._theme, + isSelected, + sceneInfra, + }) + if (err(result)) return + const { group: _group, updateOverlaysCallback } = result + seg = _group + callbacks.push(updateOverlaysCallback) seg.layers.set(SKETCH_LAYER) seg.traverse((child) => { child.layers.set(SKETCH_LAYER) @@ -602,16 +571,19 @@ export class SceneEntities { kclManager.programMemory.get(variableDeclarationName), variableDeclarationName ) - if (err(sg)) return sg - const lastSeg = sg.value?.slice(-1)[0] || sg.start + if (err(sg)) return Promise.reject(sg) + 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 mod = addNewSketchLn({ node: _ast, programMemory: kclManager.programMemory, - to: [lastSeg.to[0], lastSeg.to[1]], - from: [lastSeg.to[0], lastSeg.to[1]], + input: { + type: 'straight-segment', + to: lastSeg.to, + from: lastSeg.to, + }, fnName: segmentName, pathToNode: sketchPathToNode, }) @@ -682,8 +654,11 @@ export class SceneEntities { const tmp = addNewSketchLn({ node: kclManager.ast, programMemory: kclManager.programMemory, - to: [intersection2d.x, intersection2d.y], - from: [lastSegment.to[0], lastSegment.to[1]], + input: { + type: 'straight-segment', + from: [lastSegment.to[0], lastSegment.to[1]], + to: [intersection2d.x, intersection2d.y], + }, fnName: lastSegment.type === 'TangentialArcTo' ? 'tangentialArcTo' @@ -834,14 +809,14 @@ export class SceneEntities { sketchPathToNode || [], 'VariableDeclaration' ) - if (trap(_node)) return Promise.reject(_node) + if (trap(_node)) return const sketchInit = _node.node?.declarations?.[0]?.init if (sketchInit.type === 'PipeExpression') { updateRectangleSketch(sketchInit, x, y, tags[0]) let _recastAst = parse(recast(_ast)) - if (trap(_recastAst)) return Promise.reject(_recastAst) + if (trap(_recastAst)) return _ast = _recastAst // Update the primary AST and unequip the rectangle tool @@ -861,7 +836,7 @@ export class SceneEntities { programMemory.get(variableDeclarationName), variableDeclarationName ) - if (err(sketchGroup)) return sketchGroup + if (err(sketchGroup)) return const sgPaths = sketchGroup.value const orthoFactor = orthoScale(sceneInfra.camControls.camera) @@ -950,8 +925,11 @@ export class SceneEntities { const mod = addNewSketchLn({ node: kclManager.ast, programMemory: kclManager.programMemory, - to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y], - from: [prevSegment.from[0], prevSegment.from[1]], + input: { + type: 'straight-segment', + to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y], + from: prevSegment.from, + }, // TODO assuming it's always a straight segments being added // as this is easiest, and we'll need to add "tabbing" behavior // to support other segment types @@ -1072,7 +1050,7 @@ export class SceneEntities { group.userData.from[0], 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 } const _node = getNodeFromPath( @@ -1095,8 +1073,11 @@ export class SceneEntities { modded = updateStartProfileAtArgs({ node: modifiedAst, pathToNode, - to, - from, + input: { + type: 'straight-segment', + to: dragTo, + from, + }, previousProgramMemory: kclManager.programMemory, }) } else { @@ -1104,8 +1085,11 @@ export class SceneEntities { modifiedAst, kclManager.programMemory, [node.start, node.end], - to, - from + { + type: 'straight-segment', + from, + to: dragTo, + } ) } if (trap(modded)) return @@ -1208,264 +1192,36 @@ export class SceneEntities { ? orthoFactor : perspScale(sceneInfra.camControls.camera, group)) / 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) { - return this.updateTangentialArcToSegment({ - prevSegment: sgPaths[index - 1], - from: segment.from, - to: segment.to, - group: group, - scale: factor, - }) + update = segmentUtils.tangentialArcTo.update } else if (type === STRAIGHT_SEGMENT) { - return this.updateStraightSegment({ - from: segment.from, - to: segment.to, + update = segmentUtils.straight.update + } + const callBack = + update && + !err(update) && + update({ + input, group, 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.scale.set(factor, factor, factor) } 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[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 * that represent each of the sketch segments, to get the @@ -1578,27 +1334,30 @@ export class SceneEntities { } const orthoFactor = orthoScale(sceneInfra.camControls.camera) + const input = { + type: 'straight-segment', + from: parent.userData.from, + to: parent.userData.to, + } as const const factor = (sceneInfra.camControls.camera instanceof OrthographicCamera ? orthoFactor : perspScale(sceneInfra.camControls.camera, parent)) / sceneInfra._baseUnitMultiplier + let update: SegmentUtils['update'] | null = null if (parent.name === STRAIGHT_SEGMENT) { - this.updateStraightSegment({ - from: parent.userData.from, - to: parent.userData.to, - group: parent, - scale: factor, - }) + update = segmentUtils.straight.update } else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) { - this.updateTangentialArcToSegment({ + update = segmentUtils.tangentialArcTo.update + } + update && + update({ prevSegment: parent.userData.prevSegment, - from: parent.userData.from, - to: parent.userData.to, + input, group: parent, scale: factor, + sceneInfra, }) - } return } editorManager.setHighlightRange([[0, 0]]) @@ -1613,27 +1372,30 @@ export class SceneEntities { if (parent) { const orthoFactor = orthoScale(sceneInfra.camControls.camera) + const input = { + type: 'straight-segment', + from: parent.userData.from, + to: parent.userData.to, + } as const const factor = (sceneInfra.camControls.camera instanceof OrthographicCamera ? orthoFactor : perspScale(sceneInfra.camControls.camera, parent)) / sceneInfra._baseUnitMultiplier + let update: SegmentUtils['update'] | null = null if (parent.name === STRAIGHT_SEGMENT) { - this.updateStraightSegment({ - from: parent.userData.from, - to: parent.userData.to, - group: parent, - scale: factor, - }) + update = segmentUtils.straight.update } else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) { - this.updateTangentialArcToSegment({ + update = segmentUtils.tangentialArcTo.update + } + update && + update({ prevSegment: parent.userData.prevSegment, - from: parent.userData.from, - to: parent.userData.to, + input, group: parent, scale: factor, + sceneInfra, }) - } } const isSelected = parent?.userData?.isSelected colorSegment( diff --git a/src/clientSideScene/segments.ts b/src/clientSideScene/segments.ts index e1d451fe0b..bf1c8e636b 100644 --- a/src/clientSideScene/segments.ts +++ b/src/clientSideScene/segments.ts @@ -26,6 +26,7 @@ import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm' import { EXTRA_SEGMENT_HANDLE, EXTRA_SEGMENT_OFFSET_PX, + HIDE_HOVER_SEGMENT_LENGTH, HIDE_SEGMENT_LENGTH, PROFILE_START, SEGMENT_WIDTH_PX, @@ -35,175 +36,484 @@ import { TANGENTIAL_ARC_TO_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT_BODY, TANGENTIAL_ARC_TO__SEGMENT_DASH, + getParentGroup, } from './sceneEntities' import { getTangentPointFromPreviousArc } from 'lib/utils2d' import { ARROWHEAD, + SceneInfra, SEGMENT_LENGTH_LABEL, SEGMENT_LENGTH_LABEL_OFFSET_PX, SEGMENT_LENGTH_LABEL_TEXT, } from './sceneInfra' 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({ - from, - id, - pathToNode, - scale = 1, - theme, - isSelected, -}: { - from: Coords2d +interface CreateSegmentArgs { + input: SegmentInputs + prevSegment: SketchGroup['value'][number] id: string pathToNode: PathToNode + isDraftSegment?: boolean scale?: number + callExpName: string + texture: Texture theme: Themes isSelected?: boolean -}) { - const group = new Group() + sceneInfra: SceneInfra +} - const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later - const baseColor = getThemeColorForThreeJs(theme) - const color = isSelected ? 0x0000ff : baseColor - const body = new MeshBasicMaterial({ color }) - const mesh = new Mesh(geometry, body) +interface UpdateSegmentArgs { + input: SegmentInputs + prevSegment: SketchGroup['value'][number] + group: Group + sceneInfra: SceneInfra + scale?: number +} - group.add(mesh) +interface CreateSegmentResult { + group: Group + updateOverlaysCallback: () => SegmentOverlayPayload | null +} - group.userData = { - type: PROFILE_START, +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, - from, 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, - baseColor, + 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, + }) } - group.name = PROFILE_START - group.position.set(from[0], from[1], 0) - group.scale.set(scale, scale, scale) - return group } -export function straightSegment({ +export function createProfileStartHandle({ from, - to, id, pathToNode, - isDraftSegment, scale = 1, - callExpName, - texture, theme, - isSelected = false, + isSelected, }: { 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 group = new Group() - const baseColor = - callExpName === 'close' ? 0x444444 : getThemeColorForThreeJs(theme) + const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later + const baseColor = 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, + group.add(mesh) + + group.userData = { + type: PROFILE_START, 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 + group.name = PROFILE_START + group.position.set(from[0], from[1], 0) + group.scale.set(scale, scale, scale) + return group } function createArrowhead(scale = 1, theme: Themes, color?: number): Group { @@ -267,12 +577,12 @@ function createLengthIndicator({ from, to, scale, - length, + length = 0.1, }: { from: Coords2d to: Coords2d scale: number - length: number + length?: number }) { const lengthIndicatorGroup = new Group() lengthIndicatorGroup.name = SEGMENT_LENGTH_LABEL @@ -300,111 +610,6 @@ function createLengthIndicator({ 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({ center, radius, @@ -579,3 +784,8 @@ export function dashedStraight( geo.userData.type = 'dashed' return geo } + +export const segmentUtils = { + straight: new StraightSegment(), + tangentialArcTo: new TangentialArcToSegment(), +} as const diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 406a922d9e..533d1121f5 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -204,6 +204,7 @@ export const ModelingMachineProvider = ({ pathToNodeString, }, }) + // overlay timeout }, 800) as unknown as number return { ...context.segmentHoverMap, diff --git a/src/components/Toolbar/EqualAngle.tsx b/src/components/Toolbar/EqualAngle.tsx index ec4f719ac9..892088bc41 100644 --- a/src/components/Toolbar/EqualAngle.tsx +++ b/src/components/Toolbar/EqualAngle.tsx @@ -10,10 +10,10 @@ import { transformSecondarySketchLinesTagFirst, getTransformInfos, PathToNodeMap, - TransformInfo, } from '../../lang/std/sketchcombos' import { kclManager } from 'lib/singletons' import { err } from 'lib/trap' +import { TransformInfo } from 'lang/std/stdTypes' export function equalAngleInfo({ selectionRanges, diff --git a/src/components/Toolbar/EqualLength.tsx b/src/components/Toolbar/EqualLength.tsx index d99ff588b2..23b27bc79e 100644 --- a/src/components/Toolbar/EqualLength.tsx +++ b/src/components/Toolbar/EqualLength.tsx @@ -10,8 +10,8 @@ import { transformSecondarySketchLinesTagFirst, getTransformInfos, PathToNodeMap, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { kclManager } from 'lib/singletons' import { err } from 'lib/trap' diff --git a/src/components/Toolbar/HorzVert.tsx b/src/components/Toolbar/HorzVert.tsx index c06ad82f1d..797db49a74 100644 --- a/src/components/Toolbar/HorzVert.tsx +++ b/src/components/Toolbar/HorzVert.tsx @@ -9,8 +9,8 @@ import { PathToNodeMap, getTransformInfos, transformAstSketchLines, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { kclManager } from 'lib/singletons' import { err } from 'lib/trap' diff --git a/src/components/Toolbar/Intersect.tsx b/src/components/Toolbar/Intersect.tsx index 2398b9c8fe..c2cd833e83 100644 --- a/src/components/Toolbar/Intersect.tsx +++ b/src/components/Toolbar/Intersect.tsx @@ -1,6 +1,6 @@ import { toolTips } from 'lang/langHelpers' import { Selections } from 'lib/selections' -import { BinaryPart, Program, Expr, VariableDeclarator } from '../../lang/wasm' +import { Program, Expr, VariableDeclarator } from '../../lang/wasm' import { getNodePathFromSourceRange, getNodeFromPath, @@ -11,8 +11,9 @@ import { transformSecondarySketchLinesTagFirst, getTransformInfos, PathToNodeMap, - TransformInfo, + isExprBinaryPart, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { createVariableDeclaration } from '../../lang/modifyAst' import { removeDoubleNegatives } from '../AvailableVarsHelpers' @@ -178,11 +179,9 @@ export async function applyConstraintIntersect({ } } // transform again but forcing certain values - const finalValue = removeDoubleNegatives( - valueNode as BinaryPart, - sign, - variableName - ) + if (!isExprBinaryPart(valueNode)) + return Promise.reject('Invalid valueNode, is not a BinaryPart') + const finalValue = removeDoubleNegatives(valueNode, sign, variableName) const transform2 = transformSecondarySketchLinesTagFirst({ ast: kclManager.ast, selectionRanges: forcedSelectionRanges, diff --git a/src/components/Toolbar/RemoveConstrainingValues.tsx b/src/components/Toolbar/RemoveConstrainingValues.tsx index 44c4a4f2c1..dd88fad010 100644 --- a/src/components/Toolbar/RemoveConstrainingValues.tsx +++ b/src/components/Toolbar/RemoveConstrainingValues.tsx @@ -9,8 +9,8 @@ import { PathToNodeMap, getRemoveConstraintsTransforms, transformAstSketchLines, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { kclManager } from 'lib/singletons' import { err } from 'lib/trap' diff --git a/src/components/Toolbar/SetAbsDistance.tsx b/src/components/Toolbar/SetAbsDistance.tsx index 42be910ea1..decae95655 100644 --- a/src/components/Toolbar/SetAbsDistance.tsx +++ b/src/components/Toolbar/SetAbsDistance.tsx @@ -1,6 +1,6 @@ import { toolTips } from 'lang/langHelpers' import { Selections } from 'lib/selections' -import { BinaryPart, Program, Expr } from '../../lang/wasm' +import { Program, Expr } from '../../lang/wasm' import { getNodePathFromSourceRange, getNodeFromPath, @@ -9,8 +9,9 @@ import { getTransformInfos, transformAstSketchLines, PathToNodeMap, - TransformInfo, + isExprBinaryPart, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { SetAngleLengthModal, createSetAngleLengthModal, @@ -121,11 +122,9 @@ export async function applyConstraintAbsDistance({ value: forceVal, valueName: constraint === 'yAbs' ? 'yDis' : 'xDis', }) - let finalValue = removeDoubleNegatives( - valueNode as BinaryPart, - sign, - variableName - ) + if (!isExprBinaryPart(valueNode)) + return Promise.reject('Invalid valueNode, is not a BinaryPart') + let finalValue = removeDoubleNegatives(valueNode, sign, variableName) const transform2 = transformAstSketchLines({ ast: structuredClone(kclManager.ast), diff --git a/src/components/Toolbar/SetAngleBetween.tsx b/src/components/Toolbar/SetAngleBetween.tsx index 77a2129bba..17296fcf01 100644 --- a/src/components/Toolbar/SetAngleBetween.tsx +++ b/src/components/Toolbar/SetAngleBetween.tsx @@ -1,6 +1,6 @@ import { toolTips } from 'lang/langHelpers' import { Selections } from 'lib/selections' -import { BinaryPart, Program, Expr, VariableDeclarator } from '../../lang/wasm' +import { Program, Expr, VariableDeclarator } from '../../lang/wasm' import { getNodePathFromSourceRange, getNodeFromPath, @@ -10,8 +10,9 @@ import { transformSecondarySketchLinesTagFirst, getTransformInfos, PathToNodeMap, - TransformInfo, + isExprBinaryPart, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { createVariableDeclaration } from '../../lang/modifyAst' import { removeDoubleNegatives } from '../AvailableVarsHelpers' @@ -133,11 +134,9 @@ export async function applyConstraintAngleBetween({ } } - const finalValue = removeDoubleNegatives( - valueNode as BinaryPart, - sign, - variableName - ) + if (!isExprBinaryPart(valueNode)) + return Promise.reject('Invalid valueNode, is not a BinaryPart') + const finalValue = removeDoubleNegatives(valueNode, sign, variableName) // transform again but forcing certain values const transformed2 = transformSecondarySketchLinesTagFirst({ ast: kclManager.ast, diff --git a/src/components/Toolbar/SetHorzVertDistance.tsx b/src/components/Toolbar/SetHorzVertDistance.tsx index 36240bb714..428c680d2c 100644 --- a/src/components/Toolbar/SetHorzVertDistance.tsx +++ b/src/components/Toolbar/SetHorzVertDistance.tsx @@ -1,5 +1,5 @@ import { toolTips } from 'lang/langHelpers' -import { BinaryPart, Program, Expr, VariableDeclarator } from '../../lang/wasm' +import { Program, Expr, VariableDeclarator } from '../../lang/wasm' import { getNodePathFromSourceRange, getNodeFromPath, @@ -9,8 +9,9 @@ import { transformSecondarySketchLinesTagFirst, getTransformInfos, PathToNodeMap, - TransformInfo, + isExprBinaryPart, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst' import { removeDoubleNegatives } from '../AvailableVarsHelpers' @@ -139,9 +140,11 @@ export async function applyConstraintHorzVertDistance({ pathToNodeMap, } } else { + if (!isExprBinaryPart(valueNode)) + return Promise.reject('Invalid valueNode, is not a BinaryPart') let finalValue = isAlign ? createLiteral(0) - : removeDoubleNegatives(valueNode as BinaryPart, sign, variableName) + : removeDoubleNegatives(valueNode, sign, variableName) // transform again but forcing certain values const transformed = transformSecondarySketchLinesTagFirst({ ast: kclManager.ast, diff --git a/src/components/Toolbar/setAngleLength.tsx b/src/components/Toolbar/setAngleLength.tsx index a36cc4346d..70c68c9138 100644 --- a/src/components/Toolbar/setAngleLength.tsx +++ b/src/components/Toolbar/setAngleLength.tsx @@ -1,6 +1,6 @@ import { toolTips } from 'lang/langHelpers' import { Selections } from 'lib/selections' -import { BinaryPart, Program, Expr } from '../../lang/wasm' +import { Program, Expr } from '../../lang/wasm' import { getNodePathFromSourceRange, getNodeFromPath, @@ -8,9 +8,10 @@ import { import { PathToNodeMap, getTransformInfos, + isExprBinaryPart, transformAstSketchLines, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { SetAngleLengthModal, createSetAngleLengthModal, @@ -125,12 +126,9 @@ export async function applyConstraintAngleLength({ valueName: angleOrLength === 'setAngle' ? 'angle' : 'length', shouldCreateVariable: true, }) - - let finalValue = removeDoubleNegatives( - valueNode as BinaryPart, - sign, - variableName - ) + if (!isExprBinaryPart(valueNode)) + return Promise.reject('Invalid valueNode, is not a BinaryPart') + let finalValue = removeDoubleNegatives(valueNode, sign, variableName) if ( isReferencingYAxisAngle || (isReferencingXAxisAngle && calcIdentifier.name !== 'ZERO') diff --git a/src/lang/langHelpers.ts b/src/lang/langHelpers.ts index 82fca3fc3e..bd39f4e814 100644 --- a/src/lang/langHelpers.ts +++ b/src/lang/langHelpers.ts @@ -26,9 +26,6 @@ export type ToolTip = | 'tangentialArcTo' export const toolTips = [ - 'sketch_line', - 'move', - // original tooltips 'line', 'lineTo', 'angledLine', @@ -42,7 +39,7 @@ export const toolTips = [ 'yLineTo', 'angledLineThatIntersects', 'tangentialArcTo', -] as any as ToolTip[] +] export async function executeAst({ ast, diff --git a/src/lang/modifyAst.test.ts b/src/lang/modifyAst.test.ts index b196dfb9db..51ef65524c 100644 --- a/src/lang/modifyAst.test.ts +++ b/src/lang/modifyAst.test.ts @@ -20,6 +20,7 @@ import { import { enginelessExecutor } from '../lib/testHelpers' import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst' import { err } from 'lib/trap' +import { SimplifiedArgDetails } from './std/stdTypes' beforeAll(async () => { await initPromise @@ -627,7 +628,7 @@ describe('Testing removeSingleConstraintInfo', () => { 'offset', ], ['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) if (err(ast)) throw ast @@ -638,11 +639,27 @@ describe('Testing removeSingleConstraintInfo', () => { code.indexOf(lineOfInterest) + lineOfInterest.length, ] 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( - { - pathToCallExp: pathToNode, - [key]: value, - }, + pathToNode, + argPosition, ast, programMemory ) @@ -675,12 +692,24 @@ describe('Testing removeSingleConstraintInfo', () => { code.indexOf(lineOfInterest) + 1, 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 mod = removeSingleConstraintInfo( - { - pathToCallExp: pathToNode, - [key]: value, - }, + pathToNode, + argPosition, ast, programMemory ) diff --git a/src/lang/modifyAst.ts b/src/lang/modifyAst.ts index 17ac2f0d54..0dda319d34 100644 --- a/src/lang/modifyAst.ts +++ b/src/lang/modifyAst.ts @@ -38,7 +38,7 @@ import { import { DefaultPlaneStr } from 'clientSideScene/sceneEntities' import { isOverlap, roundOff } from 'lib/utils' 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 { Models } from '@kittycad/lib' @@ -799,15 +799,10 @@ export function deleteSegmentFromPipeExpression( ) if (!constraintInfo) return - const input = makeRemoveSingleConstraintInput( - constraintInfo.argPosition, - callExp.shallowPath - ) - if (!input) return + if (!constraintInfo.argPosition) return const transform = removeSingleConstraintInfo( - { - ...input, - }, + callExp.shallowPath, + constraintInfo.argPosition, _modifiedAst, programMemory ) @@ -834,37 +829,9 @@ export function deleteSegmentFromPipeExpression( return _modifiedAst } -export function makeRemoveSingleConstraintInput( - argPosition: ConstrainInfo['argPosition'], - pathToNode: PathToNode -): Parameters[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( - { - pathToCallExp, - arrayIndex, - objectProperty, - }: { - pathToCallExp: PathToNode - arrayIndex?: number - objectProperty?: string - }, + pathToCallExp: PathToNode, + argDetails: SimplifiedArgDetails, ast: Program, programMemory: ProgramMemory ): @@ -875,8 +842,7 @@ export function removeSingleConstraintInfo( | false { const transform = removeSingleConstraint({ pathToCallExp, - arrayIndex, - objectProperty, + inputDetails: argDetails, ast, }) if (!transform) return false diff --git a/src/lang/queryAst.ts b/src/lang/queryAst.ts index 236b21a501..8fcfd2ad9f 100644 --- a/src/lang/queryAst.ts +++ b/src/lang/queryAst.ts @@ -16,6 +16,7 @@ import { VariableDeclaration, VariableDeclarator, sketchGroupFromKclValue, + ObjectExpression, } from './wasm' import { createIdentifier, splitPathAtLastIndex } from './modifyAst' import { getSketchSegmentFromSourceRange } from './std/sketchConstraints' @@ -934,3 +935,12 @@ export function hasExtrudableGeometry(ast: Program) { }) 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 } +} diff --git a/src/lang/std/sketch.test.ts b/src/lang/std/sketch.test.ts index e5735f4c8b..2496390552 100644 --- a/src/lang/std/sketch.test.ts +++ b/src/lang/std/sketch.test.ts @@ -123,8 +123,11 @@ describe('testing changeSketchArguments', () => { ast, programMemory, [sourceStart, sourceStart + lineToChange.length], - [2, 3], - [0, 0] + { + type: 'straight-segment', + from: [0, 0], + to: [2, 3], + } ) if (err(changeSketchArgsRetVal)) return changeSketchArgsRetVal expect(recast(changeSketchArgsRetVal.modifiedAst)).toBe(expectedCode) @@ -150,8 +153,11 @@ const mySketch001 = startSketchOn('XY') const newSketchLnRetVal = addNewSketchLn({ node: ast, programMemory, - to: [2, 3], - from: [0, 0], + input: { + type: 'straight-segment', + from: [0, 0], + to: [2, 3], + }, fnName: 'lineTo', pathToNode: [ ['body', ''], diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index 068c2fd63a..c212c279dd 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -9,7 +9,6 @@ import { CallExpression, VariableDeclarator, Expr, - Literal, VariableDeclaration, Identifier, sketchGroupFromKclValue, @@ -20,7 +19,6 @@ import { getNodePathFromSourceRange, } from 'lang/queryAst' import { - LineInputsType, isLiteralArrayOrStatic, isNotLiteralArrayOrStatic, } from 'lang/std/sketchcombos' @@ -29,15 +27,15 @@ import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst' import { SketchLineHelper, - TransformCallback, ConstrainInfo, - RawValues, ArrayItemInput, ObjectPropertyInput, SingleValueInput, - VarValueKeys, - ArrayOrObjItemInput, AddTagInfo, + SegmentInputs, + SimplifiedArgDetails, + RawArgs, + CreatedSketchExprResult, } from 'lang/std/stdTypes' import { @@ -56,6 +54,10 @@ import { err } from 'lib/trap' import { perpendicularDistance } from 'sketch-helpers' 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 function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d { @@ -109,6 +111,7 @@ type AbbreviatedInput = | ArrayItemInput['index'] | ObjectPropertyInput['key'] | SingleValueInput['type'] + | SimplifiedArgDetails | undefined const constrainInfo = ( @@ -157,8 +160,12 @@ const commonConstraintInfoHelper = ( const firstArg = callExp.arguments?.[0] const isArr = firstArg.type === 'ArrayExpression' if (!isArr && firstArg.type !== 'ObjectExpression') return [] + const pipeExpressionIndex = pathToNode.findIndex( + ([_, nodeName]) => nodeName === 'PipeExpression' + ) + const pathToBase = pathToNode.slice(0, pipeExpressionIndex + 2) const pathToArrayExpression: PathToNode = [ - ...pathToNode, + ...pathToBase, ['arguments', 'CallExpression'], [0, 'index'], isArr @@ -270,45 +277,6 @@ const horzVertConstraintInfoHelper = ( ] } -function arrayRawValuesHelper(a: Array<[Literal, LineInputsType]>): RawValues { - return a.map( - ([literal, argType], index): ArrayItemInput => ({ - 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 => ({ - 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'] { return (callExp: CallExpression) => { if (callExp.type !== 'CallExpression') @@ -322,14 +290,9 @@ function getTag(index = 2): SketchLineHelper['getTag'] { } export const lineTo: SketchLineHelper = { - add: ({ - node, - pathToNode, - to, - createCallback, - replaceExisting, - referencedSegment, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const to = segmentInput.to const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -349,15 +312,23 @@ export const lineTo: SketchLineHelper = { createPipeSubstitution(), ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - if (replaceExisting && createCallback) { - const { callExp, valueUsedInTransform } = createCallback( - newVals, - arrayRawValuesHelper([ - [createLiteral(roundOff(to[0], 2)), 'xAbsolute'], - [createLiteral(roundOff(to[1], 2)), 'yAbsolute'], - ]), - referencedSegment - ) + if (replaceExistingCallback) { + const result = replaceExistingCallback([ + { + type: 'arrayItem', + index: 0, + argType: 'xAbsolute', + expr: createLiteral(roundOff(to[0], 2)), + }, + { + 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 return { modifiedAst: _node, @@ -372,7 +343,9 @@ export const lineTo: SketchLineHelper = { 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -407,13 +380,12 @@ export const line: SketchLineHelper = { node, previousProgramMemory, pathToNode, - to, - from, - replaceExisting, - referencedSegment, - createCallback, + segmentInput, + replaceExistingCallback, spliceBetween, }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -433,7 +405,11 @@ export const line: SketchLineHelper = { const newXVal = createLiteral(roundOff(to[0] - from[0], 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', [ createArrayExpression([newXVal, newYVal]), 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 { callExp, valueUsedInTransform } = createCallback( - [newXVal, newYVal], - arrayRawValuesHelper([ - [createLiteral(roundOff(to[0] - from[0], 2)), 'xRelative'], - [createLiteral(roundOff(to[1] - from[1], 2)), 'yRelative'], - ]), - referencedSegment - ) + const result = replaceExistingCallback([ + { + type: 'arrayItem', + index: 0, + argType: 'xRelative', + expr: createLiteral(roundOff(to[0] - from[0], 2)), + }, + { + 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 return { modifiedAst: _node, @@ -496,7 +480,9 @@ export const line: SketchLineHelper = { 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -530,7 +516,9 @@ export const line: 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 getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -539,12 +527,17 @@ export const xLineTo: SketchLineHelper = { const newVal = createLiteral(roundOff(to[0], 2)) - if (replaceExisting && createCallback) { + if (replaceExistingCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [newVal, newVal], - singleRawValueHelper(newVal, 'xAbsolute') - ) + const result = replaceExistingCallback([ + { + type: 'singleValue', + argType: 'xAbsolute', + expr: createLiteral(roundOff(to[0], 2)), + }, + ]) + if (err(result)) return result + const { callExp, valueUsedInTransform } = result pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -562,7 +555,9 @@ export const xLineTo: SketchLineHelper = { 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -591,7 +586,9 @@ export const xLineTo: 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 getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -600,12 +597,17 @@ export const yLineTo: SketchLineHelper = { const newVal = createLiteral(roundOff(to[1], 2)) - if (replaceExisting && createCallback) { + if (replaceExistingCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [newVal, newVal], - singleRawValueHelper(newVal, 'yAbsolute') - ) + const result = replaceExistingCallback([ + { + type: 'singleValue', + argType: 'yAbsolute', + expr: newVal, + }, + ]) + if (err(result)) return result + const { callExp, valueUsedInTransform } = result pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -623,7 +625,9 @@ export const yLineTo: SketchLineHelper = { 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -652,7 +656,9 @@ export const yLineTo: 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 getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -660,14 +666,18 @@ export const xLine: SketchLineHelper = { const { node: pipe } = _node1 const newVal = createLiteral(roundOff(to[0] - from[0], 2)) - const firstArg = newVal - if (replaceExisting && createCallback) { + if (replaceExistingCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [firstArg, firstArg], - singleRawValueHelper(firstArg, 'xRelative') - ) + const result = replaceExistingCallback([ + { + type: 'singleValue', + argType: 'xRelative', + expr: newVal, + }, + ]) + if (err(result)) return result + const { callExp, valueUsedInTransform } = result pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -677,13 +687,15 @@ export const xLine: SketchLineHelper = { } const newLine = createCallExpression('xLine', [ - firstArg, + newVal, createPipeSubstitution(), ]) pipe.body = [...pipe.body, newLine] 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -712,19 +724,26 @@ export const xLine: 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 getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') if (err(_node1)) return _node1 const { node: pipe } = _node1 const newVal = createLiteral(roundOff(to[1] - from[1], 2)) - if (replaceExisting && createCallback) { + if (replaceExistingCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [newVal, newVal], - singleRawValueHelper(newVal, 'yRelative') - ) + const result = replaceExistingCallback([ + { + type: 'singleValue', + argType: 'yRelative', + expr: newVal, + }, + ]) + if (err(result)) return result + const { callExp, valueUsedInTransform } = result pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -740,7 +759,9 @@ export const yLine: SketchLineHelper = { pipe.body = [...pipe.body, newLine] 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -769,14 +790,9 @@ export const yLine: SketchLineHelper = { } export const tangentialArcTo: SketchLineHelper = { - add: ({ - node, - pathToNode, - to, - createCallback, - replaceExisting, - referencedSegment, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -793,16 +809,24 @@ export const tangentialArcTo: SketchLineHelper = { const toX = createLiteral(roundOff(to[0], 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 { callExp, valueUsedInTransform } = createCallback( - [toX, toY], - arrayRawValuesHelper([ - [createLiteral(roundOff(to[0], 2)), 'xAbsolute'], - [createLiteral(roundOff(to[1], 2)), 'yAbsolute'], - ]), - referencedSegment - ) + const result = replaceExistingCallback([ + { + type: 'arrayItem', + index: 0, + argType: 'xRelative', + expr: toX, + }, + { + type: 'arrayItem', + index: 1, + argType: 'yAbsolute', + expr: toY, + }, + ]) + if (err(result)) return result + const { callExp, valueUsedInTransform } = result pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -832,7 +856,9 @@ export const tangentialArcTo: SketchLineHelper = { 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -900,15 +926,9 @@ export const tangentialArcTo: SketchLineHelper = { }, } export const angledLine: SketchLineHelper = { - add: ({ - node, - pathToNode, - to, - from, - createCallback, - replaceExisting, - referencedSegment, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -922,16 +942,26 @@ export const angledLine: SketchLineHelper = { createPipeSubstitution(), ]) - if (replaceExisting && createCallback) { + if (replaceExistingCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [newAngleVal, newLengthVal], - arrOrObjectRawValuesHelper([ - [newAngleVal, 'angle', 'angle'], - [newLengthVal, 'length', 'length'], - ]), - referencedSegment - ) + const result = replaceExistingCallback([ + { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + 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 return { modifiedAst: _node, @@ -946,7 +976,9 @@ export const angledLine: SketchLineHelper = { 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -988,11 +1020,11 @@ export const angledLineOfXLength: SketchLineHelper = { node, previousProgramMemory, pathToNode, - to, - from, - createCallback, - replaceExisting, + segmentInput, + replaceExistingCallback, }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1019,20 +1051,34 @@ export const angledLineOfXLength: SketchLineHelper = { } const angle = createLiteral(roundOff(getAngle(from, to), 0)) const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1) - const newLine = createCallback - ? createCallback( - [angle, xLength], - arrOrObjectRawValuesHelper([ - [angle, 'angle', 'angle'], - [xLength, 'xRelative', 'length'], - ]) - ).callExp - : createCallExpression('angledLineOfXLength', [ - createArrayExpression([angle, xLength]), - createPipeSubstitution(), - ]) + let newLine: Expr + if (replaceExistingCallback) { + const result = replaceExistingCallback([ + { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + expr: angle, + }, + { + 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) - if (replaceExisting) { + if (replaceExistingCallback) { pipe.body[callIndex] = newLine } else { pipe.body = [...pipe.body, newLine] @@ -1042,7 +1088,9 @@ export const angledLineOfXLength: SketchLineHelper = { 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -1088,11 +1136,11 @@ export const angledLineOfYLength: SketchLineHelper = { node, previousProgramMemory, pathToNode, - to, - from, - createCallback, - replaceExisting, + segmentInput, + replaceExistingCallback, }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1117,20 +1165,34 @@ export const angledLineOfYLength: SketchLineHelper = { const angle = createLiteral(roundOff(getAngle(from, to), 0)) const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1) - const newLine = createCallback - ? createCallback( - [angle, yLength], - arrOrObjectRawValuesHelper([ - [angle, 'angle', 'angle'], - [yLength, 'yRelative', 'length'], - ]) - ).callExp - : createCallExpression('angledLineOfYLength', [ - createArrayExpression([angle, yLength]), - createPipeSubstitution(), - ]) + let newLine: Expr + if (replaceExistingCallback) { + const result = replaceExistingCallback([ + { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + expr: angle, + }, + { + 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) - if (replaceExisting) { + if (replaceExistingCallback) { pipe.body[callIndex] = newLine } else { pipe.body = [...pipe.body, newLine] @@ -1140,7 +1202,9 @@ export const angledLineOfYLength: SketchLineHelper = { 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -1182,15 +1246,9 @@ export const angledLineOfYLength: SketchLineHelper = { } export const angledLineToX: SketchLineHelper = { - add: ({ - node, - pathToNode, - to, - from, - createCallback, - replaceExisting, - referencedSegment, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1202,15 +1260,25 @@ export const angledLineToX: SketchLineHelper = { const { node: pipe } = nodeMeta const angle = createLiteral(roundOff(getAngle(from, to), 0)) const xArg = createLiteral(roundOff(to[0], 2)) - if (replaceExisting && createCallback) { - const { callExp, valueUsedInTransform } = createCallback( - [angle, xArg], - arrOrObjectRawValuesHelper([ - [angle, 'angle', 'angle'], - [xArg, 'xAbsolute', 'to'], - ]), - referencedSegment - ) + if (replaceExistingCallback) { + const result = replaceExistingCallback([ + { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + 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) pipe.body[callIndex] = callExp return { @@ -1230,7 +1298,9 @@ export const angledLineToX: SketchLineHelper = { 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -1270,15 +1340,9 @@ export const angledLineToX: SketchLineHelper = { } export const angledLineToY: SketchLineHelper = { - add: ({ - node, - pathToNode, - to, - from, - createCallback, - replaceExisting, - referencedSegment, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1292,15 +1356,25 @@ export const angledLineToY: SketchLineHelper = { const angle = createLiteral(roundOff(getAngle(from, to), 0)) const yArg = createLiteral(roundOff(to[1], 2)) - if (replaceExisting && createCallback) { - const { callExp, valueUsedInTransform } = createCallback( - [angle, yArg], - arrOrObjectRawValuesHelper([ - [angle, 'angle', 'angle'], - [yArg, 'yAbsolute', 'to'], - ]), - referencedSegment - ) + if (replaceExistingCallback) { + const result = replaceExistingCallback([ + { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + 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) pipe.body[callIndex] = callExp return { @@ -1320,7 +1394,9 @@ export const angledLineToY: SketchLineHelper = { 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -1363,12 +1439,12 @@ export const angledLineThatIntersects: SketchLineHelper = { add: ({ node, pathToNode, - to, - from, - createCallback, - replaceExisting, + segmentInput, + replaceExistingCallback, referencedSegment, }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1395,24 +1471,23 @@ export const angledLineThatIntersects: SketchLineHelper = { ) ) - if (replaceExisting && createCallback) { - const { callExp, valueUsedInTransform } = createCallback( - [angle, offset], - [ - { - type: 'objectProperty', - key: 'angle', - value: angle, - argType: 'angle', - }, - { - type: 'objectProperty', - key: 'offset', - value: offset, - argType: 'intersectionOffset', - }, - ] - ) + if (replaceExistingCallback) { + const result = replaceExistingCallback([ + { + type: 'objectProperty', + key: 'angle', + argType: 'angle', + expr: angle, + }, + { + type: 'objectProperty', + key: 'offset', + argType: 'intersectionOffset', + expr: offset, + }, + ]) + if (err(result)) return result + const { callExp, valueUsedInTransform } = result const { index: callIndex } = splitPathAtPipeExpression(pathToNode) pipe.body[callIndex] = callExp return { @@ -1423,7 +1498,9 @@ export const angledLineThatIntersects: SketchLineHelper = { } 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 nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -1559,8 +1636,10 @@ export const angledLineThatIntersects: SketchLineHelper = { export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({ node, pathToNode, - to, + input, }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) { @@ -1615,8 +1694,7 @@ export function changeSketchArguments( node: Program, programMemory: ProgramMemory, sourceRange: SourceRange, - args: [number, number], - from: [number, number] + input: SegmentInputs ): { modifiedAst: Program; pathToNode: PathToNode } | Error { const _node = { ...node } const thePath = getNodePathFromSourceRange(_node, sourceRange) @@ -1635,8 +1713,7 @@ export function changeSketchArguments( node: _node, previousProgramMemory: programMemory, pathToNode: shallowPath, - to: args, - from, + input, }) } @@ -1684,8 +1761,7 @@ export function compareVec2Epsilon2( interface CreateLineFnCallArgs { node: Program programMemory: ProgramMemory - to: [number, number] - from: [number, number] + input: SegmentInputs fnName: ToolTip pathToNode: PathToNode spliceBetween?: boolean @@ -1694,10 +1770,9 @@ interface CreateLineFnCallArgs { export function addNewSketchLn({ node: _node, programMemory: previousProgramMemory, - to, fnName, pathToNode, - from, + input: segmentInput, spliceBetween = false, }: CreateLineFnCallArgs): | { @@ -1721,9 +1796,7 @@ export function addNewSketchLn({ node, previousProgramMemory, pathToNode, - to, - from, - replaceExisting: false, + segmentInput, spliceBetween, }) } @@ -1784,18 +1857,16 @@ export function replaceSketchLine({ programMemory, pathToNode: _pathToNode, fnName, - to, - from, - createCallback, + segmentInput, + replaceExistingCallback, referencedSegment, }: { node: Program programMemory: ProgramMemory pathToNode: PathToNode fnName: ToolTip - to: [number, number] - from: [number, number] - createCallback: TransformCallback + segmentInput: SegmentInputs + replaceExistingCallback: (rawArgs: RawArgs) => CreatedSketchExprResult | Error referencedSegment?: Path }): | { @@ -1805,7 +1876,7 @@ export function replaceSketchLine({ } | Error { 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 } @@ -1815,10 +1886,8 @@ export function replaceSketchLine({ previousProgramMemory: programMemory, pathToNode: _pathToNode, referencedSegment, - to, - from, - replaceExisting: true, - createCallback, + segmentInput, + replaceExistingCallback, }) if (err(addRetVal)) return addRetVal @@ -1826,13 +1895,16 @@ export function replaceSketchLine({ return { modifiedAst, valueUsedInTransform, pathToNode } } -export function addTagForSketchOnFace(a: AddTagInfo, expressionName: string) { +export function addTagForSketchOnFace( + tagInfo: AddTagInfo, + expressionName: string +) { if (expressionName === 'close') { - return addTag(1)(a) + return addTag(1)(tagInfo) } if (expressionName in sketchLineHelperMap) { const { addTag } = sketchLineHelperMap[expressionName] - return addTag(a) + return addTag(tagInfo) } return new Error(`"${expressionName}" is not a sketch line helper`) } diff --git a/src/lang/std/sketchConstraints.ts b/src/lang/std/sketchConstraints.ts index 62fc2e2f06..c26e188e1f 100644 --- a/src/lang/std/sketchConstraints.ts +++ b/src/lang/std/sketchConstraints.ts @@ -43,6 +43,17 @@ export function getSketchSegmentFromSourceRange( index: number } | 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 if ( startSourceRange && @@ -51,17 +62,7 @@ export function getSketchSegmentFromSourceRange( sketchGroup.start ) return { segment: { ...sketchGroup.start, type: 'Base' }, index: -1 } - - 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, - } + return new Error('could not find matching segment') } export function isSketchVariablesLinked( diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index bfe12a6ff2..49d573562a 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1,4 +1,11 @@ -import { TransformCallback, VarValues } from './stdTypes' +import { + CreatedSketchExprResult, + CreateStdLibSketchCallExpr, + InputArg, + InputArgs, + SimplifiedArgDetails, + TransformInfo, +} from './stdTypes' import { ToolTip, toolTips } from 'lang/langHelpers' import { Selections, Selection } from 'lib/selections' import { cleanErrs, err } from 'lib/trap' @@ -11,6 +18,7 @@ import { PathToNode, ProgramMemory, sketchGroupFromKclValue, + Literal, } from '../wasm' import { getNodeFromPath, @@ -67,13 +75,21 @@ export type ConstraintType = | 'yAbs' | 'setAngleBetween' +const REF_NUM_ERR = new Error('Referenced segment does not have a to value') +function isUndef(val: any): val is undefined { + return typeof val === 'undefined' +} +function isNum(val: any): val is number { + return typeof val === 'number' +} + function createCallWrapper( - a: ToolTip, + tooltip: ToolTip, val: [Expr, Expr] | Expr, tag?: Expr, valueUsedInTransform?: number -): ReturnType { - const args = [createFirstArg(a, val), createPipeSubstitution()] +): CreatedSketchExprResult { + const args = [createFirstArg(tooltip, val), createPipeSubstitution()] if (tag) { args.push(tag) } @@ -88,7 +104,7 @@ function createCallWrapper( } return { - callExp: createCallExpression(a, argsWOutErr), + callExp: createCallExpression(tooltip, argsWOutErr), valueUsedInTransform, } } @@ -107,7 +123,7 @@ function createStdlibCallExpression( val: Expr, tag?: Expr, valueUsedInTransform?: number -): ReturnType { +): CreatedSketchExprResult { const args = [val, createPipeSubstitution()] if (tag) { args.push(tag) @@ -132,7 +148,7 @@ function intersectCallWrapper({ intersectTag: Expr tag?: Expr valueUsedInTransform?: number -}): ReturnType { +}): CreatedSketchExprResult { const firstArg: any = { angle: angleVal, offset: offsetVal, @@ -151,18 +167,6 @@ function intersectCallWrapper({ } } -export type TransformInfo = { - tooltip: ToolTip - createNode: (a: { - varValues: VarValues - varValA: Expr // x / angle - varValB: Expr // y / length or x y for angledLineOfXlength etc - referenceSegName: string - tag?: Expr - forceValueUsedInTransform?: Expr - }) => TransformCallback -} - type TransformMap = { [key in ToolTip]?: { [key in LineInputsType | 'free']?: { @@ -172,48 +176,58 @@ type TransformMap = { } const xyLineSetLength = - ( - xOrY: 'xLine' | 'yLine', - referenceSeg = false - ): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform }) => - (args) => { + (xOrY: 'xLine' | 'yLine', referenceSeg = false): CreateStdLibSketchCallExpr => + ({ referenceSegName, tag, forceValueUsedInTransform, rawArgs: args }) => { const segRef = createSegLen(referenceSegName) const lineVal = forceValueUsedInTransform ? forceValueUsedInTransform : referenceSeg ? segRef - : args[0] - return createCallWrapper(xOrY, lineVal, tag, getArgLiteralVal(args[0])) + : args[0].expr + const literalARg = getArgLiteralVal(args[0].expr) + if (err(literalARg)) return literalARg + return createCallWrapper(xOrY, lineVal, tag, literalARg) } +type AngLenNone = 'ang' | 'len' | 'none' const basicAngledLineCreateNode = ( - referenceSeg: 'ang' | 'len' | 'none' = 'none', - valToForce: 'ang' | 'len' | 'none' = 'none', - varValToUse: 'ang' | 'len' | 'none' = 'none' - ): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform, varValA, varValB }) => - (args, _, path) => { + referenceSeg: AngLenNone = 'none', + valToForce: AngLenNone = 'none', + varValToUse: AngLenNone = 'none' + ): CreateStdLibSketchCallExpr => + ({ + referenceSegName, + tag, + forceValueUsedInTransform, + inputs, + rawArgs: args, + referencedSegment: path, + }) => { const refAng = path ? getAngle(path?.from, path?.to) : 0 + if (!isNum(args[0].expr.value)) return REF_NUM_ERR const nonForcedAng = varValToUse === 'ang' - ? varValA + ? inputs[0].expr : referenceSeg === 'ang' ? getClosesAngleDirection( - args[0], + args[0].expr.value, refAng, - createSegAngle(referenceSegName) as BinaryPart + createSegAngle(referenceSegName) ) - : args[0] + : args[0].expr const nonForcedLen = varValToUse === 'len' - ? varValB + ? inputs[1].expr : referenceSeg === 'len' ? createSegLen(referenceSegName) - : args[1] + : args[1].expr const shouldForceAng = valToForce === 'ang' && forceValueUsedInTransform const shouldForceLen = valToForce === 'len' && forceValueUsedInTransform + const literalArg = getArgLiteralVal( + valToForce === 'ang' ? args[0].expr : args[1].expr + ) + if (err(literalArg)) return literalArg return createCallWrapper( 'angledLine', [ @@ -221,17 +235,19 @@ const basicAngledLineCreateNode = shouldForceLen ? forceValueUsedInTransform : nonForcedLen, ], tag, - getArgLiteralVal(valToForce === 'ang' ? args[0] : args[1]) + literalArg ) } -const angledLineAngleCreateNode: TransformInfo['createNode'] = - ({ referenceSegName, varValA, tag }) => - () => - createCallWrapper( - 'angledLine', - [varValA, createSegLen(referenceSegName)], - tag - ) +const angledLineAngleCreateNode: CreateStdLibSketchCallExpr = ({ + referenceSegName, + inputs, + tag, +}) => + createCallWrapper( + 'angledLine', + [inputs[0].expr, createSegLen(referenceSegName)], + tag + ) const getMinAndSegLenVals = ( referenceSegName: string, @@ -260,13 +276,10 @@ const getMinAndSegAngVals = ( return [minVal, legAngle] } -const getSignedLeg = (arg: Expr, legLenVal: BinaryPart) => - arg.type === 'Literal' && Number(arg.value) < 0 - ? createUnaryExpression(legLenVal) - : legLenVal +const getSignedLeg = (arg: Literal, legLenVal: BinaryPart) => + Number(arg.value) < 0 ? createUnaryExpression(legLenVal) : legLenVal -const getLegAng = (arg: Expr, legAngleVal: BinaryPart) => { - const ang = (arg.type === 'Literal' && Number(arg.value)) || 0 +const getLegAng = (ang: number, legAngleVal: BinaryPart) => { const normalisedAngle = ((ang % 360) + 360) % 360 // between 0 and 360 const truncatedTo90 = Math.floor(normalisedAngle / 90) * 90 const binExp = createBinaryExpressionWithUnary([ @@ -276,18 +289,16 @@ const getLegAng = (arg: Expr, legAngleVal: BinaryPart) => { return truncatedTo90 === 0 ? legAngleVal : binExp } -const getAngleLengthSign = (arg: Expr, legAngleVal: BinaryPart) => { - const ang = (arg.type === 'Literal' && Number(arg.value)) || 0 +const getAngleLengthSign = (ang: number, legAngleVal: BinaryPart) => { const normalisedAngle = ((ang % 180) + 180) % 180 // between 0 and 180 return normalisedAngle > 90 ? createUnaryExpression(legAngleVal) : legAngleVal } function getClosesAngleDirection( - arg: Expr, + currentAng: number, refAngle: number, angleVal: BinaryPart ) { - const currentAng = (arg.type === 'Literal' && Number(arg.value)) || 0 const angDiff = Math.abs(currentAng - refAngle) const normalisedAngle = ((angDiff % 360) + 360) % 360 // between 0 and 180 return normalisedAngle > 90 @@ -296,55 +307,58 @@ function getClosesAngleDirection( } const setHorzVertDistanceCreateNode = - ( - xOrY: 'x' | 'y', - index = xOrY === 'x' ? 0 : 1 - ): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform }) => { - return (args, _, referencedSegment) => { - const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[index]) - (referencedSegment?.to?.[index] || 0), - 2 - ) - let finalValue: Expr = createBinaryExpressionWithUnary([ - createSegEnd(referenceSegName, !index), - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform), - ]) - if (isValueZero(forceValueUsedInTransform)) { - finalValue = createSegEnd(referenceSegName, !index) - } - return createCallWrapper( - 'lineTo', - !index ? [finalValue, args[1]] : [args[0], finalValue], - tag, - valueUsedInTransform - ) + (xOrY: 'x' | 'y', index = xOrY === 'x' ? 0 : 1): CreateStdLibSketchCallExpr => + ({ + referenceSegName, + tag, + forceValueUsedInTransform, + rawArgs: args, + referencedSegment, + }) => { + const refNum = referencedSegment?.to?.[index] + const literalArg = getArgLiteralVal(args?.[index].expr) + if (err(literalArg)) return literalArg + if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR + + const valueUsedInTransform = roundOff(literalArg - refNum, 2) + let finalValue: Expr = createBinaryExpressionWithUnary([ + createSegEnd(referenceSegName, !index), + forceValueUsedInTransform || createLiteral(valueUsedInTransform), + ]) + if (isValueZero(forceValueUsedInTransform)) { + finalValue = createSegEnd(referenceSegName, !index) } + return createCallWrapper( + 'lineTo', + !index ? [finalValue, args[1].expr] : [args[0].expr, finalValue], + tag, + valueUsedInTransform + ) } const setHorzVertDistanceForAngleLineCreateNode = - ( - xOrY: 'x' | 'y', - index = xOrY === 'x' ? 0 : 1 - ): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform, varValA }) => { - return (args, _, referencedSegment) => { - const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[1]) - (referencedSegment?.to?.[index] || 0), - 2 - ) - const binExp = createBinaryExpressionWithUnary([ - createSegEnd(referenceSegName, !index), - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform), - ]) - return createCallWrapper( - xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', - [varValA, binExp], - tag, - valueUsedInTransform - ) - } + (xOrY: 'x' | 'y', index = xOrY === 'x' ? 0 : 1): CreateStdLibSketchCallExpr => + ({ + referenceSegName, + tag, + forceValueUsedInTransform, + inputs, + rawArgs: args, + referencedSegment, + }) => { + const refNum = referencedSegment?.to?.[index] + const literalArg = getArgLiteralVal(args?.[1].expr) + if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR + const valueUsedInTransform = roundOff(literalArg - refNum, 2) + const binExp = createBinaryExpressionWithUnary([ + createSegEnd(referenceSegName, !index), + forceValueUsedInTransform || createLiteral(valueUsedInTransform), + ]) + return createCallWrapper( + xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', + [inputs[0].expr, binExp], + tag, + valueUsedInTransform + ) } const setAbsDistanceCreateNode = @@ -352,13 +366,12 @@ const setAbsDistanceCreateNode = xOrY: 'x' | 'y', isXOrYLine = false, index = xOrY === 'x' ? 0 : 1 - ): TransformInfo['createNode'] => - ({ tag, forceValueUsedInTransform }) => - (args) => { - const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[index]), 2) - const val = - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform) + ): CreateStdLibSketchCallExpr => + ({ tag, forceValueUsedInTransform, rawArgs: args }) => { + const literalArg = getArgLiteralVal(args?.[index].expr) + if (err(literalArg)) return REF_NUM_ERR + const valueUsedInTransform = roundOff(literalArg, 2) + const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform) if (isXOrYLine) { return createCallWrapper( xOrY === 'x' ? 'xLineTo' : 'yLineTo', @@ -369,166 +382,172 @@ const setAbsDistanceCreateNode = } return createCallWrapper( 'lineTo', - !index ? [val, args[1]] : [args[0], val], + !index ? [val, args[1].expr] : [args[0].expr, val], tag, valueUsedInTransform ) } const setAbsDistanceForAngleLineCreateNode = - (xOrY: 'x' | 'y'): TransformInfo['createNode'] => - ({ tag, forceValueUsedInTransform, varValA }) => { - return (args) => { - const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[1]), 2) - const val = - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform) - return createCallWrapper( - xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', - [varValA, val], - tag, - valueUsedInTransform - ) - } + (xOrY: 'x' | 'y'): CreateStdLibSketchCallExpr => + ({ tag, forceValueUsedInTransform, inputs, rawArgs: args }) => { + const literalArg = getArgLiteralVal(args?.[1].expr) + if (err(literalArg)) return REF_NUM_ERR + const valueUsedInTransform = roundOff(literalArg, 2) + const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform) + return createCallWrapper( + xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', + [inputs[0].expr, val], + tag, + valueUsedInTransform + ) } const setHorVertDistanceForXYLines = - (xOrY: 'x' | 'y'): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform }) => { - return (args, _, referencedSegment) => { - const index = xOrY === 'x' ? 0 : 1 - const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[index]) - (referencedSegment?.to?.[index] || 0), - 2 - ) - const makeBinExp = createBinaryExpressionWithUnary([ - createSegEnd(referenceSegName, xOrY === 'x'), - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform), - ]) - return createCallWrapper( - xOrY === 'x' ? 'xLineTo' : 'yLineTo', - makeBinExp, - tag, - valueUsedInTransform - ) - } + (xOrY: 'x' | 'y'): CreateStdLibSketchCallExpr => + ({ + referenceSegName, + tag, + forceValueUsedInTransform, + rawArgs: args, + referencedSegment, + }) => { + const index = xOrY === 'x' ? 0 : 1 + const refNum = referencedSegment?.to?.[index] + const literalArg = getArgLiteralVal(args?.[index].expr) + if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR + const valueUsedInTransform = roundOff(literalArg - refNum, 2) + const makeBinExp = createBinaryExpressionWithUnary([ + createSegEnd(referenceSegName, xOrY === 'x'), + forceValueUsedInTransform || createLiteral(valueUsedInTransform), + ]) + return createCallWrapper( + xOrY === 'x' ? 'xLineTo' : 'yLineTo', + makeBinExp, + tag, + valueUsedInTransform + ) } const setHorzVertDistanceConstraintLineCreateNode = - (isX: boolean): TransformInfo['createNode'] => - ({ referenceSegName, tag, varValA, varValB }) => { - const varVal = (isX ? varValB : varValA) as BinaryPart + (isX: boolean): CreateStdLibSketchCallExpr => + ({ referenceSegName, tag, inputs, rawArgs: args, referencedSegment }) => { + let varVal = isX ? inputs[1].expr : inputs[0].expr + varVal = isExprBinaryPart(varVal) ? varVal : createLiteral(0) const varValBinExp = createBinaryExpressionWithUnary([ createLastSeg(!isX), varVal, ]) - return (args, _, referencedSegment) => { - const makeBinExp = (index: 0 | 1) => { - const arg = getArgLiteralVal(args?.[index]) - return createBinaryExpressionWithUnary([ - createSegEnd(referenceSegName, isX), - createLiteral( - roundOff(arg - (referencedSegment?.to?.[index] || 0), 2) - ), - ]) - } - return createCallWrapper( - 'lineTo', - isX ? [makeBinExp(0), varValBinExp] : [varValBinExp, makeBinExp(1)], - tag - ) + const makeBinExp = (index: 0 | 1) => { + const arg = getArgLiteralVal(args?.[index].expr) + const refNum = referencedSegment?.to?.[index] + if (err(arg) || !isNum(refNum)) return REF_NUM_ERR + return createBinaryExpressionWithUnary([ + createSegEnd(referenceSegName, isX), + createLiteral(roundOff(arg - refNum, 2)), + ]) } - } - -const setAngledIntersectLineForLines: TransformInfo['createNode'] = - ({ referenceSegName, tag, forceValueUsedInTransform }) => - (args) => { - const valueUsedInTransform = roundOff( - args[1].type === 'Literal' ? Number(args[1].value) : 0, - 2 + const binExpr = isX ? makeBinExp(0) : makeBinExp(1) + if (err(binExpr)) return new Error('Invalid value for distance') + return createCallWrapper( + 'lineTo', + isX ? [binExpr, varValBinExp] : [varValBinExp, binExpr], + tag ) - const angle = args[0].type === 'Literal' ? Number(args[0].value) : 0 - const varNamMap: { [key: number]: string } = { - 0: 'ZERO', - 90: 'QUARTER_TURN', - 180: 'HALF_TURN', - 270: 'THREE_QUARTER_TURN', - } - const angleVal = [0, 90, 180, 270].includes(angle) - ? createIdentifier(varNamMap[angle]) - : createLiteral(angle) - return intersectCallWrapper({ - fnName: 'angledLineThatIntersects', - angleVal, - offsetVal: - forceValueUsedInTransform || createLiteral(valueUsedInTransform), - intersectTag: createIdentifier(referenceSegName), - tag, - valueUsedInTransform, - }) } -const setAngledIntersectForAngledLines: TransformInfo['createNode'] = - ({ referenceSegName, tag, forceValueUsedInTransform, varValA }) => - (args) => { - const valueUsedInTransform = roundOff( - args[1].type === 'Literal' ? Number(args[1].value) : 0, - 2 - ) - // const angle = args[0].type === 'Literal' ? Number(args[0].value) : 0 - return intersectCallWrapper({ - fnName: 'angledLineThatIntersects', - angleVal: varValA, - offsetVal: - forceValueUsedInTransform || createLiteral(valueUsedInTransform), - intersectTag: createIdentifier(referenceSegName), - tag, - valueUsedInTransform, - }) +const setAngledIntersectLineForLines: CreateStdLibSketchCallExpr = ({ + referenceSegName, + tag, + forceValueUsedInTransform, + rawArgs: args, +}) => { + const val = args[1].expr.value, + angle = args[0].expr.value + if (!isNum(val) || !isNum(angle)) return REF_NUM_ERR + const valueUsedInTransform = roundOff(val, 2) + const varNamMap: { [key: number]: string } = { + 0: 'ZERO', + 90: 'QUARTER_TURN', + 180: 'HALF_TURN', + 270: 'THREE_QUARTER_TURN', } + const angleVal = [0, 90, 180, 270].includes(angle) + ? createIdentifier(varNamMap[angle]) + : createLiteral(angle) + return intersectCallWrapper({ + fnName: 'angledLineThatIntersects', + angleVal, + offsetVal: forceValueUsedInTransform || createLiteral(valueUsedInTransform), + intersectTag: createIdentifier(referenceSegName), + tag, + valueUsedInTransform, + }) +} + +const setAngledIntersectForAngledLines: CreateStdLibSketchCallExpr = ({ + referenceSegName, + tag, + forceValueUsedInTransform, + inputs, + rawArgs: args, +}) => { + const val = args[1].expr.value + if (!isNum(val)) return REF_NUM_ERR + const valueUsedInTransform = roundOff(val, 2) + return intersectCallWrapper({ + fnName: 'angledLineThatIntersects', + angleVal: inputs[0].expr, + offsetVal: forceValueUsedInTransform || createLiteral(valueUsedInTransform), + intersectTag: createIdentifier(referenceSegName), + tag, + valueUsedInTransform, + }) +} const setAngleBetweenCreateNode = - (tranformToType: 'none' | 'xAbs' | 'yAbs'): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform, varValA, varValB }) => { - return (args, _, referencedSegment) => { - const refAngle = referencedSegment - ? getAngle(referencedSegment?.from, referencedSegment?.to) - : 0 - let valueUsedInTransform = roundOff( - normaliseAngle( - (args[0].type === 'Literal' ? Number(args[0].value) : 0) - refAngle - ) - ) - let firstHalfValue = createSegAngle(referenceSegName) as BinaryPart - if (Math.abs(valueUsedInTransform) > 90) { - firstHalfValue = createBinaryExpression([ - firstHalfValue, - '+', - createIdentifier('HALF_TURN'), - ]) - valueUsedInTransform = normaliseAngle(valueUsedInTransform - 180) - } - const binExp = createBinaryExpressionWithUnary([ + (tranformToType: 'none' | 'xAbs' | 'yAbs'): CreateStdLibSketchCallExpr => + ({ + referenceSegName, + tag, + forceValueUsedInTransform, + inputs, + rawArgs: args, + referencedSegment, + }) => { + const refAngle = referencedSegment + ? getAngle(referencedSegment?.from, referencedSegment?.to) + : 0 + const val = args[0].expr.value + if (!isNum(val)) return REF_NUM_ERR + let valueUsedInTransform = roundOff(normaliseAngle(val - refAngle)) + let firstHalfValue = createSegAngle(referenceSegName) + if (Math.abs(valueUsedInTransform) > 90) { + firstHalfValue = createBinaryExpression([ firstHalfValue, - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform), + '+', + createIdentifier('HALF_TURN'), ]) - return createCallWrapper( - tranformToType === 'none' - ? 'angledLine' - : tranformToType === 'xAbs' - ? 'angledLineToX' - : 'angledLineToY', - tranformToType === 'none' - ? [binExp, args[1]] - : tranformToType === 'xAbs' - ? [binExp, varValA] - : [binExp, varValB], - tag, - valueUsedInTransform - ) + valueUsedInTransform = normaliseAngle(valueUsedInTransform - 180) } + const binExp = createBinaryExpressionWithUnary([ + firstHalfValue, + forceValueUsedInTransform || createLiteral(valueUsedInTransform), + ]) + return createCallWrapper( + tranformToType === 'none' + ? 'angledLine' + : tranformToType === 'xAbs' + ? 'angledLineToX' + : 'angledLineToY', + tranformToType === 'none' + ? [binExp, args[1].expr] + : tranformToType === 'xAbs' + ? [binExp, inputs[0].expr] + : [binExp, inputs[1].expr], + tag, + valueUsedInTransform + ) } const transformMap: TransformMap = { @@ -536,25 +555,22 @@ const transformMap: TransformMap = { xRelative: { equalLength: { tooltip: 'line', - createNode: ({ referenceSegName, varValA, tag }) => { + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { const [minVal, legLenVal] = getMinAndSegLenVals( referenceSegName, - varValA + inputs[0].expr + ) + return createCallWrapper( + 'line', + [minVal, getSignedLeg(args[1].expr, legLenVal)], + tag ) - return (args) => - createCallWrapper( - 'line', - [minVal, getSignedLeg(args[1], legLenVal)], - tag - ) }, }, horizontal: { tooltip: 'xLine', - createNode: - ({ varValA, tag }) => - () => - createCallWrapper('xLine', varValA, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('xLine', inputs[0].expr, tag), }, setVertDistance: { tooltip: 'lineTo', @@ -564,25 +580,22 @@ const transformMap: TransformMap = { yRelative: { equalLength: { tooltip: 'line', - createNode: ({ referenceSegName, varValB, tag }) => { + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { const [minVal, legLenVal] = getMinAndSegLenVals( referenceSegName, - varValB + inputs[1].expr + ) + return createCallWrapper( + 'line', + [getSignedLeg(args[0].expr, legLenVal), minVal], + tag ) - return (args) => - createCallWrapper( - 'line', - [getSignedLeg(args[0], legLenVal), minVal], - tag - ) }, }, vertical: { tooltip: 'yLine', - createNode: - ({ varValB, tag }) => - () => - createCallWrapper('yLine', varValB, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('yLine', inputs[1].expr, tag), }, setHorzDistance: { tooltip: 'lineTo', @@ -596,17 +609,17 @@ const transformMap: TransformMap = { }, horizontal: { tooltip: 'xLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper('xLine', args[0], tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper('xLine', args[0].expr, tag), }, vertical: { tooltip: 'yLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper('yLine', args[1], tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper( + 'yLine', + getInputOfType(args, 'yRelative').expr, + tag + ), }, setHorzDistance: { tooltip: 'lineTo', @@ -654,46 +667,46 @@ const transformMap: TransformMap = { }, horizontal: { tooltip: 'xLineTo', - createNode: - ({ tag }) => - (args) => - createCallWrapper('xLineTo', args[0], tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper('xLineTo', args[0].expr, tag), }, vertical: { tooltip: 'yLineTo', - createNode: - ({ tag }) => - (args) => - createCallWrapper('yLineTo', args[1], tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper( + 'yLineTo', + getInputOfType(args, 'yAbsolute').expr, + tag + ), }, }, xAbsolute: { equalLength: { tooltip: 'angledLineToX', - createNode: - ({ referenceSegName, varValA, tag }) => - (args) => { - const angleToMatchLengthXCall = createCallExpression( - 'angleToMatchLengthX', - [ - createIdentifier(referenceSegName), - varValA, - createPipeSubstitution(), - ] - ) - return createCallWrapper( - 'angledLineToX', - [getAngleLengthSign(args[0], angleToMatchLengthXCall), varValA], - tag - ) - }, + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { + const angleToMatchLengthXCall = createCallExpression( + 'angleToMatchLengthX', + [ + createIdentifier(referenceSegName), + inputs[0].expr, + createPipeSubstitution(), + ] + ) + if (!isNum(args[0].expr.value)) return REF_NUM_ERR + return createCallWrapper( + 'angledLineToX', + [ + getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall), + inputs[0].expr, + ], + tag + ) + }, }, horizontal: { tooltip: 'xLineTo', - createNode: - ({ varValA, tag }) => - () => - createCallWrapper('xLineTo', varValA, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('xLineTo', inputs[0].expr, tag), }, setAngleBetween: { tooltip: 'angledLineToX', @@ -703,43 +716,48 @@ const transformMap: TransformMap = { yAbsolute: { equalLength: { tooltip: 'angledLineToY', - createNode: - ({ referenceSegName, varValB, tag }) => - (args) => { - const angleToMatchLengthYCall = createCallExpression( - 'angleToMatchLengthY', - [ - createIdentifier(referenceSegName), - varValB, - createPipeSubstitution(), - ] - ) - return createCallWrapper( - 'angledLineToY', - [getAngleLengthSign(args[0], angleToMatchLengthYCall), varValB], - tag - ) - }, + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { + const angleToMatchLengthYCall = createCallExpression( + 'angleToMatchLengthY', + [ + createIdentifier(referenceSegName), + inputs[1].expr, + createPipeSubstitution(), + ] + ) + if (!isNum(args[0].expr.value)) return REF_NUM_ERR + return createCallWrapper( + 'angledLineToY', + [ + getAngleLengthSign(args[0].expr.value, angleToMatchLengthYCall), + inputs[1].expr, + ], + tag + ) + }, }, vertical: { tooltip: 'yLineTo', - createNode: - ({ varValB, tag }) => - () => - createCallWrapper('yLineTo', varValB, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('yLineTo', inputs[1].expr, tag), }, setAngle: { tooltip: 'angledLineToY', - createNode: - ({ varValB, tag, forceValueUsedInTransform }) => - (args) => { - return createCallWrapper( - 'angledLineToY', - [forceValueUsedInTransform || args[0], varValB], - tag, - getArgLiteralVal(args[0]) - ) - }, + createNode: ({ + inputs, + tag, + forceValueUsedInTransform, + rawArgs: args, + }) => { + const val = getArgLiteralVal(args[0].expr) + if (err(val)) return val + return createCallWrapper( + 'angledLineToY', + [forceValueUsedInTransform || args[0].expr, inputs[1].expr], + tag, + val + ) + }, }, setAngleBetween: { tooltip: 'angledLineToY', @@ -751,14 +769,12 @@ const transformMap: TransformMap = { angle: { equalLength: { tooltip: 'angledLine', - createNode: - ({ referenceSegName, varValA, tag }) => - () => - createCallWrapper( - 'angledLine', - [varValA, createSegLen(referenceSegName)], - tag - ), + createNode: ({ referenceSegName, inputs, tag }) => + createCallWrapper( + 'angledLine', + [inputs[0].expr, createSegLen(referenceSegName)], + tag + ), }, setLength: { tooltip: 'angledLine', @@ -796,43 +812,43 @@ const transformMap: TransformMap = { }, vertical: { tooltip: 'yLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper('yLine', args[1], tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper( + 'yLine', + getInputOfType(args, 'yRelative').expr, + tag + ), }, horizontal: { tooltip: 'xLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper('xLine', args[0], tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper('xLine', args[0].expr, tag), }, }, length: { vertical: { tooltip: 'yLine', - createNode: - ({ varValB, tag }) => - ([arg0]) => { - const val = - arg0.type === 'Literal' && Number(arg0.value) < 0 - ? createUnaryExpression(varValB as BinaryPart) - : varValB - return createCallWrapper('yLine', val, tag) - }, + createNode: ({ inputs, tag, rawArgs: args }) => { + const expr = inputs[1].expr + if (Number(args[0].expr.value) >= 0) + return createCallWrapper('yLine', expr, tag) + if (isExprBinaryPart(expr)) + return createCallWrapper('yLine', createUnaryExpression(expr), tag) + // TODO maybe should return error here instead + return createCallWrapper('yLine', expr, tag) + }, }, horizontal: { tooltip: 'xLine', - createNode: - ({ varValB, tag }) => - ([arg0]) => { - const val = - arg0.type === 'Literal' && Number(arg0.value) < 0 - ? createUnaryExpression(varValB as BinaryPart) - : varValB - return createCallWrapper('xLine', val, tag) - }, + createNode: ({ inputs, tag, rawArgs: args }) => { + const expr = inputs[1].expr + if (Number(args[0].expr.value) >= 0) + return createCallWrapper('xLine', expr, tag) + if (isExprBinaryPart(expr)) + return createCallWrapper('xLine', createUnaryExpression(expr), tag) + // TODO maybe should return error here instead + return createCallWrapper('xLine', expr, tag) + }, }, setAngle: { tooltip: 'angledLine', @@ -852,10 +868,8 @@ const transformMap: TransformMap = { }, horizontal: { tooltip: 'xLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper('xLine', args[0], tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper('xLine', args[0].expr, tag), }, }, angle: { @@ -867,30 +881,30 @@ const transformMap: TransformMap = { xRelative: { equalLength: { tooltip: 'angledLineOfXLength', - createNode: ({ referenceSegName, varValB, tag }) => { + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { const [minVal, legAngle] = getMinAndSegAngVals( referenceSegName, - varValB + getInputOfType(inputs, 'xRelative').expr + ) + if (!isNum(args[0].expr.value)) return REF_NUM_ERR + return createCallWrapper( + 'angledLineOfXLength', + [getLegAng(args[0].expr.value, legAngle), minVal], + tag ) - return (args) => - createCallWrapper( - 'angledLineOfXLength', - [getLegAng(args[0], legAngle), minVal], - tag - ) }, }, horizontal: { tooltip: 'xLine', - createNode: - ({ varValB, tag }) => - ([arg0]) => { - const val = - arg0.type === 'Literal' && Number(arg0.value) < 0 - ? createUnaryExpression(varValB as BinaryPart) - : varValB - return createCallWrapper('xLine', val, tag) - }, + createNode: ({ inputs, tag, rawArgs: args }) => { + const expr = inputs[1].expr + if (Number(args[0].expr.value) >= 0) + return createCallWrapper('xLine', expr, tag) + if (isExprBinaryPart(expr)) + return createCallWrapper('xLine', createUnaryExpression(expr), tag) + // TODO maybe should return error here instead + return createCallWrapper('xLine', expr, tag) + }, }, }, }, @@ -902,10 +916,12 @@ const transformMap: TransformMap = { }, vertical: { tooltip: 'yLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper('yLine', args[1], tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper( + 'yLine', + getInputOfType(args, 'yRelative').expr, + tag + ), }, }, angle: { @@ -917,31 +933,31 @@ const transformMap: TransformMap = { yRelative: { equalLength: { tooltip: 'angledLineOfYLength', - createNode: ({ referenceSegName, varValB, tag }) => { + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { const [minVal, legAngle] = getMinAndSegAngVals( referenceSegName, - varValB, + inputs[1].expr, 'legAngY' ) - return (args) => - createCallWrapper( - 'angledLineOfXLength', - [getLegAng(args[0], legAngle), minVal], - tag - ) + if (!isNum(args[0].expr.value)) return REF_NUM_ERR + return createCallWrapper( + 'angledLineOfXLength', + [getLegAng(args[0].expr.value, legAngle), minVal], + tag + ) }, }, vertical: { tooltip: 'yLine', - createNode: - ({ varValB, tag }) => - ([arg0]) => { - const val = - arg0.type === 'Literal' && Number(arg0.value) < 0 - ? createUnaryExpression(varValB as BinaryPart) - : varValB - return createCallWrapper('yLine', val, tag) - }, + createNode: ({ inputs, tag, rawArgs: args }) => { + const expr = inputs[1].expr + if (Number(args[0].expr.value) >= 0) + return createCallWrapper('yLine', expr, tag) + if (isExprBinaryPart(expr)) + return createCallWrapper('yLine', createUnaryExpression(expr), tag) + // TODO maybe should return error here instead + return createCallWrapper('yLine', expr, tag) + }, }, }, }, @@ -953,10 +969,8 @@ const transformMap: TransformMap = { }, horizontal: { tooltip: 'xLineTo', - createNode: - ({ tag }) => - (args) => - createCallWrapper('xLineTo', args[0], tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper('xLineTo', args[0].expr, tag), }, }, angle: { @@ -968,30 +982,30 @@ const transformMap: TransformMap = { xAbsolute: { equalLength: { tooltip: 'angledLineToX', - createNode: - ({ referenceSegName, varValB, tag }) => - (args) => { - const angleToMatchLengthXCall = createCallExpression( - 'angleToMatchLengthX', - [ - createIdentifier(referenceSegName), - varValB, - createPipeSubstitution(), - ] - ) - return createCallWrapper( - 'angledLineToX', - [getAngleLengthSign(args[0], angleToMatchLengthXCall), varValB], - tag - ) - }, + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { + const angleToMatchLengthXCall = createCallExpression( + 'angleToMatchLengthX', + [ + createIdentifier(referenceSegName), + inputs[1].expr, + createPipeSubstitution(), + ] + ) + if (!isNum(args[0].expr.value)) return REF_NUM_ERR + return createCallWrapper( + 'angledLineToX', + [ + getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall), + inputs[1].expr, + ], + tag + ) + }, }, horizontal: { tooltip: 'xLineTo', - createNode: - ({ varValB, tag }) => - ([arg0]) => - createCallWrapper('xLineTo', varValB, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('xLineTo', inputs[1].expr, tag), }, }, }, @@ -1003,10 +1017,12 @@ const transformMap: TransformMap = { }, vertical: { tooltip: 'yLineTo', - createNode: - ({ tag }) => - (args) => - createCallWrapper('yLineTo', args[1], tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper( + 'yLineTo', + getInputOfType(args, 'yAbsolute').expr, + tag + ), }, }, angle: { @@ -1018,30 +1034,30 @@ const transformMap: TransformMap = { yAbsolute: { equalLength: { tooltip: 'angledLineToY', - createNode: - ({ referenceSegName, varValB, tag }) => - (args) => { - const angleToMatchLengthXCall = createCallExpression( - 'angleToMatchLengthY', - [ - createIdentifier(referenceSegName), - varValB, - createPipeSubstitution(), - ] - ) - return createCallWrapper( - 'angledLineToY', - [getAngleLengthSign(args[0], angleToMatchLengthXCall), varValB], - tag - ) - }, + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { + const angleToMatchLengthXCall = createCallExpression( + 'angleToMatchLengthY', + [ + createIdentifier(referenceSegName), + inputs[1].expr, + createPipeSubstitution(), + ] + ) + if (!isNum(args[0].expr.value)) return REF_NUM_ERR + return createCallWrapper( + 'angledLineToY', + [ + getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall), + inputs[1].expr, + ], + tag + ) + }, }, vertical: { tooltip: 'yLineTo', - createNode: - ({ varValB, tag }) => - () => - createCallWrapper('yLineTo', varValB, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('yLineTo', inputs[1].expr, tag), }, }, }, @@ -1049,14 +1065,21 @@ const transformMap: TransformMap = { free: { equalLength: { tooltip: 'xLine', - createNode: - ({ referenceSegName, tag }) => - (arg) => { - const argVal = getArgLiteralVal(arg[0]) - const segLen = createSegLen(referenceSegName) as BinaryPart - const val = argVal > 0 ? segLen : createUnaryExpression(segLen) - return createCallWrapper('xLine', val, tag, argVal) - }, + createNode: ({ referenceSegName, tag, rawArgs: args }) => { + const argVal = getArgLiteralVal(args[0].expr) + if (err(argVal)) return argVal + const segLen = createSegLen(referenceSegName) + if (argVal > 0) return createCallWrapper('xLine', segLen, tag, argVal) + if (isExprBinaryPart(segLen)) + return createCallWrapper( + 'xLine', + createUnaryExpression(segLen), + tag, + argVal + ) + // should probably return error here instead + return createCallWrapper('xLine', segLen, tag, argVal) + }, }, setHorzDistance: { tooltip: 'xLineTo', @@ -1080,14 +1103,13 @@ const transformMap: TransformMap = { free: { equalLength: { tooltip: 'yLine', - createNode: - ({ referenceSegName, tag }) => - (arg) => { - const argVal = getArgLiteralVal(arg[0]) - let segLen = createSegLen(referenceSegName) as BinaryPart - if (argVal < 0) segLen = createUnaryExpression(segLen) - return createCallWrapper('yLine', segLen, tag, argVal) - }, + createNode: ({ referenceSegName, tag, rawArgs: args }) => { + const argVal = getArgLiteralVal(args[0].expr) + if (err(argVal)) return argVal + let segLen = createSegLen(referenceSegName) + if (argVal < 0) segLen = createUnaryExpression(segLen) + return createCallWrapper('yLine', segLen, tag, argVal) + }, }, setLength: { tooltip: 'yLine', @@ -1111,10 +1133,8 @@ const transformMap: TransformMap = { free: { equalLength: { tooltip: 'xLine', - createNode: - ({ referenceSegName, tag }) => - () => - createCallWrapper('xLine', createSegLen(referenceSegName), tag), + createNode: ({ referenceSegName, tag }) => + createCallWrapper('xLine', createSegLen(referenceSegName), tag), }, setLength: { tooltip: 'xLine', @@ -1126,10 +1146,8 @@ const transformMap: TransformMap = { free: { equalLength: { tooltip: 'yLine', - createNode: - ({ referenceSegName, tag }) => - () => - createCallWrapper('yLine', createSegLen(referenceSegName), tag), + createNode: ({ referenceSegName, tag }) => + createCallWrapper('yLine', createSegLen(referenceSegName), tag), }, setLength: { tooltip: 'yLine', @@ -1164,23 +1182,21 @@ export function getRemoveConstraintsTransform( const transformInfo: TransformInfo = { tooltip: 'line', // tooltip: name, - createNode: - ({ tag, referenceSegName }) => - (args) => { - return createCallWrapper('line', args, tag) - // The following commented changes values to hardcode, but keeps the line type the same, maybe that's useful? - - // if (name === 'angledLineThatIntersects') { - // return intersectCallWrapper({ - // fnName: name, - // angleVal: args[0], - // offsetVal: args[1], - // intersectTag: createIdentifier(referenceSegName), - // tag, - // }) - // } - // return createCallWrapper(name, args, tag) - }, + createNode: ({ tag, referenceSegName, rawArgs: args }) => { + return createCallWrapper('line', [args[0].expr, args[1].expr], tag) + // The following commented changes values to hardcode, but keeps the line type the same, maybe that's useful? + + // if (name === 'angledLineThatIntersects') { + // return intersectCallWrapper({ + // fnName: name, + // angleVal: args[0].expr, + // offsetVal: args[1].expr, + // intersectTag: createIdentifier(referenceSegName), + // tag, + // }) + // } + // return createCallWrapper(name, args, tag) + }, } // check if the function is locked down and so can't be transformed @@ -1217,13 +1233,11 @@ export function getRemoveConstraintsTransform( export function removeSingleConstraint({ pathToCallExp, - arrayIndex, - objectProperty, + inputDetails, ast, }: { pathToCallExp: PathToNode - arrayIndex?: number - objectProperty?: string + inputDetails: SimplifiedArgDetails ast: Program }): TransformInfo | false { const callExp = getNodeFromPath( @@ -1242,68 +1256,111 @@ export function removeSingleConstraint({ const transform: TransformInfo = { tooltip: callExp.node.callee.name as any, - createNode: - ({ tag, referenceSegName, varValues }) => - (_, rawValues) => { - if (objectProperty) { - const expression: Parameters[0] = {} - varValues.forEach((varValue) => { - if ( - varValue.type !== 'objectProperty' && - varValue.type !== 'arrayOrObjItem' + createNode: ({ tag, inputs, rawArgs }) => { + // inputs is the current values for each of the inputs + // rawValues is the raw 'literal' values equivalent to the inputs + // inputDetails is the one variable we're removing the constraint from + // So we should update the call expression to use the inputs, except for + // the inputDetails, input where we should use the rawValue(s) + + if (inputDetails.type === 'arrayItem') { + const values = inputs.map((arg) => { + if ( + !( + (arg.type === 'arrayItem' || arg.type === 'arrayOrObjItem') && + arg.index === inputDetails.index ) - return - const literal = rawValues.find( - (rawValue) => - (rawValue.type === 'objectProperty' || - rawValue.type === 'arrayOrObjItem') && - rawValue.key === objectProperty - )?.value - const value = - (varValue.key === objectProperty && literal) || varValue.value - expression[varValue.key] = value - }) - const objExp = createObjectExpression(expression) - return createStdlibCallExpression( - callExp.node.callee.name as any, - objExp, - tag ) - } - if (typeof arrayIndex === 'number') { - const values = varValues.map((varValue) => { - if ( - (varValue.type === 'arrayItem' || - varValue.type === 'arrayOrObjItem') && - varValue.index === arrayIndex - ) { - const literal = rawValues.find( - (rawValue) => - (rawValue.type === 'arrayItem' || - rawValue.type === 'arrayOrObjItem') && - rawValue.index === arrayIndex - )?.value - return ( - (varValue.index === arrayIndex && literal) || varValue.value - ) - } - return varValue.value - }) - return createStdlibCallExpression( - callExp.node.callee.name as any, - createArrayExpression(values), - tag + return arg.expr + const literal = rawArgs.find( + (rawValue) => + (rawValue.type === 'arrayItem' || + rawValue.type === 'arrayOrObjItem') && + rawValue.index === inputDetails.index + )?.expr + return (arg.index === inputDetails.index && literal) || arg.expr + }) + return createStdlibCallExpression( + callExp.node.callee.name as any, + createArrayExpression(values), + tag + ) + } + if ( + inputDetails.type === 'arrayInObject' || + inputDetails.type === 'objectProperty' + ) { + const arrayDetailsNameBetterLater: { + [key: string]: Parameters[0] + } = {} + const otherThing: Parameters[0] = {} + inputs.forEach((arg) => { + if ( + arg.type !== 'objectProperty' && + arg.type !== 'arrayOrObjItem' && + arg.type !== 'arrayInObject' ) - } - - // if (typeof arrayIndex !== 'number' || !objectProperty) must be single value input xLine, yLineTo etc - - return createCallWrapper( + return + const rawLiteralArrayInObject = rawArgs.find( + (rawValue) => + rawValue.type === 'arrayInObject' && + rawValue.key === inputDetails.key && + rawValue.index === (arg.type === 'arrayInObject' ? arg.index : -1) + ) + const rawLiteralObjProp = rawArgs.find( + (rawValue) => + (rawValue.type === 'objectProperty' || + rawValue.type === 'arrayOrObjItem' || + rawValue.type === 'arrayInObject') && + rawValue.key === inputDetails.key + ) + if ( + inputDetails.type === 'arrayInObject' && + rawLiteralArrayInObject?.type === 'arrayInObject' && + rawLiteralArrayInObject?.index === inputDetails.index && + rawLiteralArrayInObject?.key === inputDetails.key + ) { + if (!arrayDetailsNameBetterLater[arg.key]) + arrayDetailsNameBetterLater[arg.key] = [] + arrayDetailsNameBetterLater[inputDetails.key][inputDetails.index] = + rawLiteralArrayInObject.expr + } else if ( + inputDetails.type === 'objectProperty' && + (rawLiteralObjProp?.type === 'objectProperty' || + rawLiteralObjProp?.type === 'arrayOrObjItem') && + rawLiteralObjProp?.key === inputDetails.key && + arg.key === inputDetails.key + ) { + otherThing[inputDetails.key] = rawLiteralObjProp.expr + } else if (arg.type === 'arrayInObject') { + if (!arrayDetailsNameBetterLater[arg.key]) + arrayDetailsNameBetterLater[arg.key] = [] + arrayDetailsNameBetterLater[arg.key][arg.index] = arg.expr + } else if (arg.type === 'objectProperty') { + otherThing[arg.key] = arg.expr + } + }) + const createObjParam: Parameters[0] = {} + Object.entries(arrayDetailsNameBetterLater).forEach(([key, value]) => { + createObjParam[key] = createArrayExpression(value) + }) + const objExp = createObjectExpression({ + ...createObjParam, + ...otherThing, + }) + return createStdlibCallExpression( callExp.node.callee.name as any, - rawValues[0].value, + objExp, tag ) - }, + } + + return createCallWrapper( + callExp.node.callee.name as any, + rawArgs[0].expr, + tag + ) + }, } return transform } @@ -1485,7 +1542,7 @@ export function transformSecondarySketchLinesTagFirst({ transformInfos: TransformInfo[] programMemory: ProgramMemory forceSegName?: string - forceValueUsedInTransform?: Expr + forceValueUsedInTransform?: BinaryPart }): | { modifiedAst: Program @@ -1556,7 +1613,7 @@ export function transformAstSketchLines({ transformInfos: TransformInfo[] programMemory: ProgramMemory referenceSegName: string - forceValueUsedInTransform?: Expr + forceValueUsedInTransform?: BinaryPart referencedSegmentRange?: Selection['range'] }): | { @@ -1583,8 +1640,6 @@ export function transformAstSketchLines({ const varDec = getNode('VariableDeclarator') if (err(varDec)) return varDec - const firstArg = getFirstArg(callExp.node) - if (err(firstArg)) return firstArg const callBackTag = callExp.node.arguments[2] const _referencedSegmentNameVal = callExp.node.arguments[0]?.type === 'ObjectExpression' && @@ -1597,10 +1652,7 @@ export function transformAstSketchLines({ _referencedSegmentNameVal.type === 'Identifier' && String(_referencedSegmentNameVal.name)) || '' - const { val } = firstArg - const [varValA, varValB] = Array.isArray(val) ? val : [val, val] - - const varValues: VarValues = [] + const inputs: InputArgs = [] getConstraintInfo(callExp.node, '', _pathToNode).forEach((a) => { if ( @@ -1614,24 +1666,32 @@ export function transformAstSketchLines({ if (err(nodeMeta)) return if (a?.argPosition?.type === 'arrayItem') { - varValues.push({ + inputs.push({ type: 'arrayItem', index: a.argPosition.index, - value: nodeMeta.node, + expr: nodeMeta.node, argType: a.type, }) } else if (a?.argPosition?.type === 'objectProperty') { - varValues.push({ + inputs.push({ type: 'objectProperty', key: a.argPosition.key, - value: nodeMeta.node, + expr: nodeMeta.node, argType: a.type, }) } else if (a?.argPosition?.type === 'singleValue') { - varValues.push({ + inputs.push({ type: 'singleValue', argType: a.type, - value: nodeMeta.node, + expr: nodeMeta.node, + }) + } else if (a?.argPosition?.type === 'arrayInObject') { + inputs.push({ + type: 'arrayInObject', + key: a.argPosition.key, + index: a.argPosition.index, + expr: nodeMeta.node, + argType: a.type, }) } }) @@ -1675,16 +1735,20 @@ export function transformAstSketchLines({ pathToNode: _pathToNode, referencedSegment, fnName: transformTo || (callExp.node.callee.name as ToolTip), - to, - from, - createCallback: callBack({ - referenceSegName: _referencedSegmentName, - varValues, - varValA, - varValB, - tag: callBackTag, - forceValueUsedInTransform, - }), + segmentInput: { + type: 'straight-segment', + to, + from, + }, + replaceExistingCallback: (rawArgs) => + callBack({ + referenceSegName: _referencedSegmentName, + inputs, + tag: callBackTag, + rawArgs, + forceValueUsedInTransform, + referencedSegment, + }), }) if (err(replacedSketchLine)) return replacedSketchLine @@ -1717,11 +1781,11 @@ export function transformAstSketchLines({ } } -function createSegLen(referenceSegName: string): Expr { +function createSegLen(referenceSegName: string): BinaryPart { return createCallExpression('segLen', [createIdentifier(referenceSegName)]) } -function createSegAngle(referenceSegName: string): Expr { +function createSegAngle(referenceSegName: string): BinaryPart { return createCallExpression('segAng', [createIdentifier(referenceSegName)]) } @@ -1737,8 +1801,9 @@ function createLastSeg(isX: boolean): CallExpression { ]) } -function getArgLiteralVal(arg: Expr): number { - return arg?.type === 'Literal' ? Number(arg.value) : 0 +function getArgLiteralVal(arg: Literal): number | Error { + if (!isNum(arg.value)) return REF_NUM_ERR + return arg.value } export type ConstraintLevel = 'free' | 'partial' | 'full' @@ -1809,3 +1874,20 @@ export function isNotLiteralArrayOrStatic( (val.type === 'UnaryExpression' && val.argument.type !== 'Literal') ) } + +export function isExprBinaryPart(expr: Expr): expr is BinaryPart { + if ( + expr.type === 'Literal' || + expr.type === 'Identifier' || + expr.type === 'BinaryExpression' || + expr.type === 'CallExpression' || + expr.type === 'UnaryExpression' || + expr.type === 'MemberExpression' + ) + return true + return false +} + +function getInputOfType(a: InputArgs, b: LineInputsType): InputArg { + return a.find(({ argType }) => argType === b) || a[0] +} diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index 166d4cbeba..c432ba33c0 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -8,23 +8,10 @@ import { PathToNode, CallExpression, Literal, + BinaryPart, } from '../wasm' -import { EngineCommandManager } from './engineConnection' 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 { node: Program // TODO #896: Remove ProgramMemory from this interface @@ -37,76 +24,162 @@ export interface AddTagInfo { pathToNode: PathToNode } -interface addCall extends ModifyAstBase { - to: [number, number] +/** Inputs for all straight segments, to and from are absolute values, as this gives a + * 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] + 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 - 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 } interface updateArgs extends ModifyAstBase { - from: [number, number] - to: [number, number] + input: SegmentInputs } -export type VarValueKeys = 'angle' | 'offset' | 'length' | 'to' | 'intersectTag' +export type InputArgKeys = 'angle' | 'offset' | 'length' | 'to' | 'intersectTag' export interface SingleValueInput { type: 'singleValue' argType: LineInputsType - value: T + expr: T } export interface ArrayItemInput { type: 'arrayItem' index: 0 | 1 argType: LineInputsType - value: T + expr: T } export interface ObjectPropertyInput { type: 'objectProperty' - key: VarValueKeys + key: InputArgKeys argType: LineInputsType - value: T + expr: T } -export interface ArrayOrObjItemInput { +interface ArrayOrObjItemInput { type: 'arrayOrObjItem' - key: VarValueKeys + key: InputArgKeys index: 0 | 1 argType: LineInputsType - value: T + expr: T +} + +interface ArrayInObject { + type: 'arrayInObject' + key: InputArgKeys + argType: LineInputsType + index: 0 | 1 + expr: T } -export type _VarValue = +type _InputArg = | SingleValueInput | ArrayItemInput | ObjectPropertyInput | ArrayOrObjItemInput + | ArrayInObject -export type VarValue = _VarValue -export type RawValue = _VarValue +/** + * {@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 -export type VarValues = Array -export type RawValues = Array +/** + * {@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 -type SimplifiedVarValue = - | { - type: 'singleValue' - } - | { type: 'arrayItem'; index: 0 | 1 } - | { type: 'objectProperty'; key: VarValueKeys } +export type InputArgs = Array -export type TransformCallback = ( - args: [Expr, Expr], - literalValues: RawValues, - referencedSegment?: Path -) => { +/** + * 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 + */ +export type RawArgs = Array + +/** + * 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, 'expr' | 'argType'> + | Omit, 'expr' | 'argType'> + | Omit, 'expr' | 'argType'> + | Omit, 'expr' | 'argType'> + | Omit, '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 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 { stdLibFnName: ToolTip type: LineInputsType | 'vertical' | 'horizontal' | 'tangentialWithPrevious' @@ -115,7 +188,7 @@ export interface ConstrainInfo { pathToNode: PathToNode value: string calculatedValue?: any - argPosition?: SimplifiedVarValue + argPosition?: SimplifiedArgDetails } export interface SketchLineHelper { diff --git a/src/lib/selections.ts b/src/lib/selections.ts index 8bb0c381ca..92abcbc81f 100644 --- a/src/lib/selections.ts +++ b/src/lib/selections.ts @@ -20,10 +20,8 @@ import { } from 'lang/queryAst' import { CommandArgument } from './commandTypes' import { - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, getParentGroup, - PROFILE_START, + SEGMENT_BODIES_PLUS_PROFILE_START, } from 'clientSideScene/sceneEntities' import { Mesh, Object3D, Object3DEventMap } from 'three' import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra' @@ -162,11 +160,7 @@ export async function getEventForSelectWithPoint({ export function getEventForSegmentSelection( obj: Object3D ): ModelingMachineEvent | null { - const group = getParentGroup(obj, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - PROFILE_START, - ]) + const group = getParentGroup(obj, SEGMENT_BODIES_PLUS_PROFILE_START) const axisGroup = getParentGroup(obj, [AXIS_GROUP]) if (!group && !axisGroup) return null if (axisGroup?.userData.type === AXIS_GROUP) { @@ -303,12 +297,7 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) { const updated = kclManager.ast Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => { - if ( - ![STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, PROFILE_START].includes( - segmentGroup?.name - ) - ) - return + if (!SEGMENT_BODIES_PLUS_PROFILE_START.includes(segmentGroup?.name)) return const nodeMeta = getNodeFromPath( updated, segmentGroup.userData.pathToNode,