diff --git a/lib/mesh-generation.ts b/lib/mesh-generation.ts index 1df41fe..260dc1f 100644 --- a/lib/mesh-generation.ts +++ b/lib/mesh-generation.ts @@ -327,44 +327,44 @@ export async function generateComponentMeshes( renderBoardTextures: false, }) - // Extract or generate triangles from component boxes - const allTriangles: GLTFTriangle[] = [] + // Create one ManifoldSolidBrep per component box so each box forms a valid + // closed manifold surface. A ClosedShell requires a single connected closed + // surface — merging all boxes into one ClosedShell produces invalid STEP + // topology that viewers silently discard. for (const box of scene3d.boxes) { - if (box.mesh && "triangles" in box.mesh) { - allTriangles.push(...box.mesh.triangles) - } else { - // Generate simple box mesh for this component - const boxTriangles = createBoxTriangles(box) - allTriangles.push(...boxTriangles) - } - } + const boxTriangles = + box.mesh && "triangles" in box.mesh + ? box.mesh.triangles + : createBoxTriangles(box) + + if (boxTriangles.length === 0) continue - // Create STEP faces from triangles if we have any - if (allTriangles.length > 0) { // Transform triangles from GLTF XZ plane (Y=up) to STEP XY plane (Z=up) - const transformedTriangles = allTriangles.map((tri) => ({ - vertices: tri.vertices.map((v) => ({ - x: v.x, - y: v.z, // GLTF Z becomes STEP Y - z: v.y, // GLTF Y becomes STEP Z - })), + const transformedTriangles = boxTriangles.map((tri: GLTFTriangle) => ({ + vertices: tri.vertices.map( + (v: { x: number; y: number; z: number }) => ({ + x: v.x, + y: v.z, // GLTF Z becomes STEP Y + z: v.y, // GLTF Y becomes STEP Z + }), + ), normal: { x: tri.normal.x, y: tri.normal.z, // GLTF Z becomes STEP Y z: tri.normal.y, // GLTF Y becomes STEP Z }, })) + const componentFaces = createStepFacesFromTriangles( repo, transformedTriangles as any, ) - // Create closed shell and solid for components const componentShell = repo.add( new ClosedShell("", componentFaces as any), ) const componentSolid = repo.add( - new ManifoldSolidBrep("Components", componentShell), + new ManifoldSolidBrep("Component", componentShell), ) solids.push(componentSolid) } diff --git a/test/basics/basics04/basics04.test.ts b/test/basics/basics04/basics04.test.ts index 3c5e84c..5737b76 100644 --- a/test/basics/basics04/basics04.test.ts +++ b/test/basics/basics04/basics04.test.ts @@ -22,9 +22,11 @@ test("basics04: convert circuit json with components to STEP", async () => { expect(stepText).toContain("CIRCLE") expect(stepText).toContain("CYLINDRICAL_SURFACE") - // Verify we have multiple solids (board + components) + // Verify we have one solid per element: 1 board + 2 component boxes = 3 total. + // Each component box must be its own ManifoldSolidBrep (issue #6: merging all + // boxes into a single ClosedShell creates invalid STEP topology that viewers discard). const solidCount = (stepText.match(/MANIFOLD_SOLID_BREP/g) || []).length - expect(solidCount).toBeGreaterThanOrEqual(1) + expect(solidCount).toBe(3) // Write STEP file to debug-output const outputPath = "debug-output/basics04.step"