diff --git a/src/lib/circuit-to-pcb-svg.ts b/src/lib/circuit-to-pcb-svg.ts
index 9fe36cb..fc11328 100644
--- a/src/lib/circuit-to-pcb-svg.ts
+++ b/src/lib/circuit-to-pcb-svg.ts
@@ -1,4 +1,4 @@
-import type { AnyCircuitElement } from "@tscircuit/soup"
+import type { Point, AnyCircuitElement } from "@tscircuit/soup"
import { type INode as SvgObject, stringify } from "svgson"
import {
type Matrix,
@@ -14,6 +14,7 @@ import { createSvgObjectsFromPcbSilkscreenPath } from "./svg-object-fns/create-s
import { createSvgObjectsFromPcbSilkscreenText } from "./svg-object-fns/create-svg-objects-from-pcb-silkscreen-text"
import { createSvgObjectsFromPcbTrace } from "./svg-object-fns/create-svg-objects-from-pcb-trace"
import { createSvgObjectsFromSmtPad } from "./svg-object-fns/create-svg-objects-from-smt-pads"
+import { createSvgObjectsFromPcbBoard } from "./svg-object-fns/create-svg-objects-from-pcb-board"
import { createSvgObjectsFromPcbVia } from "./svg-object-fns/create-svg-objects-from-pcb-via"
const OBJECT_ORDER: AnyCircuitElement["type"][] = [
@@ -26,6 +27,7 @@ const OBJECT_ORDER: AnyCircuitElement["type"][] = [
"pcb_trace",
"pcb_smtpad",
"pcb_component",
+ "pcb_board",
]
interface PointObjectNotation {
@@ -49,8 +51,15 @@ function circuitJsonToPcbSvg(
// Process all elements to determine bounds
for (const item of soup) {
- if ("center" in item && "width" in item && "height" in item) {
- updateBounds(item.center, item.width, item.height)
+ if (item.type === "pcb_board") {
+ if (
+ item.outline &&
+ Array.isArray(item.outline) &&
+ item.outline.length >= 3
+ )
+ updateBoundsToIncludeOutline(item.outline)
+ else if ("center" in item && "width" in item && "height" in item)
+ updateBounds(item.center, item.width, item.height)
} else if ("x" in item && "y" in item) {
updateBounds({ x: item.x, y: item.y }, 0, 0)
} else if ("route" in item) {
@@ -122,7 +131,8 @@ function circuitJsonToPcbSvg(
{
type: "text",
value: `
- .pcb-board { fill: #000; }
+ .boundary { fill: #000; }
+ .pcb-board { fill: none; }
.pcb-trace { fill: none; }
.pcb-hole-outer { fill: rgb(200, 52, 52); }
.pcb-hole-inner { fill: rgb(255, 38, 226); }
@@ -140,7 +150,7 @@ function circuitJsonToPcbSvg(
name: "rect",
type: "element",
attributes: {
- class: "pcb-board",
+ class: "boundary",
x: "0",
y: "0",
width: svgWidth.toString(),
@@ -168,6 +178,15 @@ function circuitJsonToPcbSvg(
maxY = Math.max(maxY, center.y + halfHeight)
}
+ function updateBoundsToIncludeOutline(outline: Point[]) {
+ for (const point of outline) {
+ minX = Math.min(minX, point.x)
+ minY = Math.min(minY, point.y)
+ maxX = Math.max(maxX, point.x)
+ maxY = Math.max(maxY, point.y)
+ }
+ }
+
function updateTraceBounds(route: any[]) {
for (const point of route) {
minX = Math.min(minX, point.x)
@@ -178,7 +197,10 @@ function circuitJsonToPcbSvg(
}
}
-function createSvgObjects(elm: AnyCircuitElement, transform: Matrix): SvgObject[] {
+function createSvgObjects(
+ elm: AnyCircuitElement,
+ transform: Matrix,
+): SvgObject[] {
switch (elm.type) {
case "pcb_component":
return [createSvgObjectsFromPcbComponent(elm, transform)].filter(Boolean)
@@ -196,6 +218,8 @@ function createSvgObjects(elm: AnyCircuitElement, transform: Matrix): SvgObject[
return createSvgObjectsFromPcbFabricationNoteText(elm, transform)
case "pcb_silkscreen_path":
return createSvgObjectsFromPcbSilkscreenPath(elm, transform)
+ case "pcb_board":
+ return createSvgObjectsFromPcbBoard(elm, transform)
case "pcb_via":
return createSvgObjectsFromPcbVia(elm, transform)
default:
diff --git a/src/lib/svg-object-fns/create-svg-objects-from-pcb-board.ts b/src/lib/svg-object-fns/create-svg-objects-from-pcb-board.ts
new file mode 100644
index 0000000..d9b54f0
--- /dev/null
+++ b/src/lib/svg-object-fns/create-svg-objects-from-pcb-board.ts
@@ -0,0 +1,63 @@
+import type { PCBBoard, Point } from "@tscircuit/soup"
+import { applyToPoint, type Matrix } from "transformation-matrix"
+import type { SvgObject } from "../svg-object"
+
+export function createSvgObjectsFromPcbBoard(
+ pcbBoard: PCBBoard,
+ transform: Matrix,
+): SvgObject[] {
+ const { width, height, center, outline } = pcbBoard
+
+ let path: string
+ if (outline && Array.isArray(outline) && outline.length >= 3) {
+ path = outline
+ .map((point: Point, index: number) => {
+ const [x, y] = applyToPoint(transform, [point.x, point.y])
+ return index === 0 ? `M ${x} ${y}` : `L ${x} ${y}`
+ })
+ .join(" ")
+ } else {
+ const halfWidth = width / 2
+ const halfHeight = height / 2
+
+ const topLeft = applyToPoint(transform, [
+ center.x - halfWidth,
+ center.y - halfHeight,
+ ])
+ const topRight = applyToPoint(transform, [
+ center.x + halfWidth,
+ center.y - halfHeight,
+ ])
+ const bottomRight = applyToPoint(transform, [
+ center.x + halfWidth,
+ center.y + halfHeight,
+ ])
+ const bottomLeft = applyToPoint(transform, [
+ center.x - halfWidth,
+ center.y + halfHeight,
+ ])
+
+ path =
+ `M ${topLeft[0]} ${topLeft[1]} ` +
+ `L ${topRight[0]} ${topRight[1]} ` +
+ `L ${bottomRight[0]} ${bottomRight[1]} ` +
+ `L ${bottomLeft[0]} ${bottomLeft[1]}`
+ }
+
+ path += " Z"
+
+ return [
+ {
+ name: "path",
+ type: "element",
+ value: "",
+ children: [],
+ attributes: {
+ class: "pcb-board",
+ d: path,
+ stroke: "rgba(255, 255, 255, 0.5)",
+ "stroke-width": (0.1 * Math.abs(transform.a)).toString(),
+ },
+ },
+ ]
+}
diff --git a/tests/__snapshots__/bottom-smtpads-in-blue.snap.svg b/tests/__snapshots__/bottom-smtpads-in-blue.snap.svg
index 08630cf..877de59 100644
--- a/tests/__snapshots__/bottom-smtpads-in-blue.snap.svg
+++ b/tests/__snapshots__/bottom-smtpads-in-blue.snap.svg
@@ -1,5 +1,6 @@
\ No newline at end of file
+
\ No newline at end of file
diff --git a/tests/__snapshots__/bottom-trace-in-blue.snap.svg b/tests/__snapshots__/bottom-trace-in-blue.snap.svg
index 91eb884..dd01cf2 100644
--- a/tests/__snapshots__/bottom-trace-in-blue.snap.svg
+++ b/tests/__snapshots__/bottom-trace-in-blue.snap.svg
@@ -1,5 +1,6 @@
\ No newline at end of file
+
\ No newline at end of file
diff --git a/tests/__snapshots__/colored-fabnotes.snap.svg b/tests/__snapshots__/colored-fabnotes.snap.svg
index f720716..79653f3 100644
--- a/tests/__snapshots__/colored-fabnotes.snap.svg
+++ b/tests/__snapshots__/colored-fabnotes.snap.svg
@@ -1,5 +1,6 @@
\ No newline at end of file
+ hello world!
\ No newline at end of file
diff --git a/tests/__snapshots__/fabnotes.snap.svg b/tests/__snapshots__/fabnotes.snap.svg
index 4f173e3..df76221 100644
--- a/tests/__snapshots__/fabnotes.snap.svg
+++ b/tests/__snapshots__/fabnotes.snap.svg
@@ -1,5 +1,6 @@
\ No newline at end of file
+ hello world!
\ No newline at end of file
diff --git a/tests/__snapshots__/silkscreen-with-hole-and-trace.snap.svg b/tests/__snapshots__/silkscreen-with-hole-and-trace.snap.svg
index 5ac446b..3b1787c 100644
--- a/tests/__snapshots__/silkscreen-with-hole-and-trace.snap.svg
+++ b/tests/__snapshots__/silkscreen-with-hole-and-trace.snap.svg
@@ -1,5 +1,6 @@
\ No newline at end of file
+
\ No newline at end of file
diff --git a/tests/bottom-trace-in-blue.test.ts b/tests/bottom-trace-in-blue.test.ts
index 17633f1..795bdb9 100644
--- a/tests/bottom-trace-in-blue.test.ts
+++ b/tests/bottom-trace-in-blue.test.ts
@@ -161,7 +161,7 @@ const soup: any = [
y: 0,
},
width: 10,
- height: 10,
+ height: 12,
},
{
type: "pcb_smtpad",
diff --git a/tests/colored-fabnotes.test.tsx b/tests/colored-fabnotes.test.tsx
index c5a2fc7..22e8248 100644
--- a/tests/colored-fabnotes.test.tsx
+++ b/tests/colored-fabnotes.test.tsx
@@ -6,7 +6,7 @@ test("fabrication note path and fabrication note text", () => {
const circuit = new Circuit()
circuit.add(
-
+
{
const circuit = new Circuit()
circuit.add(
-
+