initial query and test
This commit is contained in:
@ -18,3 +18,7 @@ export const ARG_INTERIOR_ABSOLUTE = 'interiorAbsolute'
|
||||
export const ARG_AT = 'at'
|
||||
export const ARG_LEG = 'leg'
|
||||
export const ARG_HYPOTENUSE = 'hypotenuse'
|
||||
export const ARG_P1 = 'p1'
|
||||
export const ARG_P2 = 'p2'
|
||||
export const ARG_P3 = 'p3'
|
||||
export const ARG_DIAMETER = 'diameter'
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
createPipeSubstitution,
|
||||
} from '@src/lang/create'
|
||||
import {
|
||||
doesProfileHaveAnyConstrainedDimension,
|
||||
doesSceneHaveExtrudedSketch,
|
||||
doesSceneHaveSweepableSketch,
|
||||
findAllPreviousVariables,
|
||||
@ -35,6 +36,95 @@ beforeAll(async () => {
|
||||
await initPromise
|
||||
})
|
||||
|
||||
describe('doesProfileHaveConstrainDimension', () => {
|
||||
const code = `sketch001 = startSketchOn(YZ)
|
||||
profile001 = startProfile(sketch001, at = [100, 101])
|
||||
|> line(end = [102, 103])
|
||||
|> line(endAbsolute = [104, 105])
|
||||
|> angledLine(angle = 206, length = 106)
|
||||
|> angledLine(angle = -208, lengthX = 107)
|
||||
|> angledLine(angle = 210, lengthY = 108)
|
||||
|> angledLine(angle = 212, endAbsoluteX = 109)
|
||||
|> angledLine(angle = 214, endAbsoluteY = 110)
|
||||
|> arc(interiorAbsolute = [111, 112], endAbsolute = [113, 114])
|
||||
|> tangentialArc(end = [115, -116])
|
||||
|> tangentialArc(endAbsolute = [117, 118])
|
||||
|> tangentialArc(angle = 224, radius = 119)
|
||||
|> tangentialArc(angle = 226, diameter = 120)
|
||||
|
||||
profile002 = startProfile(sketch001, at = [-121, 122])
|
||||
|> angledLine(angle = 130, length = 123, tag = $rectangleSegmentA001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001) - 232, length = 124)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
profile003 = circle(sketch001, center = [-125, -126], radius = 127)
|
||||
profile004 = circleThreePoint(
|
||||
sketch001,
|
||||
p1 = [128, 129],
|
||||
p2 = [130, 131],
|
||||
p3 = [132, 133],
|
||||
)
|
||||
profile005 = circle(sketch001, center = [-134, -135], diameter = 136)`
|
||||
const profileSearchStrings = [
|
||||
{
|
||||
profileSearchString: 'profile001 = startProfile',
|
||||
replaceCases: { start: 100, end: 120 },
|
||||
},
|
||||
{
|
||||
profileSearchString: 'profile002 = startProfile',
|
||||
replaceCases: { start: 121, end: 124 },
|
||||
},
|
||||
{
|
||||
profileSearchString: 'profile003 = circle',
|
||||
replaceCases: { start: 125, end: 127 },
|
||||
},
|
||||
{
|
||||
profileSearchString: 'profile004 = circleThreePoint',
|
||||
replaceCases: { start: 128, end: 133 },
|
||||
},
|
||||
{
|
||||
profileSearchString: 'profile005 = circle',
|
||||
replaceCases: { start: 134, end: 136 },
|
||||
},
|
||||
] as const
|
||||
it('should return false for all profiles (no constrained dimensions detected)', () => {
|
||||
const ast = assertParse(code)
|
||||
|
||||
profileSearchStrings.forEach((profile) => {
|
||||
const profileStart = code.indexOf(profile.profileSearchString)
|
||||
const profilePath = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(profileStart, profileStart)
|
||||
)
|
||||
expect(
|
||||
doesProfileHaveAnyConstrainedDimension(profilePath, ast)
|
||||
).toBeFalsy()
|
||||
})
|
||||
})
|
||||
it('should true false when adding constraints for each Profile all profiles (no constrained dimensions detected)', () => {
|
||||
profileSearchStrings.forEach((profile) => {
|
||||
for (
|
||||
let i = profile.replaceCases.start;
|
||||
i <= profile.replaceCases.end;
|
||||
i++
|
||||
) {
|
||||
const modifiedCode = code.replaceAll(String(i), `${i} + 5`)
|
||||
const ast = assertParse(modifiedCode)
|
||||
const profileStart = modifiedCode.indexOf(profile.profileSearchString)
|
||||
const profilePath = getNodePathFromSourceRange(
|
||||
ast,
|
||||
topLevelRange(profileStart, profileStart)
|
||||
)
|
||||
expect(
|
||||
doesProfileHaveAnyConstrainedDimension(profilePath, ast)
|
||||
).toBeTruthy()
|
||||
}
|
||||
// })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('findAllPreviousVariables', () => {
|
||||
it('should find all previous variables', async () => {
|
||||
const code = `baseThick = 1
|
||||
|
||||
@ -55,6 +55,23 @@ import type { OpKclValue, Operation } from '@rust/kcl-lib/bindings/Operation'
|
||||
import { ARG_INDEX_FIELD, LABELED_ARG_FIELD } from '@src/lang/queryAstConstants'
|
||||
import type { KclCommandValue } from '@src/lib/commandTypes'
|
||||
import type { UnaryExpression } from 'typescript'
|
||||
import {
|
||||
ARG_END_ABSOLUTE,
|
||||
ARG_END,
|
||||
ARG_LENGTH,
|
||||
ARG_CIRCLE_CENTER,
|
||||
ARG_RADIUS,
|
||||
ARG_LENGTH_X,
|
||||
ARG_LENGTH_Y,
|
||||
ARG_END_ABSOLUTE_X,
|
||||
ARG_END_ABSOLUTE_Y,
|
||||
ARG_INTERIOR_ABSOLUTE,
|
||||
ARG_AT,
|
||||
ARG_P1,
|
||||
ARG_P2,
|
||||
ARG_P3,
|
||||
ARG_DIAMETER,
|
||||
} from '@src/lang/constants'
|
||||
|
||||
/**
|
||||
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
|
||||
@ -1290,3 +1307,142 @@ export const getPathNormalisedForTruncatedAst = (
|
||||
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) - minIndex
|
||||
return nodePathWithCorrectedIndexForTruncatedAst
|
||||
}
|
||||
|
||||
export function doesProfileHaveAnyConstrainedDimension(
|
||||
profilePath: PathToNode,
|
||||
ast: Node<Program>
|
||||
): boolean {
|
||||
// Get the profile node from the path
|
||||
const profileNodeResult = getNodeFromPath(ast, profilePath)
|
||||
if (err(profileNodeResult)) return false
|
||||
|
||||
const profileNode = profileNodeResult.node
|
||||
|
||||
// Single value dimension parameters to check (excluding angle as per requirements)
|
||||
const singleValueLengthParams = new Set([
|
||||
ARG_DIAMETER,
|
||||
ARG_RADIUS,
|
||||
ARG_LENGTH,
|
||||
ARG_LENGTH_X,
|
||||
ARG_LENGTH_Y,
|
||||
ARG_END_ABSOLUTE_X,
|
||||
ARG_END_ABSOLUTE_Y,
|
||||
])
|
||||
|
||||
// Tuple value dimension parameters to check
|
||||
const tupleValueParams = new Set([
|
||||
ARG_CIRCLE_CENTER,
|
||||
ARG_P1,
|
||||
ARG_P2,
|
||||
ARG_P3,
|
||||
ARG_AT,
|
||||
ARG_END,
|
||||
ARG_END_ABSOLUTE,
|
||||
ARG_INTERIOR_ABSOLUTE,
|
||||
])
|
||||
|
||||
let hasConstrainedDimension = false
|
||||
|
||||
// Traverse the profile node to find all call expressions and their arguments
|
||||
traverse(profileNode as any, {
|
||||
enter: (node: any) => {
|
||||
if (node.type === 'CallExpressionKw' && node.arguments) {
|
||||
for (const arg of node.arguments) {
|
||||
if (arg.type === 'LabeledArg' && arg.label?.name) {
|
||||
const paramName = arg.label.name
|
||||
|
||||
// Check if this parameter is in our whitelist
|
||||
if (
|
||||
singleValueLengthParams.has(paramName) ||
|
||||
tupleValueParams.has(paramName)
|
||||
) {
|
||||
// Special case: endAbsolute = [profileStartX(%), profileStartY(%)]
|
||||
// This should NOT count as constrained
|
||||
if (
|
||||
paramName === ARG_END_ABSOLUTE &&
|
||||
arg.arg.type === 'ArrayExpression' &&
|
||||
arg.arg.elements.length === 2
|
||||
) {
|
||||
const [first, second] = arg.arg.elements
|
||||
if (
|
||||
first.type === 'CallExpressionKw' &&
|
||||
second.type === 'CallExpressionKw' &&
|
||||
first.callee.type === 'Name' &&
|
||||
second.callee.type === 'Name' &&
|
||||
first.callee.name.name === 'profileStartX' &&
|
||||
second.callee.name.name === 'profileStartY'
|
||||
) {
|
||||
// This is the special case - don't count as constrained
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Special case: angledLine length = -segLen(rectangleSegmentA001)
|
||||
// This should NOT count as constrained
|
||||
if (
|
||||
node.callee?.type === 'Name' &&
|
||||
node.callee.name.name === 'angledLine' &&
|
||||
paramName === ARG_LENGTH
|
||||
) {
|
||||
let callExpr = null
|
||||
|
||||
// Check if it's a direct call expression or unary expression with call expression
|
||||
if (arg.arg.type === 'CallExpressionKw') {
|
||||
callExpr = arg.arg
|
||||
} else if (
|
||||
arg.arg.type === 'UnaryExpression' &&
|
||||
arg.arg.argument?.type === 'CallExpressionKw'
|
||||
) {
|
||||
callExpr = arg.arg.argument
|
||||
}
|
||||
|
||||
if (
|
||||
callExpr &&
|
||||
callExpr.callee?.type === 'Name' &&
|
||||
callExpr.callee.name.name === 'segLen' &&
|
||||
callExpr.unlabeled?.type === 'Name' &&
|
||||
callExpr.unlabeled.name.name.startsWith('rectangleSegmentA')
|
||||
) {
|
||||
// This is the special case - don't count as constrained
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the argument value is non-literal
|
||||
if (!isLiteralValue(arg.arg)) {
|
||||
hasConstrainedDimension = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we found a constrained dimension, we can break out of the outer loop too
|
||||
if (hasConstrainedDimension) {
|
||||
return false // This stops the traversal
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return hasConstrainedDimension
|
||||
}
|
||||
|
||||
// Helper function to check if a node represents a literal value
|
||||
function isLiteralValue(node: any): boolean {
|
||||
if (node.type === 'Literal') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (node.type === 'ArrayExpression') {
|
||||
// Array is literal if all elements are literals
|
||||
return node.elements.every((element: any) => isLiteralValue(element))
|
||||
}
|
||||
|
||||
if (node.type === 'UnaryExpression' && node.operator === '-') {
|
||||
// Negative literal numbers
|
||||
return isLiteralValue(node.argument)
|
||||
}
|
||||
|
||||
// All other node types (Name, CallExpression, BinaryExpression, etc.) are non-literal
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user