diff --git a/bun.lockb b/bun.lockb
index 288ff31..1b3a21b 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index fd0027f..0924f04 100644
--- a/package.json
+++ b/package.json
@@ -26,9 +26,11 @@
"@storybook/react": "^8.2.5",
"@storybook/react-vite": "^8.2.5",
"@storybook/test": "^8.2.5",
+ "@tscircuit/core": "^0.0.55",
"@tscircuit/plop": "^0.0.10",
"bun-match-svg": "^0.0.3",
"esbuild": "^0.20.2",
+ "react": "^18.3.1",
"storybook": "^8.2.5",
"tsup": "^8.0.2",
"typescript": "^5.4.5",
diff --git a/src/lib/circuit-to-pcb-svg.ts b/src/lib/circuit-to-pcb-svg.ts
index d58107a..d180f29 100644
--- a/src/lib/circuit-to-pcb-svg.ts
+++ b/src/lib/circuit-to-pcb-svg.ts
@@ -9,7 +9,10 @@ import {
} from "transformation-matrix"
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 { createSvgObjectsFromPcbSilkscreenText } from "./svg-object-fns/create-svg-objects-from-pcb-slikscreen-text"
+import { createSvgObjectsFromPcbSilkscreenText } from "./svg-object-fns/create-svg-objects-from-pcb-silkscreen-text"
+import { createSvgObjectsFromPcbFabricationNotePath } from "./svg-object-fns/create-svg-objects-from-pcb-fabrication-note-path"
+import { createSvgObjectsFromPcbSilkscreenPath } from "./svg-object-fns/create-svg-objects-from-pcb-silkscreen-path"
+import { createSvgObjectsFromPcbFabricationNoteText } from "./svg-object-fns/create-svg-objects-from-pcb-fabrication-note-text"
interface PointObjectNotation {
x: number
@@ -73,7 +76,7 @@ function circuitJsonToPcbSvg(soup: AnySoupElement[]): string {
const silkscreenElements = soup
.filter((elm) => elm.type === "pcb_silkscreen_path")
- .flatMap((elm) => createPcbSilkscreenPath(elm, transform))
+ .flatMap((elm) => createSvgObjectsFromPcbSilkscreenPath(elm, transform))
const otherElements = soup
.filter(
@@ -201,6 +204,10 @@ function createSvgObjects(elm: AnySoupElement, transform: Matrix): SvgObject[] {
return createSvgObjectsFromSmtPad(elm, transform)
case "pcb_silkscreen_text":
return createSvgObjectsFromPcbSilkscreenText(elm, transform)
+ case "pcb_fabrication_note_path":
+ return createSvgObjectsFromPcbFabricationNotePath(elm, transform)
+ case "pcb_fabrication_note_text":
+ return createSvgObjectsFromPcbFabricationNoteText(elm, transform)
default:
return []
}
@@ -276,38 +283,6 @@ function createSvgObjectsFromPcbHole(hole: any, transform: any): any {
}
}
-function createPcbSilkscreenPath(silkscreenPath: any, transform: any): any {
- if (!silkscreenPath.route || !Array.isArray(silkscreenPath.route)) return null
-
- let path = silkscreenPath.route
- .map((point: any, index: number) => {
- const [x, y] = applyToPoint(transform, [point.x, point.y])
- return index === 0 ? `M ${x} ${y}` : `L ${x} ${y}`
- })
- .join(" ")
-
- // Close the path if it's not already closed
- const firstPoint = silkscreenPath.route[0]
- const lastPoint = silkscreenPath.route[silkscreenPath.route.length - 1]
- if (firstPoint.x !== lastPoint.x || firstPoint.y !== lastPoint.y) {
- path += " Z"
- }
-
- return {
- name: "path",
- type: "element",
- attributes: {
- class: `pcb-silkscreen pcb-silkscreen-${silkscreenPath.layer}`,
- d: path,
- "stroke-width": (
- silkscreenPath.stroke_width * Math.abs(transform.a)
- ).toString(),
- "data-pcb-component-id": silkscreenPath.pcb_component_id,
- "data-pcb-silkscreen-path-id": silkscreenPath.pcb_silkscreen_path_id,
- },
- }
-}
-
function createSvgObjectFromPcbBoundary(
transform: any,
minX: number,
diff --git a/src/lib/svg-object-fns/create-svg-objects-from-pcb-fabrication-note-path.ts b/src/lib/svg-object-fns/create-svg-objects-from-pcb-fabrication-note-path.ts
new file mode 100644
index 0000000..0ce7d25
--- /dev/null
+++ b/src/lib/svg-object-fns/create-svg-objects-from-pcb-fabrication-note-path.ts
@@ -0,0 +1,43 @@
+import type { PcbSilkscreenPath, PcbFabricationNotePath } from "@tscircuit/soup"
+import { applyToPoint, type Matrix } from "transformation-matrix"
+import type { SvgObject } from "../svg-object"
+
+export function createSvgObjectsFromPcbFabricationNotePath(
+ fabNotePath: PcbFabricationNotePath,
+ transform: Matrix,
+): SvgObject[] {
+ if (!fabNotePath.route || !Array.isArray(fabNotePath.route)) return []
+
+ let path = fabNotePath.route
+ .map((point: any, index: number) => {
+ const [x, y] = applyToPoint(transform, [point.x, point.y])
+ return index === 0 ? `M ${x} ${y}` : `L ${x} ${y}`
+ })
+ .join(" ")
+
+ // Close the path if it's not already closed
+ const firstPoint = fabNotePath.route[0]
+ const lastPoint = fabNotePath.route[fabNotePath.route.length - 1]
+ if (firstPoint!.x !== lastPoint!.x || firstPoint!.y !== lastPoint!.y) {
+ path += " Z"
+ }
+ return [
+ {
+ name: "path",
+ type: "element",
+ attributes: {
+ class: "pcb-fabrication-note-path",
+ stroke: "rgba(255,255,255,0.5)",
+ d: path,
+ "stroke-width": (
+ fabNotePath.stroke_width * Math.abs(transform.a)
+ ).toString(),
+ "data-pcb-component-id": fabNotePath.pcb_component_id,
+ "data-pcb-fabrication-note-path-id":
+ fabNotePath.fabrication_note_path_id,
+ },
+ value: "",
+ children: [],
+ },
+ ]
+}
diff --git a/src/lib/svg-object-fns/create-svg-objects-from-pcb-fabrication-note-text.ts b/src/lib/svg-object-fns/create-svg-objects-from-pcb-fabrication-note-text.ts
new file mode 100644
index 0000000..37c5df1
--- /dev/null
+++ b/src/lib/svg-object-fns/create-svg-objects-from-pcb-fabrication-note-text.ts
@@ -0,0 +1,78 @@
+import type { PcbFabricationNoteText } from "@tscircuit/soup"
+import type { INode as SvgObject } from "svgson"
+import { toString as matrixToString } from "transformation-matrix"
+import {
+ type Matrix,
+ applyToPoint,
+ compose,
+ rotate,
+ translate,
+} from "transformation-matrix"
+
+export function createSvgObjectsFromPcbFabricationNoteText(
+ pcbFabNoteText: PcbFabricationNoteText,
+ transform: Matrix,
+): SvgObject[] {
+ const {
+ anchor_position,
+ anchor_alignment,
+ text,
+ font_size = 1,
+ layer = "top",
+ } = pcbFabNoteText
+
+ if (
+ !anchor_position ||
+ typeof anchor_position.x !== "number" ||
+ typeof anchor_position.y !== "number"
+ ) {
+ console.error("Invalid anchor_position:", anchor_position)
+ return []
+ }
+
+ const [transformedX, transformedY] = applyToPoint(transform, [
+ anchor_position.x,
+ anchor_position.y,
+ ])
+ const transformedFontSize = font_size * Math.abs(transform.a)
+
+ // Remove ${} from text value and handle undefined text
+ const cleanedText = (text || "").replace(/\$\{|\}/g, "")
+ if (!cleanedText) {
+ return []
+ }
+
+ // Create a composite transformation
+ const textTransform = compose(
+ translate(transformedX, transformedY), // TODO do anchor_alignment
+ rotate(Math.PI / 180), // Convert degrees to radians
+ )
+
+ const svgObject: SvgObject = {
+ name: "text",
+ type: "element",
+ attributes: {
+ x: "0",
+ y: "0",
+ "font-family": "Arial, sans-serif",
+ "font-size": transformedFontSize.toString(),
+ "text-anchor": "middle",
+ "dominant-baseline": "central",
+ transform: matrixToString(textTransform),
+ class: "pcb-fabrication-note-text",
+ fill: "rgba(255,255,255,0.5)",
+ },
+ children: [
+ {
+ type: "text",
+ value: cleanedText,
+ name: "",
+ attributes: {},
+ children: [],
+ },
+ ],
+ value: "",
+ }
+
+ return [svgObject]
+}
diff --git a/src/lib/svg-object-fns/create-svg-objects-from-pcb-silkscreen-path.ts b/src/lib/svg-object-fns/create-svg-objects-from-pcb-silkscreen-path.ts
new file mode 100644
index 0000000..5924c28
--- /dev/null
+++ b/src/lib/svg-object-fns/create-svg-objects-from-pcb-silkscreen-path.ts
@@ -0,0 +1,41 @@
+import type { PcbSilkscreenPath } from "@tscircuit/soup"
+import { applyToPoint, type Matrix } from "transformation-matrix"
+import type { SvgObject } from "../svg-object"
+
+export function createSvgObjectsFromPcbSilkscreenPath(
+ silkscreenPath: PcbSilkscreenPath,
+ transform: Matrix,
+): SvgObject[] {
+ if (!silkscreenPath.route || !Array.isArray(silkscreenPath.route)) return []
+
+ let path = silkscreenPath.route
+ .map((point: any, index: number) => {
+ const [x, y] = applyToPoint(transform, [point.x, point.y])
+ return index === 0 ? `M ${x} ${y}` : `L ${x} ${y}`
+ })
+ .join(" ")
+
+ // Close the path if it's not already closed
+ const firstPoint = silkscreenPath.route[0]
+ const lastPoint = silkscreenPath.route[silkscreenPath.route.length - 1]
+ if (firstPoint!.x !== lastPoint!.x || firstPoint!.y !== lastPoint!.y) {
+ path += " Z"
+ }
+ return [
+ {
+ name: "path",
+ type: "element",
+ attributes: {
+ class: `pcb-silkscreen pcb-silkscreen-${silkscreenPath.layer}`,
+ d: path,
+ "stroke-width": (
+ silkscreenPath.stroke_width * Math.abs(transform.a)
+ ).toString(),
+ "data-pcb-component-id": silkscreenPath.pcb_component_id,
+ "data-pcb-silkscreen-path-id": silkscreenPath.pcb_silkscreen_path_id,
+ },
+ value: "",
+ children: [],
+ },
+ ]
+}
diff --git a/src/lib/svg-object-fns/create-svg-objects-from-pcb-slikscreen-text.ts b/src/lib/svg-object-fns/create-svg-objects-from-pcb-silkscreen-text.ts
similarity index 86%
rename from src/lib/svg-object-fns/create-svg-objects-from-pcb-slikscreen-text.ts
rename to src/lib/svg-object-fns/create-svg-objects-from-pcb-silkscreen-text.ts
index 8b6e6ab..f3b1d7d 100644
--- a/src/lib/svg-object-fns/create-svg-objects-from-pcb-slikscreen-text.ts
+++ b/src/lib/svg-object-fns/create-svg-objects-from-pcb-silkscreen-text.ts
@@ -6,10 +6,11 @@ import {
compose,
rotate,
translate,
+ toString as matrixToString,
} from "transformation-matrix"
export function createSvgObjectsFromPcbSilkscreenText(
- PcbSilkscreenText: PcbSilkscreenText,
+ pcbSilkscreenText: PcbSilkscreenText,
transform: Matrix,
): SvgObject[] {
const {
@@ -17,7 +18,7 @@ export function createSvgObjectsFromPcbSilkscreenText(
text,
font_size = 1,
layer = "top",
- } = PcbSilkscreenText
+ } = pcbSilkscreenText
if (
!anchor_position ||
@@ -56,9 +57,9 @@ export function createSvgObjectsFromPcbSilkscreenText(
"font-size": transformedFontSize.toString(),
"text-anchor": "middle",
"dominant-baseline": "central",
- transform: `matrix(${textTransform.a} ${textTransform.b} ${textTransform.c} ${textTransform.d} ${textTransform.e} ${textTransform.f})`,
+ transform: matrixToString(textTransform),
class: `pcb-silkscreen-text pcb-silkscreen-${layer}`,
- "data-pcb-silkscreen-text-id": PcbSilkscreenText.pcb_component_id,
+ "data-pcb-silkscreen-text-id": pcbSilkscreenText.pcb_component_id,
},
children: [
{
diff --git a/src/lib/svg-object.ts b/src/lib/svg-object.ts
new file mode 100644
index 0000000..a051909
--- /dev/null
+++ b/src/lib/svg-object.ts
@@ -0,0 +1 @@
+export type { INode as SvgObject } from "svgson"
diff --git a/tests/__snapshots__/fabnotes.snap.svg b/tests/__snapshots__/fabnotes.snap.svg
new file mode 100644
index 0000000..36cba46
--- /dev/null
+++ b/tests/__snapshots__/fabnotes.snap.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/tests/fabnotes.test.tsx b/tests/fabnotes.test.tsx
new file mode 100644
index 0000000..35eb007
--- /dev/null
+++ b/tests/fabnotes.test.tsx
@@ -0,0 +1,33 @@
+import { test, expect } from "bun:test"
+import { circuitJsonToPcbSvg } from "src"
+import { Circuit } from "@tscircuit/core"
+
+test("fabrication note path and fabrication note text", () => {
+ const circuit = new Circuit()
+
+ circuit.add(
+
+
+
+ ,
+ )
+
+ const circuitJson = circuit.getCircuitJson()
+
+ const svg = circuitJsonToPcbSvg(circuitJson as any)
+
+ expect(svg).toMatchSvgSnapshot(import.meta.path)
+})
diff --git a/tsconfig.json b/tsconfig.json
index de09764..9dfeb8f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "jsx": "react",
+ "jsx": "react-jsx",
// Base Options recommended for all projects
"esModuleInterop": true,
"skipLibCheck": true,