Skip to content

Commit

Permalink
Merge pull request #63 from tscircuit/fabnotes
Browse files Browse the repository at this point in the history
fabrication note path and text implementation
  • Loading branch information
seveibar authored Sep 11, 2024
2 parents 4f17664 + ccf1da8 commit 7f3e64d
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 39 deletions.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
43 changes: 9 additions & 34 deletions src/lib/circuit-to-pcb-svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 []
}
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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: [],
},
]
}
Original file line number Diff line number Diff line change
@@ -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]
}
Original file line number Diff line number Diff line change
@@ -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: [],
},
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ import {
compose,
rotate,
translate,
toString as matrixToString,
} from "transformation-matrix"

export function createSvgObjectsFromPcbSilkscreenText(
PcbSilkscreenText: PcbSilkscreenText,
pcbSilkscreenText: PcbSilkscreenText,
transform: Matrix,
): SvgObject[] {
const {
anchor_position,
text,
font_size = 1,
layer = "top",
} = PcbSilkscreenText
} = pcbSilkscreenText

if (
!anchor_position ||
Expand Down Expand Up @@ -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: [
{
Expand Down
1 change: 1 addition & 0 deletions src/lib/svg-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { INode as SvgObject } from "svgson"
12 changes: 12 additions & 0 deletions tests/__snapshots__/fabnotes.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions tests/fabnotes.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<board width="10mm" height="10mm">
<fabricationnotepath
route={[
{ x: 0, y: 0 },
{ x: 10, y: 0 },
{
x: 10,
y: 10,
},
{
x: 0,
y: 0,
},
]}
/>
<fabricationnotetext anchorAlignment="bottom_left" text="hello world!" />
</board>,
)

const circuitJson = circuit.getCircuitJson()

const svg = circuitJsonToPcbSvg(circuitJson as any)

expect(svg).toMatchSvgSnapshot(import.meta.path)
})
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"jsx": "react",
"jsx": "react-jsx",
// Base Options recommended for all projects
"esModuleInterop": true,
"skipLibCheck": true,
Expand Down

0 comments on commit 7f3e64d

Please sign in to comment.