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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ The `convert3dCircuitToSvg` function accepts the following options:
- `backgroundColor`: Background color in hex format (default: "#ffffff")
- `padding`: Padding around the board (default: 20)
- `zoom`: Zoom level (default: 1.5)
- `showPcbNotes`: Whether to render `pcb_note_*` elements (default: `false`)
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This documentation line adds showPcbNotes to convert3dCircuitToSvg, but the codebase appears to export convertCircuitJsonTo3dSvg (see src/index.tsx) and tests import that name. Please update the README function name/import path (or add an alias export) so the documented API matches what consumers can actually call, including the new showPcbNotes option.

Copilot uses AI. Check for mistakes.
- `camera`: Camera position and lookAt configuration
- `position`: {x, y, z} coordinates for camera position
- `lookAt`: {x, y, z} coordinates for camera target
Expand All @@ -123,6 +124,7 @@ Props:
- `circuit-json`: (optional) An array of AnyCircuitElement objects representing the PCB layout.
- `children`: (optional) React children elements describing the PCB layout (alternative to using `circuit-json`).
- `resolveStaticAsset`: (optional) Function that receives each component model URL (`obj`, `wrl`, `stl`, `gltf`, `glb`, `step`) and returns the resolved URL to load.
- `showPcbNotes`: (optional) Show `pcb_note_*` graphics in 3D output. Defaults to `false`.

### `<board>`

Expand Down
3 changes: 3 additions & 0 deletions src/CadViewerJscad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface Props {
onUserInteraction?: () => void
onCameraControllerReady?: (controller: CameraController | null) => void
resolveStaticAsset?: (modelUrl: string) => string
showPcbNotes?: boolean
}

export const CadViewerJscad = forwardRef<
Expand All @@ -43,6 +44,7 @@ export const CadViewerJscad = forwardRef<
onUserInteraction,
onCameraControllerReady,
resolveStaticAsset,
showPcbNotes = false,
},
ref,
) => {
Expand Down Expand Up @@ -148,6 +150,7 @@ export const CadViewerJscad = forwardRef<
circuitJson={internalCircuitJson}
pcbThickness={pcbThickness}
isFaux={isFauxBoard}
showPcbNotes={showPcbNotes}
/>
{cad_components.map((cad_component) => (
<ThreeErrorBoundary
Expand Down
9 changes: 8 additions & 1 deletion src/CadViewerManifold.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ type CadViewerManifoldProps = {
onUserInteraction?: () => void
onCameraControllerReady?: (controller: CameraController | null) => void
resolveStaticAsset?: (modelUrl: string) => string
showPcbNotes?: boolean
} & (
| { circuitJson: AnyCircuitElement[]; children?: React.ReactNode }
| { circuitJson?: never; children: React.ReactNode }
Expand All @@ -143,6 +144,7 @@ const CadViewerManifold: React.FC<CadViewerManifoldProps> = ({
children,
onCameraControllerReady,
resolveStaticAsset,
showPcbNotes = false,
}) => {
const childrenCircuitJson = useConvertChildrenToCircuitJson(children)
const circuitJson = useMemo(() => {
Expand Down Expand Up @@ -241,7 +243,12 @@ try {
isLoading: builderIsLoading,
boardData,
isFauxBoard,
} = useManifoldBoardBuilder(manifoldJSModule, circuitJson, visibility)
} = useManifoldBoardBuilder(
manifoldJSModule,
circuitJson,
visibility,
showPcbNotes,
)

const geometryMeshes = useMemo(() => createGeometryMeshes(geoms), [geoms])
const textureMeshes = useMemo(
Expand Down
14 changes: 11 additions & 3 deletions src/convert-circuit-json-to-3d-svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface CircuitToSvgOptions {
backgroundColor?: string
padding?: number
zoom?: number
showPcbNotes?: boolean
camera?: {
position: {
x: number
Expand All @@ -42,8 +43,15 @@ export async function convertCircuitJsonTo3dSvg(
backgroundColor = "#ffffff",
padding = 20,
zoom = 1.5,
showPcbNotes = false,
} = options

const filteredCircuitJson = showPcbNotes
? circuitJson
: circuitJson.filter(
(element) => !(element.type as string).startsWith("pcb_note_"),
)

Comment on lines +49 to +54
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convertCircuitJsonTo3dSvg now accepts showPcbNotes, but enabling it doesn't actually render pcb_note_* elements in the SVG output (the code only filters them out when false). This makes the new option effectively a no-op for SVG export and doesn't match the stated behavior. Consider either implementing note rendering in the SVG scene when showPcbNotes is true, or removing/renaming the option from this converter to avoid misleading API surface.

Copilot uses AI. Check for mistakes.
// Initialize scene and renderer
const scene = new THREE.Scene()
const renderer = new SVGRenderer()
Expand Down Expand Up @@ -88,15 +96,15 @@ export async function convertCircuitJsonTo3dSvg(
scene.add(pointLight)

// Add components
const components = su(circuitJson).cad_component.list()
const components = su(filteredCircuitJson).cad_component.list()
for (const component of components) {
await renderComponent(component, scene)
}

const boardData = su(circuitJson).pcb_board.list()[0]
const boardData = su(filteredCircuitJson).pcb_board.list()[0]

// Add board geometry after components
const boardGeom = createBoardGeomFromCircuitJson(circuitJson)
const boardGeom = createBoardGeomFromCircuitJson(filteredCircuitJson)
if (boardGeom) {
// Use green solder mask color for the board
const solderMaskColor = colors.fr4SolderMaskGreen
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/useManifoldBoardBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const useManifoldBoardBuilder = (
manifoldJSModule: ManifoldToplevel | null,
circuitJson: AnyCircuitElement[],
visibility: LayerVisibilityState,
showPcbNotes = false,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use opts

): UseManifoldBoardBuilderResult => {
const [geoms, setGeoms] = useState<ManifoldGeoms | null>(null)
const [pcbThickness, setPcbThickness] = useState<number | null>(null)
Expand Down Expand Up @@ -354,8 +355,9 @@ export const useManifoldBoardBuilder = (
boardData,
traceTextureResolution,
visibility,
showPcbNotes,
})
}, [circuitJson, boardData, traceTextureResolution, visibility])
}, [circuitJson, boardData, traceTextureResolution, visibility, showPcbNotes])

return {
geoms,
Expand Down
4 changes: 3 additions & 1 deletion src/textures/create-combined-board-textures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,13 @@ export function createCombinedBoardTextures({
boardData,
traceTextureResolution,
visibility,
showPcbNotes = false,
}: {
circuitJson: AnyCircuitElement[]
boardData: PcbBoard
traceTextureResolution: number
visibility?: Partial<LayerVisibilityState>
showPcbNotes?: boolean
}): CombinedBoardTextures {
const traceColorWithMask = toRgb(defaultColors.fr4TracesWithMaskGreen)
const traceColorWithoutMask = toRgb(defaultColors.fr4TracesWithoutMaskTan)
Expand Down Expand Up @@ -154,7 +156,7 @@ export function createCombinedBoardTextures({
})
: null

const pcbNoteTexture = showSilkscreen
const pcbNoteTexture = showPcbNotes
? createPcbNoteTextureForLayer({
layer,
circuitJson,
Expand Down
13 changes: 9 additions & 4 deletions src/three-components/JscadBoardTextures.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ interface JscadBoardTexturesProps {
circuitJson: AnyCircuitElement[]
pcbThickness: number
isFaux?: boolean
showPcbNotes?: boolean
}

export function JscadBoardTextures({
circuitJson,
pcbThickness,
isFaux = false,
showPcbNotes = false,
}: JscadBoardTexturesProps) {
const { rootObject } = useThree()
const { visibility } = useLayerVisibility()
Expand Down Expand Up @@ -70,8 +72,9 @@ export function JscadBoardTextures({
boardData,
traceTextureResolution,
visibility,
showPcbNotes,
})
}, [circuitJson, boardData, traceTextureResolution, visibility])
}, [circuitJson, boardData, traceTextureResolution, visibility, showPcbNotes])

useEffect(() => {
if (!rootObject || !boardData || !textures) return
Expand Down Expand Up @@ -176,17 +179,19 @@ export function JscadBoardTextures({
}

return () => {
meshes.forEach((mesh) => {
for (const mesh of meshes) {
if (mesh.parent === rootObject) {
rootObject.remove(mesh)
}
mesh.geometry.dispose()
if (Array.isArray(mesh.material)) {
mesh.material.forEach((material) => disposeTextureMaterial(material))
for (const material of mesh.material) {
disposeTextureMaterial(material)
}
} else if (mesh.material instanceof THREE.Material) {
disposeTextureMaterial(mesh.material)
}
})
}

textures.topBoard?.dispose()
textures.bottomBoard?.dispose()
Expand Down
25 changes: 19 additions & 6 deletions stories/PcbNoteLine.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CadViewer } from "src/CadViewer"
import type { Meta, StoryObj } from "@storybook/react"

const pcbNoteLineCircuit = [
{
Expand Down Expand Up @@ -143,11 +144,23 @@ const pcbNoteLineCircuit = [
},
]

export const PcbNoteLines = () => (
<CadViewer circuitJson={pcbNoteLineCircuit as any} />
)

export default {
const meta = {
title: "PCB Note Line",
component: PcbNoteLines,
component: CadViewer,
argTypes: {
showPcbNotes: {
control: "boolean",
description: "Show/hide pcb_note_* elements in the 3D view",
},
},
} satisfies Meta<typeof CadViewer>

export default meta
type Story = StoryObj<typeof meta>

export const PcbNoteLines: Story = {
args: {
circuitJson: pcbNoteLineCircuit as any,
showPcbNotes: true,
},
}
72 changes: 72 additions & 0 deletions tests/pcb-notes-visibility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { expect, test } from "bun:test"
import type { AnyCircuitElement, PcbBoard } from "circuit-json"
import { JSDOM } from "jsdom"
import { createCombinedBoardTextures } from "../src/textures"
import { applyJsdomShim } from "../src/utils/jsdom-shim"
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this repo's other Bun tests, imports of internal TS modules include the .ts extension (e.g. ../src/utils/jsdom-shim.ts). This test uses an extensionless import, which can break under stricter ESM resolution/tooling and is inconsistent with the existing test suite style. Align the import with the other tests for consistency.

Suggested change
import { applyJsdomShim } from "../src/utils/jsdom-shim"
import { applyJsdomShim } from "../src/utils/jsdom-shim.ts"

Copilot uses AI. Check for mistakes.

test("pcb notes are hidden by default in board textures unless showPcbNotes is enabled", () => {
const dom = new JSDOM()
applyJsdomShim(dom)

const boardData: PcbBoard = {
type: "pcb_board",
pcb_board_id: "board_1",
center: { x: 0, y: 0 },
width: 10,
height: 8,
thickness: 1.6,
material: "fr4",
num_layers: 2,
}

const circuitJson: AnyCircuitElement[] = [
boardData,
{
type: "pcb_note_line",
pcb_note_line_id: "pcb_note_line_1",
pcb_component_id: "pcb_component_1",
x1: -3,
y1: 1,
x2: 3,
y2: 1,
stroke_width: 0.15,
layer: "top",
} as AnyCircuitElement,
]

const hiddenByDefault = createCombinedBoardTextures({
circuitJson,
boardData,
traceTextureResolution: 32,
visibility: {
boardBody: false,
topCopper: false,
bottomCopper: false,
topSilkscreen: false,
bottomSilkscreen: false,
topMask: false,
bottomMask: false,
},
})

expect(hiddenByDefault.topBoard).toBeNull()

const explicitlyShown = createCombinedBoardTextures({
circuitJson,
boardData,
traceTextureResolution: 32,
showPcbNotes: true,
visibility: {
boardBody: false,
topCopper: false,
bottomCopper: false,
topSilkscreen: false,
bottomSilkscreen: false,
topMask: false,
bottomMask: false,
},
})

expect(explicitlyShown.topBoard).not.toBeNull()
expect(explicitlyShown.topBoard?.image).toBeDefined()
})