Skip to content

Commit

Permalink
feat: silkscreen text
Browse files Browse the repository at this point in the history
  • Loading branch information
Abse2001 committed Dec 14, 2024
1 parent db14e17 commit 23b2735
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ dist-ssr
.aider*
.vercel
.env

.yalc
yalc.lock
115 changes: 113 additions & 2 deletions src/soup-to-3d/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Geom3 } from "@jscad/modeling/src/geometries/types"
import { vectorText } from "@jscad/modeling/src/text"
import type { AnySoupElement, PCBPlatedHole } from "@tscircuit/soup"
import { su } from "@tscircuit/soup-util"
import { translate } from "@jscad/modeling/src/operations/transforms"
Expand All @@ -10,7 +11,7 @@ import { M, colors } from "../geoms/constants"
import { extrudeLinear } from "@jscad/modeling/src/operations/extrusions"
import { expand } from "@jscad/modeling/src/operations/expansions"
import { createBoardWithOutline } from "src/geoms/create-board-with-outline"

import { Vec2 } from "@jscad/modeling/src/maths/types"
export const createBoardGeomFromSoup = (soup: AnySoupElement[]): Geom3[] => {
const board = su(soup).pcb_board.list()[0]
if (!board) {
Expand All @@ -21,6 +22,7 @@ export const createBoardGeomFromSoup = (soup: AnySoupElement[]): Geom3[] => {
const pads = su(soup).pcb_smtpad.list()
const traces = su(soup).pcb_trace.list()
const pcb_vias = su(soup).pcb_via.list()
const silkscreenTexts = su(soup).pcb_silkscreen_text.list()

// PCB Board
let boardGeom: Geom3
Expand Down Expand Up @@ -219,9 +221,118 @@ export const createBoardGeomFromSoup = (soup: AnySoupElement[]): Geom3[] => {
pcb_plated_hole_id: "",
})
}
// Add silkscreen text
const silkscreenGeoms: Geom3[] = []
for (const silkscreenText of silkscreenTexts) {
// Generate 2D text outlines
const textOutlines = vectorText({
height: silkscreenText.font_size,
input: silkscreenText.text,
})
// Split number 8 and small e into two parts to fix visual issues
textOutlines.forEach((outline) => {
if (outline.length === 29) {
textOutlines.splice(
textOutlines.indexOf(outline),
1,
outline.slice(0, 15),
)
textOutlines.splice(
textOutlines.indexOf(outline),
0,
outline.slice(14, 29),
)
} else if (outline.length === 17) {
textOutlines.splice(
textOutlines.indexOf(outline),
1,
outline.slice(0, 10),
)
textOutlines.splice(
textOutlines.indexOf(outline),
0,
outline.slice(9, 17),
)
}
})
// Calculate text bounds and center point
const points = textOutlines.flatMap((o) => o)
const textBounds = {
minX: Math.min(...points.map((p) => p[0])),
maxX: Math.max(...points.map((p) => p[0])),
minY: Math.min(...points.map((p) => p[1])),
maxY: Math.max(...points.map((p) => p[1])),
}
const textWidth = textBounds.maxX - textBounds.minX
const textHeight = textBounds.maxY - textBounds.minY
const centerX = (textBounds.minX + textBounds.maxX) / 2
const centerY = (textBounds.minY + textBounds.maxY) / 2

// Calculate offset based on anchor alignment
let xOffset = -centerX
let yOffset = -centerY

// Adjust for specific alignments
if (silkscreenText.anchor_alignment?.includes("right")) {
xOffset = -textBounds.maxX
} else if (silkscreenText.anchor_alignment?.includes("left")) {
xOffset = -textBounds.minX
}

if (silkscreenText.anchor_alignment?.includes("top")) {
yOffset = -textBounds.maxY
} else if (silkscreenText.anchor_alignment?.includes("bottom")) {
yOffset = -textBounds.minY
}
// Bottom alignment is default (yOffset = 0)

for (let outline of textOutlines) {
// Create path from outline points with alignment offset

const alignedOutline = outline.map((point) => [
point[0] + xOffset + silkscreenText.anchor_position.x,
point[1] + yOffset + silkscreenText.anchor_position.y,
]) as Vec2[]
const textPath = line(alignedOutline)

// Scale expansion delta with font size
const fontSize = silkscreenText.font_size || 0.25
const expansionDelta = Math.min(
Math.max(0.01, fontSize * 0.1),
fontSize * 0.2, // Maximum cap scales with font size
)
const expandedPath = expand(
{
delta: expansionDelta,
corners: "round",
},
textPath,
)

// Extrude and position the text with smaller height
let textGeom = translate(
[0, 0, 0.6], // Position above board
extrudeLinear(
{ height: 0.012 }, // Thinner extrusion
expandedPath,
),
)

// Color white like silkscreen
textGeom = colorize([256, 256, 256], textGeom)

silkscreenGeoms.push(textGeom)
}
}

// Colorize to a PCB green color: #05A32E
boardGeom = colorize(colors.fr4Green, boardGeom)

return [boardGeom, ...platedHoleGeoms, ...padGeoms, ...traceGeoms]
return [
boardGeom,
...platedHoleGeoms,
...padGeoms,
...traceGeoms,
...silkscreenGeoms,
]
}
70 changes: 70 additions & 0 deletions stories/SilkscreenText/SilkscreenText.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { CadViewer } from "src/CadViewer"
import { Circuit } from "@tscircuit/core"

const createCircuit = () => {
const circuit = new Circuit()

circuit.add(
<board width="10mm" height="10mm">
<footprint>
<silkscreentext
text="bottom_L"
pcbX={0}
pcbY={0}
anchorAlignment="bottom_left"
pcbRotation={0}
layer="top"
fontSize={0.25}
/>
<silkscreentext
text="top_L"
pcbX={0}
pcbY={0}
anchorAlignment="top_left"
pcbRotation={0}
layer="top"
fontSize={0.25}
/>
<silkscreentext
text="bottom_R"
pcbX={0}
pcbY={0}
anchorAlignment="bottom_right"
pcbRotation={0}
layer="top"
fontSize={0.25}
/>
<silkscreentext
text="top_R"
pcbX={0}
pcbY={0}
anchorAlignment="top_right"
pcbRotation={0}
layer="top"
fontSize={0.25}
/>
<silkscreentext
text="center"
pcbX={0}
pcbY={1}
anchorAlignment="center"
pcbRotation={0}
layer="bottom"
fontSize={0.25}
/>
</footprint>
</board>,
)

return circuit.getCircuitJson()
}

export const SilkscreenText = () => {
const circuitJson = createCircuit()
return <CadViewer soup={circuitJson as any} />
}

export default {
title: "Silkscreen Text",
component: SilkscreenText,
}
41 changes: 41 additions & 0 deletions stories/SilkscreenText/SilkscreenTextAlphabet.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CadViewer } from "src/CadViewer"
import { Circuit } from "@tscircuit/core"

const createCircuit = () => {
const circuit = new Circuit()

circuit.add(
<board width="10mm" height="10mm">
<footprint>
<silkscreentext
text="abcdefghijklmnopqrstuvwxyz"
pcbX={0}
pcbY={0}
pcbRotation={0}
layer="top"
fontSize={0.25}
/>
<silkscreentext
text="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
pcbX={0}
pcbY={2}
pcbRotation={0}
fontSize={0.25}
layer="top"
/>
</footprint>
</board>,
)

return circuit.getCircuitJson()
}

export const SilkscreenTextAlphabet = () => {
const circuitJson = createCircuit()
return <CadViewer soup={circuitJson as any} />
}

export default {
title: "Silkscreen Text",
component: SilkscreenTextAlphabet,
}
26 changes: 26 additions & 0 deletions stories/SilkscreenText/SilkscreenTextCenter.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { CadViewer } from "src/CadViewer"
import { Circuit, SilkscreenText } from "@tscircuit/core"

const createCircuit = () => {
const circuit = new Circuit()

circuit.add(
<board width="20mm" height="20mm">
<footprint>
<silkscreentext text="A" />
</footprint>
</board>,
)

return circuit.getCircuitJson()
}

export const SilkscreenTextCenter = () => {
const circuitJson = createCircuit()
return <CadViewer soup={circuitJson as any} />
}

export default {
title: "Silkscreen Text",
component: SilkscreenTextCenter,
}
32 changes: 32 additions & 0 deletions stories/SilkscreenText/SilkscreenTextNumbers.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { CadViewer } from "src/CadViewer"
import { Circuit } from "@tscircuit/core"

const createCircuit = () => {
const circuit = new Circuit()

circuit.add(
<board width="10mm" height="10mm">
<footprint>
<silkscreentext
text="1234567890"
pcbX={0}
pcbY={0}
fontSize={0.25}
layer="top"
/>
</footprint>
</board>,
)

return circuit.getCircuitJson()
}

export const SilkscreenTextNumbers = () => {
const circuitJson = createCircuit()
return <CadViewer soup={circuitJson as any} />
}

export default {
title: "Silkscreen Text",
component: SilkscreenTextNumbers,
}
56 changes: 56 additions & 0 deletions stories/SilkscreenText/SilkscreenTextOnTraces.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { CadViewer } from "src/CadViewer"
import { Circuit, SilkscreenText } from "@tscircuit/core"

const createCircuit = () => {
const circuit = new Circuit()

circuit.add(
<board width="20mm" height="20mm">
<resistor
name="R1"
footprint="0805"
resistance="10k"
pcbX={10}
pcbY={0}
/>
<resistor
name="R2"
footprint="0805"
resistance="10k"
pcbX={-10}
pcbY={0}
/>
<resistor
name="R3"
footprint="0805"
resistance="10k"
pcbX={0}
pcbY={-10}
/>
<resistor
name="R4"
footprint="0805"
resistance="10k"
pcbX={0}
pcbY={10}
/>
<trace from={".R1 > .right"} to={".R2 > .left"} />
<trace from={".R4 > .right"} to={".R3 > .left"} />
<footprint>
<silkscreentext text="Tscircuit" pcbX={0} pcbY={1.6} />
</footprint>
</board>,
)

return circuit.getCircuitJson()
}

export const SilkscreenTextOnTraces = () => {
const circuitJson = createCircuit()
return <CadViewer soup={circuitJson as any} />
}

export default {
title: "Silkscreen Text",
component: SilkscreenTextOnTraces,
}

0 comments on commit 23b2735

Please sign in to comment.