* Start porting artifact graph creation to Rust * Add most of artifact graph creation * Add handling loft command from recent PR * Refactor artifact merge code so that it errors when a new artifact type is added * Add sweep subtype * Finish implementation of build artifact graph * Fix wasm.ts to use new combined generated ts-rs file * Fix Rust lints * Fix lints * Fix up replacement code * Add artifact graph to WASM outcome * Add artifact graph to simulation test output * Add new artifact graph output snapshots * Fix wall field and reduce unreachable code * Change field order for subtype * Change subtype to be determined from the request, like the TS * Fix plane sweep_id * Condense code * Change ID types to be properly optional * Change to favor the new ID, the same as TS * Fix to make error impossible * Rename artifact type tag values to match TS * Fix name of field on Cap * Update outputs * Change to use Rust source range * Update output snapshots * Add conversion to mermaid mind map and add to snapshot tests * Add new mermaid mind map output * Add flowchart * Remove raw artifact graph from tests * Remove JSON artifact graph output * Update output file with header * Update output after adding flowchart * Fix flowchart to not have duplicate edges, one in each direction * Fix not not output duplicate edges in flowcharts * Change flowchart edge style to be more obvious when a direction is missing * Update output after deduplication of edges * Fix not not skip sketch-on-face artifacts * Add docs * Fix edge iteration order to be stable * Update output after fixing order * Port TS artifactGraph.test.ts tests to simulation tests * Add grouping segments and solid2ds with their path * Update output flowcharts since grouping paths * Remove TS artifactGraph tests * Remove unused d3 dependencies * Fix to track loft ID on paths * Add command ID to error messages * Move artifact graph test code to a separate file since it's a large file * Reduce function visibility * Remove TS artifact graph code * Fix spelling error with serde * Add TODO for edge cut consumed ID * Add comment about mermaid edge rank * Fix mermaid flowchart edge cuts to appear as children of their edges * Update output since fixing flowchart order * Fix to always build the artifact graph even when there's a KCL error * Add artifact graph to error output * Change optional ID merge to match TS * Remove redundant SourceRange definition * Remove Rust-flavored default source range function * Add helper for source range creation * Update doc comment for the website * Update docs after doc comment change * Fix to save engine responses in execution cache * Remove unused import * Fix to not call WASM function before beforeAll callback is run * Remove more unused imports
409 lines
16 KiB
TypeScript
409 lines
16 KiB
TypeScript
import {
|
|
assertParse,
|
|
Sketch,
|
|
recast,
|
|
initPromise,
|
|
sketchFromKclValue,
|
|
SourceRange,
|
|
topLevelRange,
|
|
} from '../wasm'
|
|
import {
|
|
ConstraintType,
|
|
getTransformInfos,
|
|
transformAstSketchLines,
|
|
} from './sketchcombos'
|
|
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
|
import { enginelessExecutor } from '../../lib/testHelpers'
|
|
import { err } from 'lib/trap'
|
|
import { codeRefFromRange } from './artifactGraph'
|
|
|
|
beforeAll(async () => {
|
|
await initPromise
|
|
})
|
|
|
|
// testing helper function
|
|
async function testingSwapSketchFnCall({
|
|
inputCode,
|
|
callToSwap,
|
|
constraintType,
|
|
}: {
|
|
inputCode: string
|
|
callToSwap: string
|
|
constraintType: ConstraintType
|
|
}): Promise<{
|
|
newCode: string
|
|
originalRange: SourceRange
|
|
}> {
|
|
const startIndex = inputCode.indexOf(callToSwap)
|
|
const range = topLevelRange(startIndex, startIndex + callToSwap.length)
|
|
const ast = assertParse(inputCode)
|
|
|
|
const execState = await enginelessExecutor(ast)
|
|
const selections = {
|
|
graphSelections: [
|
|
{
|
|
codeRef: codeRefFromRange(range, ast),
|
|
},
|
|
],
|
|
otherSelections: [],
|
|
}
|
|
const transformInfos = getTransformInfos(selections, ast, constraintType)
|
|
|
|
if (!transformInfos)
|
|
return Promise.reject(new Error('transformInfos undefined'))
|
|
const ast2 = transformAstSketchLines({
|
|
ast,
|
|
programMemory: execState.memory,
|
|
selectionRanges: selections,
|
|
transformInfos,
|
|
referenceSegName: '',
|
|
})
|
|
if (err(ast2)) return Promise.reject(ast2)
|
|
|
|
const newCode = recast(ast2.modifiedAst)
|
|
if (err(newCode)) return Promise.reject(newCode)
|
|
|
|
return {
|
|
newCode,
|
|
originalRange: range,
|
|
}
|
|
}
|
|
|
|
describe('testing swapping out sketch calls with xLine/xLineTo', () => {
|
|
const bigExampleArr = [
|
|
`part001 = startSketchOn('XY')`,
|
|
` |> startProfileAt([0, 0], %)`,
|
|
` |> lineTo([1, 1], %, $abc1)`,
|
|
` |> line([-2.04, -0.7], %, $abc2)`,
|
|
` |> angledLine({ angle = 157, length = 1.69 }, %, $abc3)`,
|
|
` |> angledLineOfXLength({ angle = 217, length = 0.86 }, %, $abc4)`,
|
|
` |> angledLineOfYLength({ angle = 104, length = 1.58 }, %, $abc5)`,
|
|
` |> angledLineToX({ angle = 55, to = -2.89 }, %, $abc6)`,
|
|
` |> angledLineToY({ angle = 330, to = 2.53 }, %, $abc7)`,
|
|
` |> xLine(1.47, %, $abc8)`,
|
|
` |> yLine(1.57, %, $abc9)`,
|
|
` |> xLineTo(1.49, %, $abc10)`,
|
|
` |> yLineTo(2.64, %, $abc11)`,
|
|
` |> lineTo([2.55, 3.58], %) // lineTo`,
|
|
` |> line([0.73, -0.75], %)`,
|
|
` |> angledLine([63, 1.38], %) // angledLine`,
|
|
` |> angledLineOfXLength([319, 1.15], %) // angledLineOfXLength`,
|
|
` |> angledLineOfYLength([50, 1.35], %) // angledLineOfYLength`,
|
|
` |> angledLineToX([291, 6.66], %) // angledLineToX`,
|
|
` |> angledLineToY([228, 2.14], %) // angledLineToY`,
|
|
` |> xLine(-1.33, %)`,
|
|
` |> yLine(-1.07, %)`,
|
|
` |> xLineTo(3.27, %)`,
|
|
` |> yLineTo(2.14, %)`,
|
|
]
|
|
const bigExample = bigExampleArr.join('\n')
|
|
it('line with tag converts to xLine', async () => {
|
|
const callToSwap = 'line([-2.04, -0.7], %, $abc2)'
|
|
const expectedLine = 'xLine(-2.04, %, $abc2)'
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap,
|
|
constraintType: 'horizontal',
|
|
})
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('line w/o tag converts to xLine', async () => {
|
|
const callToSwap = 'line([0.73, -0.75], %)'
|
|
const expectedLine = 'xLine(0.73, %)'
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap,
|
|
constraintType: 'horizontal',
|
|
})
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('lineTo with tag converts to xLineTo', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap: 'lineTo([1, 1], %, $abc1)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLineTo(1, %, $abc1)'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('lineTo w/o tag converts to xLineTo', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap: 'lineTo([2.55, 3.58], %)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLineTo(2.55, %) // lineTo'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLine with tag converts to xLine', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap: 'angledLine({ angle = 157, length = 1.69 }, %, $abc3)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLine(-1.56, %, $abc3)'
|
|
console.log(newCode)
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLine w/o tag converts to xLine', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap: 'angledLine([63, 1.38], %)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLine(0.63, %) // angledLine'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineOfXLength with tag converts to xLine', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap:
|
|
'angledLineOfXLength({ angle = 217, length = 0.86 }, %, $abc4)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLine(-0.86, %, $abc4)'
|
|
// hmm "-0.86" is correct since the angle is 104, but need to make sure this is compatible `-myVar`
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineOfXLength w/o tag converts to xLine', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap: 'angledLineOfXLength([319, 1.15], %)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLine(1.15, %) // angledLineOfXLength'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineOfYLength with tag converts to yLine', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap:
|
|
'angledLineOfYLength({ angle = 104, length = 1.58 }, %, $abc5)',
|
|
constraintType: 'vertical',
|
|
})
|
|
const expectedLine = 'yLine(1.58, %, $abc5)'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineOfYLength w/o tag converts to yLine', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap: 'angledLineOfYLength([50, 1.35], %)',
|
|
constraintType: 'vertical',
|
|
})
|
|
const expectedLine = 'yLine(1.35, %) // angledLineOfYLength'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineToX with tag converts to xLineTo', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap: 'angledLineToX({ angle = 55, to = -2.89 }, %, $abc6)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLineTo(-2.89, %, $abc6)'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineToX w/o tag converts to xLineTo', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap: 'angledLineToX([291, 6.66], %)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLineTo(6.66, %) // angledLineToX'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineToY with tag converts to yLineTo', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap: 'angledLineToY({ angle = 330, to = 2.53 }, %, $abc7)',
|
|
constraintType: 'vertical',
|
|
})
|
|
const expectedLine = 'yLineTo(2.53, %, $abc7)'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineToY w/o tag converts to yLineTo', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: bigExample,
|
|
callToSwap: 'angledLineToY([228, 2.14], %)',
|
|
constraintType: 'vertical',
|
|
})
|
|
const expectedLine = 'yLineTo(2.14, %) // angledLineToY'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
})
|
|
|
|
describe('testing swapping out sketch calls with xLine/xLineTo while keeping variable/identifiers intact', () => {
|
|
// Enable rotations #152
|
|
const variablesExampleArr = [
|
|
`lineX = -1`,
|
|
`lineToX = -1.3`,
|
|
`angledLineAngle = 207`,
|
|
`angledLineOfXLengthX = 0.8`,
|
|
`angledLineOfYLengthY = 0.89`,
|
|
`angledLineToXx = -1.86`,
|
|
`angledLineToYy = -0.76`,
|
|
`part001 = startSketchOn('XY')`,
|
|
` |> startProfileAt([0, 0], %)`,
|
|
// ` |> rx(90, %)`,
|
|
` |> lineTo([1, 1], %)`,
|
|
` |> line([lineX, 2.13], %)`,
|
|
` |> lineTo([lineToX, 2.85], %)`,
|
|
` |> angledLine([angledLineAngle, 1.64], %)`,
|
|
` |> angledLineOfXLength([329, angledLineOfXLengthX], %)`,
|
|
` |> angledLineOfYLength([222, angledLineOfYLengthY], %)`,
|
|
` |> angledLineToX([330, angledLineToXx], %)`,
|
|
` |> angledLineToY([217, angledLineToYy], %)`,
|
|
` |> line([0.89, -0.1], %)`,
|
|
]
|
|
const varExample = variablesExampleArr.join('\n')
|
|
it('line keeps variable when converted to xLine', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: varExample,
|
|
callToSwap: 'line([lineX, 2.13], %)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLine(lineX, %)'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('lineTo keeps variable when converted to xLineTo', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: varExample,
|
|
callToSwap: 'lineTo([lineToX, 2.85], %)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLineTo(lineToX, %)'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineOfXLength keeps variable when converted to xLine', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: varExample,
|
|
callToSwap: 'angledLineOfXLength([329, angledLineOfXLengthX], %)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLine(angledLineOfXLengthX, %)'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineOfYLength keeps variable when converted to yLine', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: varExample,
|
|
callToSwap: 'angledLineOfYLength([222, angledLineOfYLengthY], %)',
|
|
constraintType: 'vertical',
|
|
})
|
|
const expectedLine = 'yLine(-angledLineOfYLengthY, %)'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineToX keeps variable when converted to xLineTo', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: varExample,
|
|
callToSwap: 'angledLineToX([330, angledLineToXx], %)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
const expectedLine = 'xLineTo(angledLineToXx, %)'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
it('angledLineToY keeps variable when converted to yLineTo', async () => {
|
|
const { newCode, originalRange } = await testingSwapSketchFnCall({
|
|
inputCode: varExample,
|
|
callToSwap: 'angledLineToY([217, angledLineToYy], %)',
|
|
constraintType: 'vertical',
|
|
})
|
|
const expectedLine = 'yLineTo(angledLineToYy, %)'
|
|
expect(newCode).toContain(expectedLine)
|
|
// new line should start at the same place as the old line
|
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
|
})
|
|
|
|
it('trying to convert angledLineToY to xLineTo should not work because of the variable', async () => {
|
|
const illegalConvert = () =>
|
|
testingSwapSketchFnCall({
|
|
inputCode: varExample,
|
|
callToSwap: 'angledLineToY([217, angledLineToYy], %)',
|
|
constraintType: 'horizontal',
|
|
})
|
|
await expect(illegalConvert).rejects.toThrowError('no callback helper')
|
|
})
|
|
})
|
|
|
|
describe('testing getSketchSegmentIndexFromSourceRange', () => {
|
|
const code = `
|
|
part001 = startSketchOn('XY')
|
|
|> startProfileAt([0, 0.04], %) // segment-in-start
|
|
|> line([0, 0.4], %)
|
|
|> xLine(3.48, %)
|
|
|> line([2.14, 1.35], %) // normal-segment
|
|
|> xLine(3.54, %)`
|
|
it('normal case works', async () => {
|
|
const execState = await enginelessExecutor(assertParse(code))
|
|
const index = code.indexOf('// normal-segment') - 7
|
|
const sg = sketchFromKclValue(
|
|
execState.memory.get('part001'),
|
|
'part001'
|
|
) as Sketch
|
|
const _segment = getSketchSegmentFromSourceRange(
|
|
sg,
|
|
topLevelRange(index, index)
|
|
)
|
|
if (err(_segment)) throw _segment
|
|
const { __geoMeta, ...segment } = _segment.segment
|
|
expect(segment).toEqual({
|
|
type: 'ToPoint',
|
|
to: [5.62, 1.79],
|
|
from: [3.48, 0.44],
|
|
tag: null,
|
|
})
|
|
})
|
|
it('verify it works when the segment is in the `start` property', async () => {
|
|
const execState = await enginelessExecutor(assertParse(code))
|
|
const index = code.indexOf('// segment-in-start') - 7
|
|
const _segment = getSketchSegmentFromSourceRange(
|
|
sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
|
|
topLevelRange(index, index)
|
|
)
|
|
if (err(_segment)) throw _segment
|
|
const { __geoMeta, ...segment } = _segment.segment
|
|
expect(segment).toEqual({
|
|
to: [0, 0.04],
|
|
from: [0, 0.04],
|
|
tag: null,
|
|
type: 'Base',
|
|
})
|
|
})
|
|
})
|