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_AT = 'at'
|
||||||
export const ARG_LEG = 'leg'
|
export const ARG_LEG = 'leg'
|
||||||
export const ARG_HYPOTENUSE = 'hypotenuse'
|
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,
|
createPipeSubstitution,
|
||||||
} from '@src/lang/create'
|
} from '@src/lang/create'
|
||||||
import {
|
import {
|
||||||
|
doesProfileHaveAnyConstrainedDimension,
|
||||||
doesSceneHaveExtrudedSketch,
|
doesSceneHaveExtrudedSketch,
|
||||||
doesSceneHaveSweepableSketch,
|
doesSceneHaveSweepableSketch,
|
||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
@ -35,6 +36,95 @@ beforeAll(async () => {
|
|||||||
await initPromise
|
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', () => {
|
describe('findAllPreviousVariables', () => {
|
||||||
it('should find all previous variables', async () => {
|
it('should find all previous variables', async () => {
|
||||||
const code = `baseThick = 1
|
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 { ARG_INDEX_FIELD, LABELED_ARG_FIELD } from '@src/lang/queryAstConstants'
|
||||||
import type { KclCommandValue } from '@src/lib/commandTypes'
|
import type { KclCommandValue } from '@src/lib/commandTypes'
|
||||||
import type { UnaryExpression } from 'typescript'
|
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.
|
* 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
|
Number(nodePathWithCorrectedIndexForTruncatedAst[1][0]) - minIndex
|
||||||
return nodePathWithCorrectedIndexForTruncatedAst
|
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