From 8c8e2a9a8187eb45c363c5a171df1090104c673a Mon Sep 17 00:00:00 2001 From: Shibo Date: Tue, 17 Sep 2024 18:56:41 +0200 Subject: [PATCH] can now draw boards rectangular and outlined (#73) --- src/lib/circuit-to-pcb-svg.ts | 36 +++++++++-- .../create-svg-objects-from-pcb-board.ts | 63 +++++++++++++++++++ .../bottom-smtpads-in-blue.snap.svg | 5 +- .../bottom-trace-in-blue.snap.svg | 5 +- tests/__snapshots__/colored-fabnotes.snap.svg | 5 +- tests/__snapshots__/fabnotes.snap.svg | 5 +- .../silkscreen-with-hole-and-trace.snap.svg | 5 +- tests/bottom-trace-in-blue.test.ts | 2 +- tests/colored-fabnotes.test.tsx | 2 +- tests/fabnotes.test.tsx | 2 +- tests/silkscreen-with-hole-and-trace.test.ts | 8 +++ 11 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 src/lib/svg-object-fns/create-svg-objects-from-pcb-board.ts 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 @@ hello world! \ 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 @@ hello world! \ 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( - +