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 @@ +hello world! \ 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,