diff --git a/src/lang/std/artifactGraph.test.ts b/src/lang/std/artifactGraph.test.ts index a36ef9e82..996876f0e 100644 --- a/src/lang/std/artifactGraph.test.ts +++ b/src/lang/std/artifactGraph.test.ts @@ -49,6 +49,26 @@ sketch002 = startSketchOn(extrude001, seg02) extrude002 = extrude(5, sketch002) ` +const exampleCodeNo3D = `sketch003 = startSketchOn('YZ') + |> startProfileAt([5.82, 0], %) + |> angledLine([180, 11.54], %, $rectangleSegmentA001) + |> angledLine([ + segAng(rectangleSegmentA001) - 90, + 8.21 + ], %, $rectangleSegmentB001) + |> angledLine([ + segAng(rectangleSegmentA001), + -segLen(rectangleSegmentA001) + ], %, $rectangleSegmentC001) + |> lineTo([profileStartX(%), profileStartY(%)], %) + |> close(%) +sketch004 = startSketchOn('-XZ') + |> startProfileAt([0, 14.36], %) + |> line([15.49, 0.05], %) + |> tangentialArcTo([0, 0], %) + |> tangentialArcTo([-6.8, 8.17], %) +` + const sketchOnFaceOnFaceEtc = `sketch001 = startSketchOn('XZ') |> startProfileAt([0, 0], %) |> line([4, 8], %) @@ -83,6 +103,7 @@ extrude004 = extrude(3, sketch004) const codeToWriteCacheFor = { exampleCode1, sketchOnFaceOnFaceEtc, + exampleCodeNo3D, } as const type CodeKey = keyof typeof codeToWriteCacheFor @@ -236,6 +257,69 @@ describe('testing createArtifactGraph', () => { await GraphTheGraph(theMap, 2000, 2000, 'exampleCode1.png') }, 20000) }) + + describe(`code with sketches but no extrusions or other 3D elements`, () => { + let ast: Program + let theMap: ReturnType + it(`setup`, () => { + // putting this logic in here because describe blocks runs before beforeAll has finished + const { + orderedCommands, + responseMap, + ast: _ast, + } = getCommands('exampleCodeNo3D') + ast = _ast + theMap = createArtifactGraph({ orderedCommands, responseMap, ast }) + }) + + it('there should be two planes, one for each sketch path', () => { + const planes = [...filterArtifacts({ types: ['plane'] }, theMap)].map( + (plane) => expandPlane(plane[1], theMap) + ) + expect(planes).toHaveLength(2) + planes.forEach((path) => { + expect(path.type).toBe('plane') + }) + }) + it('there should be two paths, one on each plane', () => { + const paths = [...filterArtifacts({ types: ['path'] }, theMap)].map( + (path) => expandPath(path[1], theMap) + ) + expect(paths).toHaveLength(2) + paths.forEach((path) => { + if (err(path)) throw path + expect(path.type).toBe('path') + }) + }) + + it(`there should be 1 solid2D, just for the first closed path`, () => { + const solid2Ds = [...filterArtifacts({ types: ['solid2D'] }, theMap)] + expect(solid2Ds).toHaveLength(1) + }) + + it('there should be no extrusions', () => { + const extrusions = [...filterArtifacts({ types: ['sweep'] }, theMap)].map( + (extrusion) => expandSweep(extrusion[1], theMap) + ) + expect(extrusions).toHaveLength(0) + }) + + it('there should be 8 segments, 4 + 1 (close) from the first sketch and 3 from the second', () => { + const segments = [...filterArtifacts({ types: ['segment'] }, theMap)].map( + (segment) => expandSegment(segment[1], theMap) + ) + expect(segments).toHaveLength(8) + }) + + it('screenshot graph', async () => { + // Ostensibly this takes a screen shot of the graph of the artifactGraph + // but it's it also tests that all of the id links are correct because if one + // of the edges refers to a non-existent node, the graph will throw. + // further more we can check that each edge is bi-directional, if it's not + // by checking the arrow heads going both ways, on the graph. + await GraphTheGraph(theMap, 2000, 2000, 'exampleCodeNo3D.png') + }, 20000) + }) }) describe('capture graph of sketchOnFaceOnFace...', () => { @@ -457,7 +541,10 @@ async function GraphTheGraph( `./src/lang/std/artifactMapGraphs/${imageName}` ) // chop the top 30 pixels off the image - const originalImg = PNG.sync.read(fs.readFileSync(originalImgPath)) + const originalImgExists = fs.existsSync(originalImgPath) + const originalImg = originalImgExists + ? PNG.sync.read(fs.readFileSync(originalImgPath)) + : null // const img1Data = new Uint8Array(img1.data) // const img1DataChopped = img1Data.slice(30 * img1.width * 4) // img1.data = Buffer.from(img1DataChopped) @@ -468,10 +555,10 @@ async function GraphTheGraph( const newImageDataChopped = newImageData.slice(30 * newImage.width * 4) newImage.data = Buffer.from(newImageDataChopped) - const { width, height } = originalImg + const { width, height } = originalImg ?? newImage const diff = new PNG({ width, height }) - const imageSizeDifferent = originalImg.data.length !== newImage.data.length + const imageSizeDifferent = originalImg?.data.length !== newImage.data.length let numDiffPixels = 0 if (!imageSizeDifferent) { numDiffPixels = pixelmatch( diff --git a/src/lang/std/artifactGraph.ts b/src/lang/std/artifactGraph.ts index fc7625576..702e1a14a 100644 --- a/src/lang/std/artifactGraph.ts +++ b/src/lang/std/artifactGraph.ts @@ -36,9 +36,12 @@ interface solid2D { } export interface PathArtifactRich { type: 'path' + /** A path must always lie on a plane */ plane: PlaneArtifact | WallArtifact + /** A path must always contain 0 or more segments */ segments: Array - sweep: SweepArtifact + /** A path may not result in a sweep artifact */ + sweep?: SweepArtifact codeRef: CommonCommandProperties } @@ -587,13 +590,15 @@ export function expandPath( { keys: path.segIds, types: ['segment'] }, artifactGraph ) - const sweep = getArtifactOfTypes( - { - key: path.sweepId, - types: ['sweep'], - }, - artifactGraph - ) + const sweep = path.sweepId + ? getArtifactOfTypes( + { + key: path.sweepId, + types: ['sweep'], + }, + artifactGraph + ) + : undefined const plane = getArtifactOfTypes( { key: path.planeId, types: ['plane', 'wall'] }, artifactGraph diff --git a/src/lang/std/artifactMapGraphs/exampleCodeNo3D.png b/src/lang/std/artifactMapGraphs/exampleCodeNo3D.png new file mode 100644 index 000000000..24932cb93 Binary files /dev/null and b/src/lang/std/artifactMapGraphs/exampleCodeNo3D.png differ