Skip to content

Commit

Permalink
added support for silkscreen text (#27)
Browse files Browse the repository at this point in the history
* added support for silkscreen text

* cleanup
  • Loading branch information
ShiboSoftwareDev authored Nov 6, 2024
1 parent c657c0a commit 2d7a8ee
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 7 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"circuit-json": "*"
},
"dependencies": {
"@tscircuit/alphabet": "^0.0.2",
"fast-json-stable-stringify": "^2.1.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
PcbHole,
PcbSolderPaste,
PcbSilkscreenPath,
PcbSilkscreenText,
} from "circuit-json"
import stableStringify from "fast-json-stable-stringify"
import type { AnyGerberCommand } from "../any_gerber_command"
Expand Down Expand Up @@ -116,6 +117,18 @@ export const getApertureConfigFromPcbSilkscreenPath = (
throw new Error(`Provide stroke_width for: ${elm as any}`)
}

export const getApertureConfigFromPcbSilkscreenText = (
elm: PcbSilkscreenText,
): ApertureTemplateConfig => {
if ("font_size" in elm) {
return {
standard_template_code: "C",
diameter: elm.font_size / 4, // font size and diamater have different units of measurement
}
}
throw new Error(`Provide font_size for: ${elm as any}`)
}

export const getApertureConfigFromPcbSolderPaste = (
elm: PcbSolderPaste,
): ApertureTemplateConfig => {
Expand Down Expand Up @@ -242,6 +255,8 @@ function getAllApertureTemplateConfigsForLayer(
addConfigIfNew(getApertureConfigFromPcbVia(elm))
} else if (elm.type === "pcb_silkscreen_path")
addConfigIfNew(getApertureConfigFromPcbSilkscreenPath(elm))
else if (elm.type === "pcb_silkscreen_text")
addConfigIfNew(getApertureConfigFromPcbSilkscreenText(elm))
}

return configs
Expand Down
68 changes: 68 additions & 0 deletions src/gerber/convert-soup-to-gerber-commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import {
getApertureConfigFromCirclePcbHole,
getApertureConfigFromPcbPlatedHole,
getApertureConfigFromPcbSilkscreenPath,
getApertureConfigFromPcbSilkscreenText,
getApertureConfigFromPcbSmtpad,
getApertureConfigFromPcbSolderPaste,
getApertureConfigFromPcbVia,
} from "./defineAperturesForLayer"
import { findApertureNumber } from "./findApertureNumber"
import { getCommandHeaders } from "./getCommandHeaders"
import { getGerberLayerName } from "./getGerberLayerName"
import { lineAlphabet } from "@tscircuit/alphabet"

/**
* Converts tscircuit soup to arrays of Gerber commands for each layer
Expand Down Expand Up @@ -146,6 +148,72 @@ export const convertSoupToGerberCommands = (
})
}

glayer.push(...gerber.build())
}
} else if (element.type === "pcb_silkscreen_text") {
if (element.layer === layer) {
const glayer = glayers[getGerberLayerName(layer, "silkscreen")]
const apertureConfig = getApertureConfigFromPcbSilkscreenText(element)
const gerber = gerberBuilder().add("select_aperture", {
aperture_number: findApertureNumber(glayer, apertureConfig),
})

let initialX = element.anchor_position.x
let initialY = element.anchor_position.y
const fontSize = element.font_size
const letterSpacing = fontSize * 0.4
const spaceWidth = fontSize * 0.5

const textWidth =
element.text.split("").reduce((width, char) => {
if (char === " ") {
return width + spaceWidth + letterSpacing
}
return width + fontSize + letterSpacing
}, 0) - letterSpacing

const textHeight = fontSize
switch (element.anchor_alignment || "center") {
case "top_right":
// No adjustment needed
break
case "top_left":
initialX -= textWidth
break
case "bottom_right":
initialY -= textHeight
break
case "bottom_left":
initialX -= textWidth
initialY -= textHeight
break
case "center":
initialX -= textWidth / 2
initialY -= textHeight / 2
break
}

let anchoredX = initialX
const anchoredY = initialY
for (const char of element.text.toUpperCase()) {
if (char === " ") {
anchoredX += spaceWidth + letterSpacing
continue
}

const letterPaths = lineAlphabet[char] || []
for (const path of letterPaths) {
const x1 = anchoredX + path.x1 * fontSize
const y1 = anchoredY + path.y1 * fontSize
const x2 = anchoredX + path.x2 * fontSize
const y2 = anchoredY + path.y2 * fontSize

gerber.add("move_operation", { x: x1, y: mfy(y1) })
gerber.add("plot_operation", { x: x2, y: mfy(y2) })
}

anchoredX += fontSize + letterSpacing // Move to next character position
}
glayer.push(...gerber.build())
}
} else if (element.type === "pcb_smtpad") {
Expand Down
7 changes: 7 additions & 0 deletions tests/gerber/__snapshots__/silkscreen-text-bottom.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions tests/gerber/__snapshots__/silkscreen-text-top.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 7 additions & 7 deletions tests/gerber/__snapshots__/simple1-top.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
121 changes: 121 additions & 0 deletions tests/gerber/generate-board-with-slikscreen-text.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { test, expect } from "bun:test"
import { convertSoupToGerberCommands } from "src/gerber/convert-soup-to-gerber-commands"
import {
stringifyGerberCommandLayers,
stringifyGerberCommands,
} from "src/gerber/stringify-gerber"
import { maybeOutputGerber } from "tests/fixtures/maybe-output-gerber"
import gerberToSvg from "gerber-to-svg"
import type { AnyCircuitElement } from "circuit-json"
import {
convertSoupToExcellonDrillCommands,
stringifyExcellonDrill,
} from "src/excellon-drill"
import { Circuit } from "@tscircuit/core"

test("Generate simple board with a multi-layer trace", async () => {
const circuit = new Circuit()
circuit.add(
<board
width={160}
height={40}
outline={[
{ x: -80, y: -5 },
{ x: -80, y: 20 },
{ x: 30, y: 20 },
{ x: 30, y: -20 },
{ x: 20, y: -5 },
]}
>
<platedhole
name="H1"
shape="circle"
holeDiameter={1}
outerDiameter={2}
pcbX={27}
pcbY={-12}
/>
<hole name="H1" diameter={0.2} pcbX={-30} pcbY={15} />
<hole name="H1" diameter={0.2} pcbX={-30} pcbY={12} />

<silkscreentext
text="large gerber text**"
fontSize={4}
layer={"top"}
pcbX={-20}
pcbY={0}
/>
<silkscreentext
text="medium gerber text."
fontSize={2}
layer={"top"}
pcbX={-30}
pcbY={5}
/>
<silkscreentext
text="small gerber text"
fontSize={1}
layer={"top"}
pcbX={-30}
pcbY={10}
/>
<silkscreentext
text="][[]][><][[]]["
fontSize={0.5}
anchorAlignment="center"
layer={"top"}
pcbX={-30}
pcbY={12}
/>
<silkscreentext
text="bottom left[]"
fontSize={0.5}
anchorAlignment="bottom_left"
layer={"top"}
pcbX={-30}
pcbY={15}
/>
<silkscreentext
text="[]bottom right"
fontSize={0.5}
anchorAlignment="bottom_right"
layer={"top"}
pcbX={-30}
pcbY={15}
/>
<silkscreentext
text="top left[]"
fontSize={0.5}
anchorAlignment="top_left"
layer={"top"}
pcbX={-30}
pcbY={15}
/>
<silkscreentext
text="[]top right"
fontSize={0.5}
anchorAlignment="top_right"
layer={"top"}
pcbX={-30}
pcbY={15}
/>
</board>,
)
const soup = circuit.getCircuitJson()

const gerber_cmds = convertSoupToGerberCommands(soup)
const excellon_drill_cmds = convertSoupToExcellonDrillCommands({
circuitJson: soup,
is_plated: true,
})

const gerberOutput = stringifyGerberCommandLayers(gerber_cmds)
const excellonDrillOutput = stringifyExcellonDrill(excellon_drill_cmds)

await maybeOutputGerber(gerberOutput, excellonDrillOutput)

expect(gerberOutput).toMatchGerberSnapshot(
import.meta.path,
"silkscreen-text",
)
})

0 comments on commit 2d7a8ee

Please sign in to comment.