From 96894d197176ac397650e8ae13c4b9b8a760c59b Mon Sep 17 00:00:00 2001 From: chengyixu Date: Sun, 29 Mar 2026 04:15:02 +0800 Subject: [PATCH 1/3] fix: create one ManifoldSolidBrep per component box (closes #6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, all component box triangles were merged into a single ClosedShell and wrapped in one ManifoldSolidBrep. A ClosedShell in STEP must be a single connected closed surface — multiple disconnected boxes cannot form a valid shell. STEP viewers silently reject the invalid topology, making all component rectangles invisible. Fix: iterate over each box individually, build separate ClosedShell and ManifoldSolidBrep for each. This produces one solid per component (e.g. basics04: 1 board + 2 components = 3 solids; repro01: 5 solids), all of which pass occt-import-js validation. Updated basics04 test assertion from solidCount >= 1 to solidCount === 3 to guard against regression. Co-Authored-By: Claude Sonnet 4.6 --- lib/mesh-generation.ts | 28 +++++++++++++-------------- test/basics/basics04/basics04.test.ts | 6 ++++-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/mesh-generation.ts b/lib/mesh-generation.ts index 1df41fe..486e41c 100644 --- a/lib/mesh-generation.ts +++ b/lib/mesh-generation.ts @@ -327,22 +327,20 @@ 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) => ({ + const transformedTriangles = boxTriangles.map((tri) => ({ vertices: tri.vertices.map((v) => ({ x: v.x, y: v.z, // GLTF Z becomes STEP Y @@ -354,17 +352,17 @@ export async function generateComponentMeshes( 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" From 314c912607f8823e7e693f53eec4b031fd473eb2 Mon Sep 17 00:00:00 2001 From: chengyixu Date: Sun, 29 Mar 2026 14:25:49 +0800 Subject: [PATCH 2/3] fix: add explicit type annotations to resolve TS7006 implicit any errors Add GLTFTriangle and Point3-compatible types to the .map() callbacks in generateComponentMeshes() to satisfy strict TypeScript type checking. Co-Authored-By: Claude Sonnet 4.6 --- lib/mesh-generation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mesh-generation.ts b/lib/mesh-generation.ts index 486e41c..8c01fd8 100644 --- a/lib/mesh-generation.ts +++ b/lib/mesh-generation.ts @@ -340,8 +340,8 @@ export async function generateComponentMeshes( if (boxTriangles.length === 0) continue // Transform triangles from GLTF XZ plane (Y=up) to STEP XY plane (Z=up) - const transformedTriangles = boxTriangles.map((tri) => ({ - vertices: tri.vertices.map((v) => ({ + 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 From 7fb2a06cc99269608c0efd014a7c780b9b0345ca Mon Sep 17 00:00:00 2001 From: chengyixu Date: Sun, 29 Mar 2026 14:28:15 +0800 Subject: [PATCH 3/3] style: fix biome formatting for nested map callback Reformat the tri.vertices.map() call to satisfy biome's line-length rule, resolving the format-check CI failure. --- lib/mesh-generation.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/mesh-generation.ts b/lib/mesh-generation.ts index 8c01fd8..260dc1f 100644 --- a/lib/mesh-generation.ts +++ b/lib/mesh-generation.ts @@ -341,11 +341,13 @@ export async function generateComponentMeshes( // Transform triangles from GLTF XZ plane (Y=up) to STEP XY plane (Z=up) 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 - })), + 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