-
Notifications
You must be signed in to change notification settings - Fork 11
Support pcb note elements in footprint generation #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -12,6 +12,11 @@ export const generateFootprintTsx = ( | |||||
| const fabricationNotePaths = su(circuitJson).pcb_fabrication_note_path.list() | ||||||
| const silkscreenTexts = su(circuitJson).pcb_silkscreen_text.list() | ||||||
| const pcbCutouts = su(circuitJson).pcb_cutout.list() | ||||||
| const noteTexts = su(circuitJson).pcb_note_text.list() | ||||||
| const noteRects = su(circuitJson).pcb_note_rect.list() | ||||||
| const notePaths = su(circuitJson).pcb_note_path.list() | ||||||
| const noteLines = su(circuitJson).pcb_note_line.list() | ||||||
| const noteDimensions = su(circuitJson).pcb_note_dimension.list() | ||||||
|
|
||||||
| const elementStrings: string[] = [] | ||||||
|
|
||||||
|
|
@@ -111,6 +116,122 @@ export const generateFootprintTsx = ( | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| for (const noteText of noteTexts) { | ||||||
| const anchorPosition = noteText.anchor_position ?? { x: 0, y: 0 } | ||||||
| const anchorAlignment = noteText.anchor_alignment ?? "center" | ||||||
| const font = noteText.font ?? "tscircuit2024" | ||||||
| const fontSize = noteText.font_size ?? 0 | ||||||
| const colorAttr = noteText.color ? ` color="${noteText.color}"` : "" | ||||||
|
|
||||||
| const rawText = String(noteText.text ?? "") | ||||||
| const escapedText = rawText.replace(/"/g, '\\"') | ||||||
|
|
||||||
| elementStrings.push( | ||||||
| `<pcbnotetext pcbX={${anchorPosition.x}} pcbY={${anchorPosition.y}} anchorAlignment="${anchorAlignment}" font="${font}" fontSize={${fontSize}} text="${escapedText}"${colorAttr} />`, | ||||||
|
Comment on lines
+119
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The conversion logic escapes only double quotes before writing Useful? React with 👍 / 👎. |
||||||
| ) | ||||||
| } | ||||||
|
|
||||||
| for (const noteRect of noteRects) { | ||||||
| const center = noteRect.center ?? { x: 0, y: 0 } | ||||||
| const width = noteRect.width ?? 0 | ||||||
| const height = noteRect.height ?? 0 | ||||||
| const strokeWidth = noteRect.stroke_width ?? 0 | ||||||
|
|
||||||
| const attrs = [ | ||||||
| `pcbX={${center.x}}`, | ||||||
| `pcbY={${center.y}}`, | ||||||
| `width={${width}}`, | ||||||
| `height={${height}}`, | ||||||
| `strokeWidth={${strokeWidth}}`, | ||||||
| ] | ||||||
|
|
||||||
| if (noteRect.is_filled !== undefined) { | ||||||
| attrs.push(`isFilled={${noteRect.is_filled}}`) | ||||||
| } | ||||||
| if (noteRect.has_stroke !== undefined) { | ||||||
| attrs.push(`hasStroke={${noteRect.has_stroke}}`) | ||||||
| } | ||||||
| if (noteRect.is_stroke_dashed !== undefined) { | ||||||
| attrs.push(`isStrokeDashed={${noteRect.is_stroke_dashed}}`) | ||||||
| } | ||||||
| if (noteRect.color !== undefined) { | ||||||
| attrs.push(`color="${noteRect.color}"`) | ||||||
| } | ||||||
|
|
||||||
| elementStrings.push(`<pcbnoterect ${attrs.join(" ")} />`) | ||||||
| } | ||||||
|
|
||||||
| for (const notePath of notePaths) { | ||||||
| const routeJson = JSON.stringify(notePath.route ?? []) | ||||||
| const attrs = [`route={${routeJson}}`] | ||||||
|
|
||||||
| if (notePath.stroke_width !== undefined) { | ||||||
| attrs.push(`strokeWidth={${notePath.stroke_width}}`) | ||||||
| } | ||||||
| if (notePath.color !== undefined) { | ||||||
| attrs.push(`color="${notePath.color}"`) | ||||||
| } | ||||||
|
|
||||||
| elementStrings.push(`<pcbnotepath ${attrs.join(" ")} />`) | ||||||
| } | ||||||
|
|
||||||
| for (const noteLine of noteLines) { | ||||||
| const attrs = [ | ||||||
| `x1={${noteLine.x1 ?? 0}}`, | ||||||
| `y1={${noteLine.y1 ?? 0}}`, | ||||||
| `x2={${noteLine.x2 ?? 0}}`, | ||||||
| `y2={${noteLine.y2 ?? 0}}`, | ||||||
| ] | ||||||
|
|
||||||
| if (noteLine.stroke_width !== undefined) { | ||||||
| attrs.push(`strokeWidth={${noteLine.stroke_width}}`) | ||||||
| } | ||||||
| if (noteLine.color !== undefined) { | ||||||
| attrs.push(`color="${noteLine.color}"`) | ||||||
| } | ||||||
| if (noteLine.is_dashed !== undefined) { | ||||||
| attrs.push(`isDashed={${noteLine.is_dashed}}`) | ||||||
| } | ||||||
|
|
||||||
| elementStrings.push(`<pcbnoteline ${attrs.join(" ")} />`) | ||||||
| } | ||||||
|
|
||||||
| for (const noteDimension of noteDimensions) { | ||||||
| const fromPoint = noteDimension.from ?? { x: 0, y: 0 } | ||||||
| const toPoint = noteDimension.to ?? { x: 0, y: 0 } | ||||||
| const font = noteDimension.font ?? "tscircuit2024" | ||||||
| const fontSize = noteDimension.font_size ?? 0 | ||||||
| const arrowSize = noteDimension.arrow_size | ||||||
| const attrs = [ | ||||||
| `from={{ x: ${fromPoint.x}, y: ${fromPoint.y} }}`, | ||||||
| `to={{ x: ${toPoint.x}, y: ${toPoint.y} }}`, | ||||||
| `font="${font}"`, | ||||||
| `fontSize={${fontSize}}`, | ||||||
| ] | ||||||
|
|
||||||
| if (arrowSize !== undefined) { | ||||||
| attrs.push(`arrowSize={${arrowSize}}`) | ||||||
| } | ||||||
|
|
||||||
| if ("offset" in noteDimension) { | ||||||
| const offsetValue = (noteDimension as { offset?: number }).offset | ||||||
| if (offsetValue !== undefined) { | ||||||
| attrs.push(`offset={${offsetValue}}`) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (noteDimension.text !== undefined) { | ||||||
| const escapedText = String(noteDimension.text).replace(/"/g, '\\"') | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same backslash escaping issue as noteText. Backslashes in dimension text are not escaped before embedding in the generated TSX string. Fix by escaping backslashes before quotes: const escapedText = String(noteDimension.text).replace(/\\/g, '\\\\').replace(/"/g, '\\"')
Suggested change
Spotted by Graphite Agent This comment came from an experimental review—please leave feedback if it was helpful/unhelpful. Learn more about experimental comments here. |
||||||
| attrs.push(`text="${escapedText}"`) | ||||||
|
Comment on lines
+223
to
+225
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The dimension loop performs the same quote-only escaping for Useful? React with 👍 / 👎. |
||||||
| } | ||||||
|
|
||||||
| if (noteDimension.color !== undefined) { | ||||||
| attrs.push(`color="${noteDimension.color}"`) | ||||||
| } | ||||||
|
|
||||||
| elementStrings.push(`<pcbnotedimension ${attrs.join(" ")} />`) | ||||||
| } | ||||||
|
|
||||||
| return ` | ||||||
| <footprint> | ||||||
| ${elementStrings.join("\n")} | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| import { expect, test } from "bun:test" | ||
| import { convertCircuitJsonToTscircuit } from "lib" | ||
| import { runTscircuitCode } from "tscircuit" | ||
|
|
||
| test("test6 support pcb notes", async () => { | ||
| const tscircuit = convertCircuitJsonToTscircuit(circuitJson as any, { | ||
| componentName: "Test6Component", | ||
| }) | ||
|
|
||
| expect(tscircuit).toMatchInlineSnapshot(` | ||
| "import { type ChipProps } from \"tscircuit\"\n export const Test6Component = (props: ChipProps) => (\n <chip\n footprint={<footprint>\n <pcbnotetext pcbX={1} pcbY={2} anchorAlignment=\"top_left\" font=\"tscircuit2024\" fontSize={1.5} text=\"Assembly\" color=\"#ff0000\" />\n <pcbnoterect pcbX={0} pcbY={0} width={3.2} height={1.6} strokeWidth={0.2} isFilled={false} hasStroke={true} isStrokeDashed={true} color=\"#00ff00\" />\n <pcbnotepath route={[{\"x\":-1,\"y\":-1},{\"x\":1,\"y\":-1},{\"x\":1,\"y\":1}]} strokeWidth={0.15} color=\"#0000ff\" />\n <pcbnoteline x1={-0.5} y1={-0.5} x2={0.5} y2={0.5} strokeWidth={0.1} color=\"#123456\" isDashed={true} />\n <pcbnotedimension from={{ x: -2, y: 0 }} to={{ x: 2, y: 0 }} font=\"tscircuit2024\" fontSize={1.2} arrowSize={0.25} text=\"4mm\" color=\"#654321\" />\n </footprint>}\n {...props}\n />\n )" | ||
| `) | ||
|
|
||
| const result = await runTscircuitCode(tscircuit) | ||
|
|
||
| expect(Array.isArray(result)).toBe(true) | ||
| expect(result).not.toHaveLength(0) | ||
| }) | ||
|
|
||
| const circuitJson = [ | ||
| { | ||
| type: "source_component", | ||
| source_component_id: "generic_0", | ||
| supplier_part_numbers: {}, | ||
| }, | ||
| { | ||
| type: "schematic_component", | ||
| schematic_component_id: "schematic_generic_component_0", | ||
| source_component_id: "generic_0", | ||
| center: { x: 0, y: 0 }, | ||
| rotation: 0, | ||
| size: { width: 0, height: 0 }, | ||
| }, | ||
| { | ||
| type: "pcb_component", | ||
| source_component_id: "generic_0", | ||
| pcb_component_id: "pcb_generic_component_0", | ||
| layer: "top", | ||
| center: { x: 0, y: 0 }, | ||
| rotation: 0, | ||
| width: 1, | ||
| height: 1, | ||
| }, | ||
| { | ||
| type: "pcb_note_text", | ||
| pcb_note_text_id: "pcb_note_text_0", | ||
| pcb_component_id: "pcb_generic_component_0", | ||
| anchor_position: { x: 1, y: 2 }, | ||
| anchor_alignment: "top_left", | ||
| font: "tscircuit2024", | ||
| font_size: 1.5, | ||
| text: "Assembly", | ||
| color: "#ff0000", | ||
| }, | ||
| { | ||
| type: "pcb_note_rect", | ||
| pcb_note_rect_id: "pcb_note_rect_0", | ||
| pcb_component_id: "pcb_generic_component_0", | ||
| center: { x: 0, y: 0 }, | ||
| width: 3.2, | ||
| height: 1.6, | ||
| stroke_width: 0.2, | ||
| is_filled: false, | ||
| has_stroke: true, | ||
| is_stroke_dashed: true, | ||
| color: "#00ff00", | ||
| }, | ||
| { | ||
| type: "pcb_note_path", | ||
| pcb_note_path_id: "pcb_note_path_0", | ||
| pcb_component_id: "pcb_generic_component_0", | ||
| route: [ | ||
| { x: -1, y: -1 }, | ||
| { x: 1, y: -1 }, | ||
| { x: 1, y: 1 }, | ||
| ], | ||
| stroke_width: 0.15, | ||
| color: "#0000ff", | ||
| }, | ||
| { | ||
| type: "pcb_note_line", | ||
| pcb_note_line_id: "pcb_note_line_0", | ||
| pcb_component_id: "pcb_generic_component_0", | ||
| x1: -0.5, | ||
| y1: -0.5, | ||
| x2: 0.5, | ||
| y2: 0.5, | ||
| stroke_width: 0.1, | ||
| color: "#123456", | ||
| is_dashed: true, | ||
| }, | ||
| { | ||
| type: "pcb_note_dimension", | ||
| pcb_note_dimension_id: "pcb_note_dimension_0", | ||
| pcb_component_id: "pcb_generic_component_0", | ||
| from: { x: -2, y: 0 }, | ||
| to: { x: 2, y: 0 }, | ||
| text: "4mm", | ||
| font: "tscircuit2024", | ||
| font_size: 1.2, | ||
| arrow_size: 0.25, | ||
| color: "#654321", | ||
| }, | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Backslashes in text are not escaped, which will break the generated TSX syntax. If the text contains a backslash (e.g.,
"test\value"), the generated code will havetext="test\value"where the backslash could escape the closing quote or create invalid escape sequences.Fix by escaping backslashes before quotes:
Spotted by Graphite Agent

Is this helpful? React 👍 or 👎 to let us know.
This comment came from an experimental review—please leave feedback if it was helpful/unhelpful. Learn more about experimental comments here.