Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 74 additions & 1 deletion lib/mesh-generation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export async function generateComponentMeshes(
} = options

const solids: GeneratedSceneSolid[] = []
let fallbackCircuitJson = circuitJson

try {
const filteredCircuitJson = circuitJson
Expand Down Expand Up @@ -93,6 +94,7 @@ export async function generateComponentMeshes(

return element
})
fallbackCircuitJson = filteredCircuitJson as CircuitJson

const { convertCircuitJsonTo3D } = await getCircuitJsonToGltfModule()

Expand All @@ -101,12 +103,83 @@ export async function generateComponentMeshes(
renderBoardTextures: false,
})

for (const box of scene3d.boxes as SceneBox[]) {
const boxes = scene3d.boxes?.length
? (scene3d.boxes as SceneBox[])
: createFallbackComponentBoxes(filteredCircuitJson, boardThickness)

for (const box of boxes) {
solids.push(createSceneBoxSolid(repo, box))
}
} catch (error) {
console.warn("Failed to generate component mesh:", error)
for (const box of createFallbackComponentBoxes(
fallbackCircuitJson,
boardThickness,
)) {
solids.push(createSceneBoxSolid(repo, box))
}
}

return solids
}

export function createFallbackComponentBoxes(
circuitJson: CircuitJson,
boardThickness: number,
): SceneBox[] {
const sourceNames = new Map<string, string>()
for (const element of circuitJson as any[]) {
if (
element.type === "source_component" &&
element.source_component_id &&
element.name
) {
sourceNames.set(element.source_component_id, element.name)
}
}

const componentThickness = 0.6

return (circuitJson as any[])
.filter((element) => element.type === "pcb_component")
.map((component): SceneBox | null => {
const center = component.center
const centerX = Number(center?.x)
const centerY = Number(center?.y)
const width = Number(component.width)
const height = Number(component.height)
if (
!Number.isFinite(centerX) ||
!Number.isFinite(centerY) ||
!Number.isFinite(width) ||
!Number.isFinite(height)
) {
return null
}

const layer = component.layer === "bottom" ? "bottom" : "top"
const verticalSign = layer === "bottom" ? -1 : 1
const rotationDegrees = Number(component.rotation ?? 0)

return {
center: {
x: centerX,
y: verticalSign * (boardThickness / 2 + componentThickness / 2),
z: centerY,
},
size: {
x: width,
y: componentThickness,
z: height,
},
rotation: Number.isFinite(rotationDegrees)
? { x: 0, y: (-rotationDegrees * Math.PI) / 180, z: 0 }
: undefined,
label:
sourceNames.get(component.source_component_id) ??
component.pcb_component_id ??
"Component",
}
})
.filter((box): box is SceneBox => box !== null)
}
67 changes: 67 additions & 0 deletions test/fallback-component-boxes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { expect, test } from "bun:test"

import { createFallbackComponentBoxes } from "../lib/mesh-generation"

test("creates fallback STEP boxes from pcb_component rectangles", () => {
const boxes = createFallbackComponentBoxes(
[
{
type: "source_component",
source_component_id: "source_component_1",
name: "R1",
supplier_part_numbers: {},
ftype: "simple_resistor",
},
{
type: "pcb_component",
pcb_component_id: "pcb_component_1",
source_component_id: "source_component_1",
center: { x: 12, y: 7 },
width: 3.2,
height: 1.6,
layer: "top",
rotation: 90,
},
{
type: "pcb_component",
pcb_component_id: "pcb_component_2",
center: { x: -4, y: 2 },
width: 2,
height: 1.25,
layer: "bottom",
},
] as any,
1.6,
)

expect(boxes).toHaveLength(2)
expect(boxes[0]).toMatchObject({
center: { x: 12, y: 1.1, z: 7 },
size: { x: 3.2, y: 0.6, z: 1.6 },
label: "R1",
})
expect(boxes[0]!.rotation?.y).toBeCloseTo(-Math.PI / 2)

expect(boxes[1]).toMatchObject({
center: { x: -4, y: -1.1, z: 2 },
size: { x: 2, y: 0.6, z: 1.25 },
label: "pcb_component_2",
})
})

test("skips fallback component boxes with missing dimensions", () => {
const boxes = createFallbackComponentBoxes(
[
{
type: "pcb_component",
pcb_component_id: "missing_width",
center: { x: 0, y: 0 },
height: 1,
layer: "top",
},
] as any,
1.6,
)

expect(boxes).toHaveLength(0)
})