Skip to content
44 changes: 44 additions & 0 deletions lib/generate-footprint-tsx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,19 @@ export const generateFootprintTsx = (
const platedHoles = su(circuitJson).pcb_plated_hole.list()
const smtPads = su(circuitJson).pcb_smtpad.list()
const silkscreenPaths = su(circuitJson).pcb_silkscreen_path.list()
const silkscreenCircles = su(circuitJson).pcb_silkscreen_circle.list()
const fabricationNotePaths = su(circuitJson).pcb_fabrication_note_path.list()
const silkscreenTexts = su(circuitJson).pcb_silkscreen_text.list()
// NOTE: soup-util doesn't currently expose all courtyard helpers, so we filter manually
const courtyardCircles = circuitJson.filter(
(e: any) => e?.type === "pcb_courtyard_circle",
) as any[]
const courtyardRects = circuitJson.filter(
(e: any) => e?.type === "pcb_courtyard_rect",
) as any[]
const courtyardOutlines = circuitJson.filter(
(e: any) => e?.type === "pcb_courtyard_outline",
) as any[]
const pcbCutouts = su(circuitJson).pcb_cutout.list()
const noteTexts = su(circuitJson).pcb_note_text.list()
const noteRects = su(circuitJson).pcb_note_rect.list()
Expand Down Expand Up @@ -60,6 +71,19 @@ export const generateFootprintTsx = (
)
}

for (const silkscreenCircle of silkscreenCircles) {
const pcbX = silkscreenCircle.center?.x ?? 0
const pcbY = silkscreenCircle.center?.y ?? 0
const strokeWidth =
silkscreenCircle.stroke_width !== undefined
? ` strokeWidth="${mmStr(silkscreenCircle.stroke_width)}"`
: ""

elementStrings.push(
`<silkscreencircle pcbX="${mmStr(pcbX)}" pcbY="${mmStr(pcbY)}" radius="${mmStr(silkscreenCircle.radius)}"${strokeWidth} />`,
)
}

// Map fabrication note paths to silkscreen paths in footprints
for (const fabPath of fabricationNotePaths) {
elementStrings.push(
Expand All @@ -82,6 +106,26 @@ export const generateFootprintTsx = (
)
}

// Add courtyard elements
for (const courtyardCircle of courtyardCircles) {
elementStrings.push(
`<courtyardcircle pcbX="${mmStr(courtyardCircle.center.x)}" pcbY="${mmStr(courtyardCircle.center.y)}" radius="${mmStr(courtyardCircle.radius)}" />`,
)
}
Comment on lines +110 to +117
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing null safety for courtyardCircle.center. The code directly accesses courtyardCircle.center.x and courtyardCircle.center.y without optional chaining, which will throw a runtime error if center is undefined or null.

This is inconsistent with the silkscreen circle implementation (lines 75-76) which safely uses silkscreenCircle.center?.x ?? 0.

Fix:

for (const courtyardCircle of courtyardCircles) {
  const pcbX = courtyardCircle.center?.x ?? 0
  const pcbY = courtyardCircle.center?.y ?? 0
  elementStrings.push(
    `<courtyardcircle pcbX="${mmStr(pcbX)}" pcbY="${mmStr(pcbY)}" radius="${mmStr(courtyardCircle.radius)}" />`,
  )
}
Suggested change
for (const courtyardCircle of courtyardCircles) {
elementStrings.push(
`<courtyardcircle pcbX="${mmStr(courtyardCircle.center.x)}" pcbY="${mmStr(courtyardCircle.center.y)}" radius="${mmStr(courtyardCircle.radius)}" />`,
)
}
for (const courtyardCircle of courtyardCircles) {
const pcbX = courtyardCircle.center?.x ?? 0
const pcbY = courtyardCircle.center?.y ?? 0
elementStrings.push(
`<courtyardcircle pcbX="${mmStr(pcbX)}" pcbY="${mmStr(pcbY)}" radius="${mmStr(courtyardCircle.radius)}" />`,
)
}

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.


for (const courtyardRect of courtyardRects) {
const colorAttr = courtyardRect.color ? ` color="${courtyardRect.color}"` : ""
elementStrings.push(
`<courtyardrect pcbX="${mmStr(courtyardRect.center.x)}" pcbY="${mmStr(courtyardRect.center.y)}" width="${mmStr(courtyardRect.width)}" height="${mmStr(courtyardRect.height)}"${colorAttr} />`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing null-safety checks on courtyardRect.center, width, and height properties. This is inconsistent with the defensive pattern used for circles (e.g., lines 75-76, 111-112 use optional chaining center?.x ?? 0).

If courtyardRect.center is undefined or null, this will throw: Cannot read property 'x' of undefined.

Fix:

const pcbX = courtyardRect.center?.x ?? 0
const pcbY = courtyardRect.center?.y ?? 0
const width = mmStr(courtyardRect.width ?? 0)
const height = mmStr(courtyardRect.height ?? 0)
const colorAttr = courtyardRect.color
  ? ` color="${courtyardRect.color}"`
  : ""
elementStrings.push(
  `<courtyardrect pcbX="${mmStr(pcbX)}" pcbY="${mmStr(pcbY)}" width="${width}" height="${height}"${colorAttr} />`,
)
Suggested change
`<courtyardrect pcbX="${mmStr(courtyardRect.center.x)}" pcbY="${mmStr(courtyardRect.center.y)}" width="${mmStr(courtyardRect.width)}" height="${mmStr(courtyardRect.height)}"${colorAttr} />`,
const pcbX = courtyardRect.center?.x ?? 0
const pcbY = courtyardRect.center?.y ?? 0
const width = mmStr(courtyardRect.width ?? 0)
const height = mmStr(courtyardRect.height ?? 0)
const colorAttr = courtyardRect.color
? ` color="${courtyardRect.color}"`
: ""
elementStrings.push(
`<courtyardrect pcbX="${mmStr(pcbX)}" pcbY="${mmStr(pcbY)}" width="${width}" height="${height}"${colorAttr} />`,
)

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

)
}

for (const courtyardOutline of courtyardOutlines) {
elementStrings.push(
`<courtyardoutline outline={${JSON.stringify(courtyardOutline.outline)}} />`,
)
}

// Add cutout elements
for (const cutout of pcbCutouts) {
if (cutout.shape === "rect") {
Expand Down
64 changes: 64 additions & 0 deletions tests/test7-support-courtyard-and-circles.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { expect, test } from "bun:test"
import { convertCircuitJsonToTscircuit } from "lib"

test("test7 support courtyards + silkscreen circles", async () => {
const tscircuit = convertCircuitJsonToTscircuit(circuitJson, {
componentName: "Test7Component",
})

expect(tscircuit).toContain("<silkscreencircle")
expect(tscircuit).toContain('radius="2mm"')
expect(tscircuit).toContain('strokeWidth="0.15mm"')

expect(tscircuit).toContain("<courtyardcircle")
expect(tscircuit).toContain('radius="3mm"')

expect(tscircuit).toContain("<courtyardrect")
expect(tscircuit).toContain('width="4mm"')
expect(tscircuit).toContain('height="5mm"')
expect(tscircuit).toContain('color="#ff0000"')

expect(tscircuit).toContain("<courtyardoutline")
})

const circuitJson: any = [
{
type: "pcb_silkscreen_circle",
pcb_silkscreen_circle_id: "pcb_silkscreen_circle_0",
pcb_component_id: "pcb_component_0",
layer: "top",
center: { x: 1, y: 2 },
radius: 2,
stroke_width: 0.15,
},
{
type: "pcb_courtyard_circle",
pcb_courtyard_circle_id: "pcb_courtyard_circle_0",
pcb_component_id: "pcb_component_0",
layer: "top",
center: { x: 0, y: 0 },
radius: 3,
},
{
type: "pcb_courtyard_rect",
pcb_courtyard_rect_id: "pcb_courtyard_rect_0",
pcb_component_id: "pcb_component_0",
layer: "top",
center: { x: 0, y: 0 },
width: 4,
height: 5,
color: "#ff0000",
},
{
type: "pcb_courtyard_outline",
pcb_courtyard_outline_id: "pcb_courtyard_outline_0",
pcb_component_id: "pcb_component_0",
layer: "top",
outline: [
{ x: -1, y: -1 },
{ x: 1, y: -1 },
{ x: 1, y: 1 },
{ x: -1, y: 1 },
],
},
]
Loading