Files
modeling-app/src/components/Toolbar/setAngleLength.tsx
2025-07-03 09:47:05 -04:00

188 lines
5.7 KiB
TypeScript

import { removeDoubleNegatives } from '@src/components/AvailableVarsHelpers'
import {
SetAngleLengthModal,
createSetAngleLengthModal,
} from '@src/components/SetAngleLengthModal'
import { angleLengthInfo } from '@src/components/Toolbar/angleLengthInfo'
import {
createBinaryExpressionWithUnary,
createLocalName,
createName,
createVariableDeclaration,
} from '@src/lang/create'
import type { PathToNodeMap } from '@src/lang/std/sketchcombos'
import {
isExprBinaryPart,
transformAstSketchLines,
} from '@src/lang/std/sketchcombos'
import type { Expr, Program } from '@src/lang/wasm'
import type { KclCommandValue } from '@src/lib/commandTypes'
import type { Selections } from '@src/lib/selections'
import { kclManager } from '@src/lib/singletons'
import { err } from '@src/lib/trap'
import { normaliseAngle } from '@src/lib/utils'
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
export async function applyConstraintLength({
length,
selectionRanges,
}: {
length: KclCommandValue
selectionRanges: Selections
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
exprInsertIndex: number
}> {
const ast = kclManager.ast
const angleLength = angleLengthInfo({ selectionRanges })
if (err(angleLength)) return Promise.reject(angleLength)
const { transforms } = angleLength
let distanceExpression: Expr = length.valueAst
/**
* To be "constrained", the value must be a binary expression, a named value, or a function call.
* If it has a variable name, we need to insert a variable declaration at the correct index.
*/
if (
'variableName' in length &&
length.variableName &&
length.insertIndex !== undefined
) {
const newBody = [...ast.body]
newBody.splice(length.insertIndex, 0, length.variableDeclarationAst)
ast.body = newBody
distanceExpression = createLocalName(length.variableName)
}
if (!isExprBinaryPart(distanceExpression)) {
return Promise.reject('Invalid valueNode, is not a BinaryPart')
}
const retval = transformAstSketchLines({
ast,
selectionRanges,
transformInfos: transforms,
memVars: kclManager.variables,
referenceSegName: '',
forceValueUsedInTransform: distanceExpression,
})
if (err(retval)) return Promise.reject(retval)
const { modifiedAst: _modifiedAst, pathToNodeMap } = retval
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
exprInsertIndex:
'variableName' in length &&
length.variableName &&
length.insertIndex !== undefined
? length.insertIndex
: -1,
}
}
export async function applyConstraintAngleLength({
selectionRanges,
angleOrLength = 'setLength',
}: {
selectionRanges: Selections
angleOrLength?: 'setLength' | 'setAngle'
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
exprInsertIndex: number
}> {
const angleLength = angleLengthInfo({ selectionRanges, angleOrLength })
if (err(angleLength)) return Promise.reject(angleLength)
const { transforms } = angleLength
const sketched = transformAstSketchLines({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos: transforms,
memVars: kclManager.variables,
referenceSegName: '',
})
if (err(sketched)) return Promise.reject(sketched)
const { valueUsedInTransform } = sketched
const isReferencingYAxis =
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'y-axis'
const isReferencingYAxisAngle =
isReferencingYAxis && angleOrLength === 'setAngle'
const isReferencingXAxis =
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'x-axis'
const isReferencingXAxisAngle =
isReferencingXAxis && angleOrLength === 'setAngle'
let forceVal = valueUsedInTransform || 0
let calcIdentifier = createName(['turns'], 'ZERO')
if (isReferencingYAxisAngle) {
calcIdentifier = createName(
['turns'],
forceVal < 0 ? 'THREE_QUARTER_TURN' : 'QUARTER_TURN'
)
forceVal = normaliseAngle(forceVal + (forceVal < 0 ? 90 : -90))
} else if (isReferencingXAxisAngle) {
calcIdentifier = createName(
['turns'],
Math.abs(forceVal) > 90 ? 'HALF_TURN' : 'ZERO'
)
forceVal =
Math.abs(forceVal) > 90 ? normaliseAngle(forceVal - 180) : forceVal
}
const { valueNode, variableName, newVariableInsertIndex, sign } =
await getModalInfo({
value: forceVal,
valueName: angleOrLength === 'setAngle' ? 'angle' : 'length',
shouldCreateVariable: true,
selectionRanges,
})
if (!isExprBinaryPart(valueNode))
return Promise.reject('Invalid valueNode, is not a BinaryPart')
let finalValue = removeDoubleNegatives(valueNode, sign, variableName)
if (
isReferencingYAxisAngle ||
(isReferencingXAxisAngle && calcIdentifier.name.name !== 'ZERO')
) {
finalValue = createBinaryExpressionWithUnary([calcIdentifier, finalValue])
}
const retval = transformAstSketchLines({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos: transforms,
memVars: kclManager.variables,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
if (err(retval)) return Promise.reject(retval)
const { modifiedAst: _modifiedAst, pathToNodeMap } = retval
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
Object.values(pathToNodeMap).forEach((pathToNode) => {
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
})
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
exprInsertIndex: variableName ? newVariableInsertIndex : -1,
}
}