Skip to content

Commit 01d7fdc

Browse files
committed
Support all PCB fabrication note elements in footprint conversion
1 parent 7f6b063 commit 01d7fdc

3 files changed

Lines changed: 294 additions & 0 deletions

File tree

lib/generate-footprint-tsx.tsx

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export const generateFootprintTsx = (
1010
const smtPads = su(circuitJson).pcb_smtpad.list()
1111
const silkscreenPaths = su(circuitJson).pcb_silkscreen_path.list()
1212
const fabricationNotePaths = su(circuitJson).pcb_fabrication_note_path.list()
13+
const fabricationNoteTexts = su(circuitJson).pcb_fabrication_note_text.list()
14+
const fabricationNoteRects = su(circuitJson).pcb_fabrication_note_rect.list()
15+
const fabricationNoteDimensions =
16+
su(circuitJson).pcb_fabrication_note_dimension.list()
1317
const silkscreenTexts = su(circuitJson).pcb_silkscreen_text.list()
1418
const pcbCutouts = su(circuitJson).pcb_cutout.list()
1519
const noteTexts = su(circuitJson).pcb_note_text.list()
@@ -75,10 +79,115 @@ export const generateFootprintTsx = (
7579
if ("color" in fabPath && fabPath.color !== undefined) {
7680
attrs.push(`color="${fabPath.color}"`)
7781
}
82+
if ("layer" in fabPath && fabPath.layer === "bottom") {
83+
attrs.push(`layer="bottom"`)
84+
}
7885

7986
elementStrings.push(`<fabricationnotepath ${attrs.join(" ")} />`)
8087
}
8188

89+
for (const fabText of fabricationNoteTexts) {
90+
const anchorPosition = fabText.anchor_position ?? { x: 0, y: 0 }
91+
const anchorAlignment = fabText.anchor_alignment ?? "center"
92+
const rawText = String(fabText.text ?? "")
93+
const escapedText = rawText.replace(/"/g, '\\"')
94+
95+
const attrs = [
96+
`pcbX={${anchorPosition.x}}`,
97+
`pcbY={${anchorPosition.y}}`,
98+
`anchorAlignment="${anchorAlignment}"`,
99+
`text="${escapedText}"`,
100+
]
101+
102+
if (fabText.font !== undefined) {
103+
attrs.push(`font="${fabText.font}"`)
104+
}
105+
if (fabText.font_size !== undefined) {
106+
attrs.push(`fontSize={${fabText.font_size}}`)
107+
}
108+
if (fabText.color !== undefined) {
109+
attrs.push(`color="${fabText.color}"`)
110+
}
111+
if (fabText.layer === "bottom") {
112+
attrs.push(`layer="bottom"`)
113+
}
114+
115+
elementStrings.push(`<fabricationnotetext ${attrs.join(" ")} />`)
116+
}
117+
118+
for (const fabRect of fabricationNoteRects) {
119+
const center = fabRect.center ?? { x: 0, y: 0 }
120+
const attrs = [
121+
`pcbX={${center.x}}`,
122+
`pcbY={${center.y}}`,
123+
`width={${fabRect.width ?? 0}}`,
124+
`height={${fabRect.height ?? 0}}`,
125+
]
126+
127+
if (fabRect.stroke_width !== undefined) {
128+
attrs.push(`strokeWidth={${fabRect.stroke_width}}`)
129+
}
130+
if (fabRect.is_filled !== undefined) {
131+
attrs.push(`isFilled={${fabRect.is_filled}}`)
132+
}
133+
if (fabRect.has_stroke !== undefined) {
134+
attrs.push(`hasStroke={${fabRect.has_stroke}}`)
135+
}
136+
if (fabRect.is_stroke_dashed !== undefined) {
137+
attrs.push(`isStrokeDashed={${fabRect.is_stroke_dashed}}`)
138+
}
139+
if (fabRect.color !== undefined) {
140+
attrs.push(`color="${fabRect.color}"`)
141+
}
142+
if ("corner_radius" in fabRect && fabRect.corner_radius !== undefined) {
143+
attrs.push(`cornerRadius={${fabRect.corner_radius}}`)
144+
}
145+
if (fabRect.layer === "bottom") {
146+
attrs.push(`layer="bottom"`)
147+
}
148+
149+
elementStrings.push(`<fabricationnoterect ${attrs.join(" ")} />`)
150+
}
151+
152+
for (const fabDimension of fabricationNoteDimensions) {
153+
const fromPoint = fabDimension.from ?? { x: 0, y: 0 }
154+
const toPoint = fabDimension.to ?? { x: 0, y: 0 }
155+
const attrs = [
156+
`from={{ x: ${fromPoint.x}, y: ${fromPoint.y} }}`,
157+
`to={{ x: ${toPoint.x}, y: ${toPoint.y} }}`,
158+
]
159+
160+
if (fabDimension.text !== undefined) {
161+
const escapedText = String(fabDimension.text).replace(/"/g, '\\"')
162+
attrs.push(`text="${escapedText}"`)
163+
}
164+
if (fabDimension.font !== undefined) {
165+
attrs.push(`font="${fabDimension.font}"`)
166+
}
167+
if (fabDimension.font_size !== undefined) {
168+
attrs.push(`fontSize={${fabDimension.font_size}}`)
169+
}
170+
if (fabDimension.color !== undefined) {
171+
attrs.push(`color="${fabDimension.color}"`)
172+
}
173+
if (fabDimension.arrow_size !== undefined) {
174+
attrs.push(`arrowSize={${fabDimension.arrow_size}}`)
175+
}
176+
if (fabDimension.offset !== undefined) {
177+
attrs.push(`offset={${fabDimension.offset}}`)
178+
} else if (
179+
"offset_distance" in fabDimension &&
180+
fabDimension.offset_distance !== undefined
181+
) {
182+
attrs.push(`offset={${fabDimension.offset_distance}}`)
183+
}
184+
if (fabDimension.layer === "bottom") {
185+
attrs.push(`layer="bottom"`)
186+
}
187+
188+
elementStrings.push(`<fabricationnotedimension ${attrs.join(" ")} />`)
189+
}
190+
82191
// Add silkscreen text elements (use pcbX/pcbY instead of anchorPosition)
83192
for (const stext of silkscreenTexts) {
84193
const pcbX = stext.anchor_position?.x ?? 0
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { expect, test } from "bun:test"
2+
import { convertCircuitJsonToTscircuit } from "lib"
3+
import { runTscircuitCode } from "tscircuit"
4+
5+
test("test10 support fabrication elements", async () => {
6+
const tscircuit = convertCircuitJsonToTscircuit(circuitJson as any, {
7+
componentName: "Test10Component",
8+
})
9+
10+
expect(tscircuit).toMatchInlineSnapshot(`
11+
"import { type ChipProps } from "tscircuit"
12+
export const Test10Component = (props: ChipProps) => (
13+
<chip
14+
footprint={<footprint>
15+
<fabricationnotepath route={[{"x":-1,"y":-1},{"x":1,"y":-1},{"x":1,"y":1}]} strokeWidth={0.15} color="#0000ff" layer="bottom" />
16+
<fabricationnotetext pcbX={1} pcbY={2} anchorAlignment="top_left" text="Assembly" font="tscircuit2024" fontSize={1.5} color="#ff0000" />
17+
<fabricationnoterect pcbX={0} pcbY={0} width={3.2} height={1.6} strokeWidth={0.2} isFilled={false} hasStroke={true} isStrokeDashed={true} color="#00ff00" cornerRadius={0.25} layer="bottom" />
18+
<fabricationnotedimension from={{ x: -2, y: 0 }} to={{ x: 2, y: 0 }} text="4mm" font="tscircuit2024" fontSize={1.2} color="#654321" arrowSize={0.25} offset={0.5} layer="bottom" />
19+
</footprint>}
20+
{...props}
21+
/>
22+
)"
23+
`)
24+
25+
const renderedCircuitJson = (await runTscircuitCode(`
26+
${tscircuit}
27+
28+
circuit.add(
29+
<board width="20mm" height="20mm">
30+
<Test10Component />
31+
</board>,
32+
)
33+
`)) as any[]
34+
35+
const fabricationElements = renderedCircuitJson.filter((elm) =>
36+
elm.type.startsWith("pcb_fabrication_note_"),
37+
)
38+
39+
expect(fabricationElements).toHaveLength(8)
40+
41+
const pathElements = fabricationElements.filter(
42+
(elm) => elm.type === "pcb_fabrication_note_path",
43+
)
44+
const textElements = fabricationElements.filter(
45+
(elm) => elm.type === "pcb_fabrication_note_text",
46+
)
47+
const rectElements = fabricationElements.filter(
48+
(elm) => elm.type === "pcb_fabrication_note_rect",
49+
)
50+
const dimensionElements = fabricationElements.filter(
51+
(elm) => elm.type === "pcb_fabrication_note_dimension",
52+
)
53+
54+
expect(pathElements).toHaveLength(2)
55+
expect(textElements).toHaveLength(2)
56+
expect(rectElements).toHaveLength(2)
57+
expect(dimensionElements).toHaveLength(2)
58+
59+
expect(pathElements[0]).toMatchObject({
60+
layer: "bottom",
61+
color: "#0000ff",
62+
stroke_width: 0.15,
63+
})
64+
expect(pathElements[1]).toMatchObject({
65+
layer: "bottom",
66+
color: "#0000ff",
67+
stroke_width: 0.15,
68+
})
69+
expect(textElements[0]).toMatchObject({
70+
layer: "top",
71+
text: "Assembly",
72+
color: "#ff0000",
73+
})
74+
expect(textElements[1]).toMatchObject({
75+
layer: "top",
76+
text: "Assembly",
77+
color: "#ff0000",
78+
})
79+
expect(rectElements[0]).toMatchObject({
80+
layer: "bottom",
81+
width: 3.2,
82+
height: 1.6,
83+
corner_radius: 0.25,
84+
})
85+
expect(rectElements[1]).toMatchObject({
86+
layer: "bottom",
87+
width: 3.2,
88+
height: 1.6,
89+
corner_radius: 0.25,
90+
})
91+
expect(dimensionElements[0]).toMatchObject({
92+
layer: "bottom",
93+
text: "4mm",
94+
arrow_size: 0.25,
95+
offset: 0.5,
96+
})
97+
expect(dimensionElements[1]).toMatchObject({
98+
layer: "bottom",
99+
text: "4mm",
100+
arrow_size: 0.25,
101+
offset: 0.5,
102+
})
103+
}, 15000)
104+
105+
const circuitJson = [
106+
{
107+
type: "source_component",
108+
source_component_id: "generic_0",
109+
supplier_part_numbers: {},
110+
},
111+
{
112+
type: "schematic_component",
113+
schematic_component_id: "schematic_generic_component_0",
114+
source_component_id: "generic_0",
115+
center: { x: 0, y: 0 },
116+
rotation: 0,
117+
size: { width: 0, height: 0 },
118+
},
119+
{
120+
type: "pcb_component",
121+
source_component_id: "generic_0",
122+
pcb_component_id: "pcb_generic_component_0",
123+
layer: "top",
124+
center: { x: 0, y: 0 },
125+
rotation: 0,
126+
width: 1,
127+
height: 1,
128+
},
129+
{
130+
type: "pcb_fabrication_note_path",
131+
pcb_fabrication_note_path_id: "pcb_fabrication_note_path_0",
132+
pcb_component_id: "pcb_generic_component_0",
133+
layer: "bottom",
134+
route: [
135+
{ x: -1, y: -1 },
136+
{ x: 1, y: -1 },
137+
{ x: 1, y: 1 },
138+
],
139+
stroke_width: 0.15,
140+
color: "#0000ff",
141+
},
142+
{
143+
type: "pcb_fabrication_note_text",
144+
pcb_fabrication_note_text_id: "pcb_fabrication_note_text_0",
145+
pcb_component_id: "pcb_generic_component_0",
146+
layer: "top",
147+
anchor_position: { x: 1, y: 2 },
148+
anchor_alignment: "top_left",
149+
font: "tscircuit2024",
150+
font_size: 1.5,
151+
text: "Assembly",
152+
color: "#ff0000",
153+
},
154+
{
155+
type: "pcb_fabrication_note_rect",
156+
pcb_fabrication_note_rect_id: "pcb_fabrication_note_rect_0",
157+
pcb_component_id: "pcb_generic_component_0",
158+
layer: "bottom",
159+
center: { x: 0, y: 0 },
160+
width: 3.2,
161+
height: 1.6,
162+
stroke_width: 0.2,
163+
corner_radius: 0.25,
164+
is_filled: false,
165+
has_stroke: true,
166+
is_stroke_dashed: true,
167+
color: "#00ff00",
168+
},
169+
{
170+
type: "pcb_fabrication_note_dimension",
171+
pcb_fabrication_note_dimension_id: "pcb_fabrication_note_dimension_0",
172+
pcb_component_id: "pcb_generic_component_0",
173+
layer: "bottom",
174+
from: { x: -2, y: 0 },
175+
to: { x: 2, y: 0 },
176+
text: "4mm",
177+
font: "tscircuit2024",
178+
font_size: 1.2,
179+
arrow_size: 0.25,
180+
offset: 0.5,
181+
color: "#654321",
182+
},
183+
]

tests/test3-kicad-mod-circuit-json-example.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ test("test3 kicad mod circuit json example", async () => {
7474
<silkscreenpath route={[{"x":-6.985,"y":-10.414},{"x":-7.937499698293818,"y":-10.158777964987209},{"x":-8.63477796498721,"y":-9.461499698293817},{"x":-8.89,"y":-8.509}]} />
7575
<silkscreenpath route={[{"x":6.985,"y":10.541},{"x":7.937499698293818,"y":10.285777964987211},{"x":8.63477796498721,"y":9.588499698293818},{"x":8.889999999999999,"y":8.636}]} />
7676
<silkscreenpath route={[{"x":8.89,"y":-8.509},{"x":8.63477796498721,"y":-9.461499698293817},{"x":7.937499698293818,"y":-10.158777964987209},{"x":6.985,"y":-10.413999999999998}]} />
77+
<fabricationnotetext pcbX={-8} pcbY={11.5} anchorAlignment="center" text="REF**" font="tscircuit2024" fontSize={1.27} />
78+
<fabricationnotetext pcbX={0} pcbY={0} anchorAlignment="center" text="XIAO-Add-On" font="tscircuit2024" fontSize={1.27} />
7779
</footprint>}
7880
{...props}
7981
/>

0 commit comments

Comments
 (0)