circuit-json-to-tscircuit 0.0.40 → 0.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/dist/{chunk-UHPKLBYL.js → chunk-UXTI3RT6.js} +62 -92
  2. package/dist/cli/main.js +2 -2
  3. package/dist/lib/index.js +1 -1
  4. package/lib/generate-footprint-tsx/convert-copper-text.ts +1 -1
  5. package/lib/generate-footprint-tsx/convert-courtyard.ts +4 -0
  6. package/lib/generate-footprint-tsx/convert-cutouts.ts +1 -1
  7. package/lib/generate-footprint-tsx/convert-fabrication.ts +1 -1
  8. package/lib/generate-footprint-tsx/convert-holes.ts +1 -1
  9. package/lib/generate-footprint-tsx/convert-keepouts.ts +1 -1
  10. package/lib/generate-footprint-tsx/convert-notes.ts +1 -1
  11. package/lib/generate-footprint-tsx/convert-plated-holes.ts +3 -5
  12. package/lib/generate-footprint-tsx/convert-silkscreen-text.ts +1 -1
  13. package/lib/generate-footprint-tsx/convert-smt-pads.ts +13 -65
  14. package/lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/escape-jsx-text.ts +2 -0
  15. package/lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-mm.ts +3 -0
  16. package/lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-optional-mm-attr.ts +9 -0
  17. package/lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-pcb-rotation-attr.ts +8 -0
  18. package/lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-solder-mask-attrs.ts +14 -0
  19. package/package.json +1 -1
  20. package/tests/__snapshots__/test16-support-pcb-smtpad-shapes-pcb.snap.svg +1 -1
  21. package/tests/__snapshots__/test8-support-courtyard-courtyard-pcb.snap.svg +1 -1
  22. package/tests/test16-support-pcb-smtpad-shapes.test.tsx +30 -1
  23. package/tests/test8-support-courtyard.test.tsx +31 -10
  24. package/lib/generate-footprint-tsx/helpers.ts +0 -33
@@ -1,24 +1,12 @@
1
1
  // lib/get-board-using-template.ts
2
- import { mmStr as mmStr6 } from "@tscircuit/mm";
2
+ import { mmStr as mmStr7 } from "@tscircuit/mm";
3
3
  import { su as su12 } from "@tscircuit/soup-util";
4
4
 
5
5
  // lib/generate-footprint-tsx/convert-copper-text.ts
6
6
  import { su } from "@tscircuit/soup-util";
7
7
 
8
- // lib/generate-footprint-tsx/helpers.ts
9
- import { mmStr } from "@tscircuit/mm";
8
+ // lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/escape-jsx-text.ts
10
9
  var escapeJsxText = (value) => String(value ?? "").replace(/"/g, '\\"');
11
- var formatMm = (value) => mmStr(value ?? 0);
12
- var formatOptionalMmAttr = (attrName, value) => {
13
- if (value === void 0) return "";
14
- return ` ${attrName}="${mmStr(value)}"`;
15
- };
16
- var formatSolderMaskAttrs = (platedHole) => `${platedHole.is_covered_with_solder_mask === void 0 ? "" : ` coveredWithSolderMask={${platedHole.is_covered_with_solder_mask}}`}${formatOptionalMmAttr("solderMaskMargin", platedHole.soldermask_margin)}`;
17
- var formatPcbRotationAttr = (rotation, attrName = "pcbRotation") => {
18
- if (rotation === void 0) return "";
19
- const formatted = typeof rotation === "number" ? `${rotation}deg` : rotation;
20
- return ` ${attrName}="${formatted}"`;
21
- };
22
10
 
23
11
  // lib/generate-footprint-tsx/convert-copper-text.ts
24
12
  var convertCopperText = (circuitJson) => {
@@ -57,6 +45,15 @@ var convertCopperText = (circuitJson) => {
57
45
 
58
46
  // lib/generate-footprint-tsx/convert-courtyard.ts
59
47
  import { su as su2 } from "@tscircuit/soup-util";
48
+
49
+ // lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-pcb-rotation-attr.ts
50
+ var formatPcbRotationAttr = (rotation, attrName = "pcbRotation") => {
51
+ if (rotation === void 0) return "";
52
+ const formatted = typeof rotation === "number" ? `${rotation}deg` : rotation;
53
+ return ` ${attrName}="${formatted}"`;
54
+ };
55
+
56
+ // lib/generate-footprint-tsx/convert-courtyard.ts
60
57
  var convertCourtyard = (circuitJson) => {
61
58
  const courtyardOutlines = su2(circuitJson).pcb_courtyard_outline.list();
62
59
  const courtyardRects = su2(circuitJson).pcb_courtyard_rect.list();
@@ -81,6 +78,9 @@ var convertCourtyard = (circuitJson) => {
81
78
  if (courtyardRect.layer !== void 0) {
82
79
  attrs.push(`layer="${courtyardRect.layer}"`);
83
80
  }
81
+ if (courtyardRect.ccw_rotation !== void 0) {
82
+ attrs.push(formatPcbRotationAttr(courtyardRect.ccw_rotation).trimStart());
83
+ }
84
84
  elementStrings.push(`<courtyardrect ${attrs.join(" ")} />`);
85
85
  }
86
86
  for (const courtyardCircle of courtyardCircles) {
@@ -99,6 +99,12 @@ var convertCourtyard = (circuitJson) => {
99
99
 
100
100
  // lib/generate-footprint-tsx/convert-cutouts.ts
101
101
  import { su as su3 } from "@tscircuit/soup-util";
102
+
103
+ // lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-mm.ts
104
+ import { mmStr } from "@tscircuit/mm";
105
+ var formatMm = (value) => mmStr(value ?? 0);
106
+
107
+ // lib/generate-footprint-tsx/convert-cutouts.ts
102
108
  var convertCutouts = (circuitJson) => {
103
109
  const pcbCutouts = su3(circuitJson).pcb_cutout.list();
104
110
  const elementStrings = [];
@@ -379,33 +385,51 @@ var convertNotes = (circuitJson) => {
379
385
 
380
386
  // lib/generate-footprint-tsx/convert-plated-holes.ts
381
387
  import { su as su8 } from "@tscircuit/soup-util";
388
+ import { mmStr as mmStr4 } from "@tscircuit/mm";
389
+
390
+ // lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-optional-mm-attr.ts
382
391
  import { mmStr as mmStr3 } from "@tscircuit/mm";
392
+ var formatOptionalMmAttr = (attrName, value) => {
393
+ if (value === void 0) return "";
394
+ return ` ${attrName}="${mmStr3(value)}"`;
395
+ };
396
+
397
+ // lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-solder-mask-attrs.ts
398
+ var formatSolderMaskAttrs = (platedHole) => {
399
+ let coveredWithSolderMaskAttr = "";
400
+ if (platedHole.is_covered_with_solder_mask !== void 0) {
401
+ coveredWithSolderMaskAttr = ` coveredWithSolderMask={${platedHole.is_covered_with_solder_mask}}`;
402
+ }
403
+ return `${coveredWithSolderMaskAttr}${formatOptionalMmAttr("solderMaskMargin", platedHole.soldermask_margin)}`;
404
+ };
405
+
406
+ // lib/generate-footprint-tsx/convert-plated-holes.ts
383
407
  var convertPlatedHoles = (circuitJson) => {
384
408
  const platedHoles = su8(circuitJson).pcb_plated_hole.list();
385
409
  const elementStrings = [];
386
410
  for (const platedHole of platedHoles) {
387
411
  if (platedHole.shape === "oval" || platedHole.shape === "pill") {
388
412
  elementStrings.push(
389
- `<platedhole portHints={${JSON.stringify(platedHole.port_hints)}} pcbX="${mmStr3(platedHole.x)}" pcbY="${mmStr3(platedHole.y)}"${formatSolderMaskAttrs(platedHole)} outerHeight="${mmStr3(platedHole.outer_height)}" outerWidth="${mmStr3(platedHole.outer_width)}" holeHeight="${mmStr3(platedHole.hole_height)}" holeWidth="${mmStr3(platedHole.hole_width)}" height="${mmStr3(platedHole.hole_height)}" shape="${platedHole.shape}"${formatPcbRotationAttr("ccw_rotation" in platedHole ? platedHole.ccw_rotation : void 0)} />`
413
+ `<platedhole portHints={${JSON.stringify(platedHole.port_hints)}} pcbX="${mmStr4(platedHole.x)}" pcbY="${mmStr4(platedHole.y)}"${formatSolderMaskAttrs(platedHole)} outerHeight="${mmStr4(platedHole.outer_height)}" outerWidth="${mmStr4(platedHole.outer_width)}" holeHeight="${mmStr4(platedHole.hole_height)}" holeWidth="${mmStr4(platedHole.hole_width)}" height="${mmStr4(platedHole.hole_height)}" shape="${platedHole.shape}"${formatPcbRotationAttr("ccw_rotation" in platedHole ? platedHole.ccw_rotation : void 0)} />`
390
414
  );
391
415
  } else if (platedHole.shape === "circle") {
392
416
  elementStrings.push(
393
- `<platedhole portHints={${JSON.stringify(platedHole.port_hints)}} pcbX="${mmStr3(platedHole.x)}" pcbY="${mmStr3(platedHole.y)}"${formatSolderMaskAttrs(platedHole)} outerDiameter="${mmStr3(platedHole.outer_diameter)}" holeDiameter="${mmStr3(platedHole.hole_diameter)}" shape="circle" />`
417
+ `<platedhole portHints={${JSON.stringify(platedHole.port_hints)}} pcbX="${mmStr4(platedHole.x)}" pcbY="${mmStr4(platedHole.y)}"${formatSolderMaskAttrs(platedHole)} outerDiameter="${mmStr4(platedHole.outer_diameter)}" holeDiameter="${mmStr4(platedHole.hole_diameter)}" shape="circle" />`
394
418
  );
395
419
  } else if (platedHole.shape === "circular_hole_with_rect_pad") {
396
420
  elementStrings.push(
397
- `<platedhole portHints={${JSON.stringify(platedHole.port_hints)}} pcbX="${mmStr3(platedHole.x)}" pcbY="${mmStr3(platedHole.y)}"${platedHole.hole_shape === void 0 ? "" : ` holeShape="${platedHole.hole_shape}"`}${platedHole.pad_shape === void 0 ? "" : ` padShape="${platedHole.pad_shape}"`}${formatSolderMaskAttrs(platedHole)} holeDiameter="${mmStr3(platedHole.hole_diameter)}" rectPadWidth="${mmStr3(platedHole.rect_pad_width)}" rectPadHeight="${mmStr3(platedHole.rect_pad_height)}"${formatOptionalMmAttr("rectBorderRadius", platedHole.rect_border_radius)}${formatOptionalMmAttr("holeOffsetX", platedHole.hole_offset_x)}${formatOptionalMmAttr("holeOffsetY", platedHole.hole_offset_y)}${formatPcbRotationAttr(platedHole.rect_ccw_rotation)} shape="circular_hole_with_rect_pad" />`
421
+ `<platedhole portHints={${JSON.stringify(platedHole.port_hints)}} pcbX="${mmStr4(platedHole.x)}" pcbY="${mmStr4(platedHole.y)}"${platedHole.hole_shape === void 0 ? "" : ` holeShape="${platedHole.hole_shape}"`}${platedHole.pad_shape === void 0 ? "" : ` padShape="${platedHole.pad_shape}"`}${formatSolderMaskAttrs(platedHole)} holeDiameter="${mmStr4(platedHole.hole_diameter)}" rectPadWidth="${mmStr4(platedHole.rect_pad_width)}" rectPadHeight="${mmStr4(platedHole.rect_pad_height)}"${formatOptionalMmAttr("rectBorderRadius", platedHole.rect_border_radius)}${formatOptionalMmAttr("holeOffsetX", platedHole.hole_offset_x)}${formatOptionalMmAttr("holeOffsetY", platedHole.hole_offset_y)}${formatPcbRotationAttr(platedHole.rect_ccw_rotation)} shape="circular_hole_with_rect_pad" />`
398
422
  );
399
423
  } else if (platedHole.shape === "pill_hole_with_rect_pad" || platedHole.shape === "rotated_pill_hole_with_rect_pad") {
400
424
  const holeShape = platedHole.shape === "rotated_pill_hole_with_rect_pad" ? "pill" : platedHole.hole_shape;
401
425
  const rotation = platedHole.shape === "rotated_pill_hole_with_rect_pad" ? platedHole.hole_ccw_rotation : void 0;
402
426
  elementStrings.push(
403
- `<platedhole portHints={${JSON.stringify(platedHole.port_hints)}} pcbX="${mmStr3(platedHole.x)}" pcbY="${mmStr3(platedHole.y)}"${holeShape === void 0 ? "" : ` holeShape="${holeShape}"`}${platedHole.pad_shape === void 0 ? "" : ` padShape="${platedHole.pad_shape}"`}${formatSolderMaskAttrs(platedHole)} holeWidth="${mmStr3(platedHole.hole_width)}" holeHeight="${mmStr3(platedHole.hole_height)}" rectPadWidth="${mmStr3(platedHole.rect_pad_width)}" rectPadHeight="${mmStr3(platedHole.rect_pad_height)}"${formatOptionalMmAttr("rectBorderRadius", platedHole.rect_border_radius)}${formatOptionalMmAttr("holeOffsetX", platedHole.hole_offset_x)}${formatOptionalMmAttr("holeOffsetY", platedHole.hole_offset_y)}${formatPcbRotationAttr(rotation)} shape="pill_hole_with_rect_pad" />`
427
+ `<platedhole portHints={${JSON.stringify(platedHole.port_hints)}} pcbX="${mmStr4(platedHole.x)}" pcbY="${mmStr4(platedHole.y)}"${holeShape === void 0 ? "" : ` holeShape="${holeShape}"`}${platedHole.pad_shape === void 0 ? "" : ` padShape="${platedHole.pad_shape}"`}${formatSolderMaskAttrs(platedHole)} holeWidth="${mmStr4(platedHole.hole_width)}" holeHeight="${mmStr4(platedHole.hole_height)}" rectPadWidth="${mmStr4(platedHole.rect_pad_width)}" rectPadHeight="${mmStr4(platedHole.rect_pad_height)}"${formatOptionalMmAttr("rectBorderRadius", platedHole.rect_border_radius)}${formatOptionalMmAttr("holeOffsetX", platedHole.hole_offset_x)}${formatOptionalMmAttr("holeOffsetY", platedHole.hole_offset_y)}${formatPcbRotationAttr(rotation)} shape="pill_hole_with_rect_pad" />`
404
428
  );
405
429
  } else if (platedHole.shape === "hole_with_polygon_pad") {
406
- const holeSizeAttrs = platedHole.hole_shape === "circle" ? ` holeDiameter="${mmStr3(platedHole.hole_diameter ?? 0)}"` : `${formatOptionalMmAttr("holeWidth", platedHole.hole_width)}${formatOptionalMmAttr("holeHeight", platedHole.hole_height)}`;
430
+ const holeSizeAttrs = platedHole.hole_shape === "circle" ? ` holeDiameter="${mmStr4(platedHole.hole_diameter ?? 0)}"` : `${formatOptionalMmAttr("holeWidth", platedHole.hole_width)}${formatOptionalMmAttr("holeHeight", platedHole.hole_height)}`;
407
431
  elementStrings.push(
408
- `<platedhole portHints={${JSON.stringify(platedHole.port_hints)}} pcbX="${mmStr3(platedHole.x)}" pcbY="${mmStr3(platedHole.y)}"${platedHole.hole_shape === void 0 ? "" : ` holeShape="${platedHole.hole_shape}"`}${formatSolderMaskAttrs(platedHole)}${holeSizeAttrs} padOutline={${JSON.stringify(platedHole.pad_outline)}} holeOffsetX="${mmStr3(platedHole.hole_offset_x ?? 0)}" holeOffsetY="${mmStr3(platedHole.hole_offset_y ?? 0)}" shape="hole_with_polygon_pad" />`
432
+ `<platedhole portHints={${JSON.stringify(platedHole.port_hints)}} pcbX="${mmStr4(platedHole.x)}" pcbY="${mmStr4(platedHole.y)}"${platedHole.hole_shape === void 0 ? "" : ` holeShape="${platedHole.hole_shape}"`}${formatSolderMaskAttrs(platedHole)}${holeSizeAttrs} padOutline={${JSON.stringify(platedHole.pad_outline)}} holeOffsetX="${mmStr4(platedHole.hole_offset_x ?? 0)}" holeOffsetY="${mmStr4(platedHole.hole_offset_y ?? 0)}" shape="hole_with_polygon_pad" />`
409
433
  );
410
434
  }
411
435
  }
@@ -481,7 +505,7 @@ var convertSilkscreen = (circuitJson) => {
481
505
 
482
506
  // lib/generate-footprint-tsx/convert-silkscreen-text.ts
483
507
  import { su as su10 } from "@tscircuit/soup-util";
484
- import { mmStr as mmStr4 } from "@tscircuit/mm";
508
+ import { mmStr as mmStr5 } from "@tscircuit/mm";
485
509
  var convertSilkscreenText = (circuitJson) => {
486
510
  const silkscreenTexts = su10(circuitJson).pcb_silkscreen_text.list();
487
511
  const elementStrings = [];
@@ -491,7 +515,7 @@ var convertSilkscreenText = (circuitJson) => {
491
515
  const knockoutPadding = silkscreenText.knockout_padding;
492
516
  const hasUniformKnockoutPadding = knockoutPadding && knockoutPadding.left === knockoutPadding.top && knockoutPadding.top === knockoutPadding.right && knockoutPadding.right === knockoutPadding.bottom;
493
517
  elementStrings.push(
494
- `<silkscreentext pcbX={${pcbX}} pcbY={${pcbY}} anchorAlignment="${silkscreenText.anchor_alignment}" fontSize={${silkscreenText.font_size}}${silkscreenText.font !== void 0 ? ` font="${silkscreenText.font}"` : ""}${silkscreenText.ccw_rotation !== void 0 ? ` pcbRotation="${typeof silkscreenText.ccw_rotation === "number" ? `${silkscreenText.ccw_rotation}deg` : silkscreenText.ccw_rotation}"` : ""}${silkscreenText.is_knockout !== void 0 ? ` isKnockout={${silkscreenText.is_knockout}}` : ""}${knockoutPadding ? hasUniformKnockoutPadding ? ` knockoutPadding="${mmStr4(knockoutPadding.left)}"` : ` knockoutPaddingLeft="${mmStr4(knockoutPadding.left)}" knockoutPaddingTop="${mmStr4(knockoutPadding.top)}" knockoutPaddingRight="${mmStr4(knockoutPadding.right)}" knockoutPaddingBottom="${mmStr4(knockoutPadding.bottom)}"` : ""}${silkscreenText.is_mirrored !== void 0 ? ` mirrored={${silkscreenText.is_mirrored}}` : ""}${silkscreenText.layer !== void 0 ? ` layer="${silkscreenText.layer}"` : ""} text="${escapeJsxText(silkscreenText.text)}" />`
518
+ `<silkscreentext pcbX={${pcbX}} pcbY={${pcbY}} anchorAlignment="${silkscreenText.anchor_alignment}" fontSize={${silkscreenText.font_size}}${silkscreenText.font !== void 0 ? ` font="${silkscreenText.font}"` : ""}${silkscreenText.ccw_rotation !== void 0 ? ` pcbRotation="${typeof silkscreenText.ccw_rotation === "number" ? `${silkscreenText.ccw_rotation}deg` : silkscreenText.ccw_rotation}"` : ""}${silkscreenText.is_knockout !== void 0 ? ` isKnockout={${silkscreenText.is_knockout}}` : ""}${knockoutPadding ? hasUniformKnockoutPadding ? ` knockoutPadding="${mmStr5(knockoutPadding.left)}"` : ` knockoutPaddingLeft="${mmStr5(knockoutPadding.left)}" knockoutPaddingTop="${mmStr5(knockoutPadding.top)}" knockoutPaddingRight="${mmStr5(knockoutPadding.right)}" knockoutPaddingBottom="${mmStr5(knockoutPadding.bottom)}"` : ""}${silkscreenText.is_mirrored !== void 0 ? ` mirrored={${silkscreenText.is_mirrored}}` : ""}${silkscreenText.layer !== void 0 ? ` layer="${silkscreenText.layer}"` : ""} text="${escapeJsxText(silkscreenText.text)}" />`
495
519
  );
496
520
  }
497
521
  return elementStrings;
@@ -499,103 +523,49 @@ var convertSilkscreenText = (circuitJson) => {
499
523
 
500
524
  // lib/generate-footprint-tsx/convert-smt-pads.ts
501
525
  import { su as su11 } from "@tscircuit/soup-util";
502
- import { mmStr as mmStr5 } from "@tscircuit/mm";
526
+ import { mmStr as mmStr6 } from "@tscircuit/mm";
503
527
  var convertSmtPads = (circuitJson) => {
504
528
  const smtPads = su11(circuitJson).pcb_smtpad.list();
505
529
  const elementStrings = [];
506
530
  for (const smtPad of smtPads) {
507
531
  const commonAttrs = [];
532
+ const solderMaskAttrs = formatSolderMaskAttrs(smtPad);
508
533
  if (smtPad.port_hints !== void 0) {
509
534
  commonAttrs.push(`portHints={${JSON.stringify(smtPad.port_hints)}}`);
510
535
  }
511
536
  if ("x" in smtPad && smtPad.x !== void 0) {
512
- commonAttrs.push(`pcbX="${mmStr5(smtPad.x)}"`);
537
+ commonAttrs.push(`pcbX="${mmStr6(smtPad.x)}"`);
513
538
  }
514
539
  if ("y" in smtPad && smtPad.y !== void 0) {
515
- commonAttrs.push(`pcbY="${mmStr5(smtPad.y)}"`);
540
+ commonAttrs.push(`pcbY="${mmStr6(smtPad.y)}"`);
516
541
  }
517
542
  if (smtPad.layer !== void 0) {
518
543
  commonAttrs.push(`layer="${smtPad.layer}"`);
519
544
  }
520
- if (smtPad.is_covered_with_solder_mask !== void 0) {
521
- commonAttrs.push(
522
- `coveredWithSolderMask={${smtPad.is_covered_with_solder_mask}}`
523
- );
524
- }
525
- if (smtPad.soldermask_margin !== void 0) {
526
- commonAttrs.push(`solderMaskMargin="${mmStr5(smtPad.soldermask_margin)}"`);
527
- }
528
545
  if (smtPad.shape === "circle") {
529
546
  elementStrings.push(
530
- `<smtpad ${commonAttrs.join(" ")} radius="${mmStr5(smtPad.radius ?? 0)}" shape="circle" />`
547
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} radius="${mmStr6(smtPad.radius ?? 0)}" shape="circle" />`
531
548
  );
532
549
  } else if (smtPad.shape === "rect") {
533
- if (smtPad.rect_border_radius !== void 0) {
534
- commonAttrs.push(
535
- `rectBorderRadius="${mmStr5(smtPad.rect_border_radius)}"`
536
- );
537
- }
538
- if (smtPad.corner_radius !== void 0) {
539
- commonAttrs.push(`cornerRadius="${mmStr5(smtPad.corner_radius)}"`);
540
- }
541
- if (smtPad.soldermask_margin_left !== void 0) {
542
- commonAttrs.push(
543
- `solderMaskMarginLeft="${mmStr5(smtPad.soldermask_margin_left)}"`
544
- );
545
- }
546
- if (smtPad.soldermask_margin_top !== void 0) {
547
- commonAttrs.push(
548
- `solderMaskMarginTop="${mmStr5(smtPad.soldermask_margin_top)}"`
549
- );
550
- }
551
- if (smtPad.soldermask_margin_right !== void 0) {
552
- commonAttrs.push(
553
- `solderMaskMarginRight="${mmStr5(smtPad.soldermask_margin_right)}"`
554
- );
555
- }
556
- if (smtPad.soldermask_margin_bottom !== void 0) {
557
- commonAttrs.push(
558
- `solderMaskMarginBottom="${mmStr5(smtPad.soldermask_margin_bottom)}"`
559
- );
560
- }
561
550
  elementStrings.push(
562
- `<smtpad ${commonAttrs.join(" ")} width="${mmStr5(smtPad.width ?? 0)}" height="${mmStr5(smtPad.height ?? 0)}" shape="rect" />`
551
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs}${formatOptionalMmAttr("rectBorderRadius", smtPad.rect_border_radius)}${formatOptionalMmAttr("cornerRadius", smtPad.corner_radius)}${formatOptionalMmAttr("solderMaskMarginLeft", smtPad.soldermask_margin_left)}${formatOptionalMmAttr("solderMaskMarginTop", smtPad.soldermask_margin_top)}${formatOptionalMmAttr("solderMaskMarginRight", smtPad.soldermask_margin_right)}${formatOptionalMmAttr("solderMaskMarginBottom", smtPad.soldermask_margin_bottom)} width="${mmStr6(smtPad.width ?? 0)}" height="${mmStr6(smtPad.height ?? 0)}" shape="rect" />`
563
552
  );
564
553
  } else if (smtPad.shape === "pill") {
565
554
  elementStrings.push(
566
- `<smtpad ${commonAttrs.join(" ")} width="${mmStr5(smtPad.width ?? 0)}" height="${mmStr5(smtPad.height ?? 0)}" radius="${mmStr5(smtPad.radius ?? 0)}" shape="pill" />`
555
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} width="${mmStr6(smtPad.width ?? 0)}" height="${mmStr6(smtPad.height ?? 0)}" radius="${mmStr6(smtPad.radius ?? 0)}" shape="pill" />`
556
+ );
557
+ } else if (smtPad.shape === "rotated_pill") {
558
+ elementStrings.push(
559
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} width="${mmStr6(smtPad.width ?? 0)}" height="${mmStr6(smtPad.height ?? 0)}" radius="${mmStr6(smtPad.radius ?? 0)}"${formatPcbRotationAttr(smtPad.ccw_rotation)} shape="pill" />`
567
560
  );
568
561
  } else if (smtPad.shape === "polygon") {
569
562
  elementStrings.push(
570
- `<smtpad ${commonAttrs.join(" ")} shape="polygon" points={${JSON.stringify(smtPad.points)}} />`
563
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} shape="polygon" points={${JSON.stringify(smtPad.points)}} />`
571
564
  );
572
565
  } else if (smtPad.shape === "rotated_rect") {
573
566
  const cornerRadius = smtPad.corner_radius ?? smtPad.rect_border_radius ?? void 0;
574
- if (cornerRadius !== void 0) {
575
- commonAttrs.push(`cornerRadius="${mmStr5(cornerRadius)}"`);
576
- }
577
- if (smtPad.soldermask_margin_left !== void 0) {
578
- commonAttrs.push(
579
- `solderMaskMarginLeft="${mmStr5(smtPad.soldermask_margin_left)}"`
580
- );
581
- }
582
- if (smtPad.soldermask_margin_top !== void 0) {
583
- commonAttrs.push(
584
- `solderMaskMarginTop="${mmStr5(smtPad.soldermask_margin_top)}"`
585
- );
586
- }
587
- if (smtPad.soldermask_margin_right !== void 0) {
588
- commonAttrs.push(
589
- `solderMaskMarginRight="${mmStr5(smtPad.soldermask_margin_right)}"`
590
- );
591
- }
592
- if (smtPad.soldermask_margin_bottom !== void 0) {
593
- commonAttrs.push(
594
- `solderMaskMarginBottom="${mmStr5(smtPad.soldermask_margin_bottom)}"`
595
- );
596
- }
597
567
  elementStrings.push(
598
- `<smtpad ${commonAttrs.join(" ")} width="${mmStr5(smtPad.width ?? 0)}" height="${mmStr5(smtPad.height ?? 0)}"${formatPcbRotationAttr(smtPad.ccw_rotation)} shape="rotated_rect" />`
568
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs}${formatOptionalMmAttr("cornerRadius", cornerRadius)}${formatOptionalMmAttr("solderMaskMarginLeft", smtPad.soldermask_margin_left)}${formatOptionalMmAttr("solderMaskMarginTop", smtPad.soldermask_margin_top)}${formatOptionalMmAttr("solderMaskMarginRight", smtPad.soldermask_margin_right)}${formatOptionalMmAttr("solderMaskMarginBottom", smtPad.soldermask_margin_bottom)} width="${mmStr6(smtPad.width ?? 0)}" height="${mmStr6(smtPad.height ?? 0)}"${formatPcbRotationAttr(smtPad.ccw_rotation)} shape="rotated_rect" />`
599
569
  );
600
570
  }
601
571
  }
@@ -634,17 +604,17 @@ var getBoardUsingTemplate = ({ circuitJson }) => {
634
604
  const boardProps = [];
635
605
  if (pcbBoard) {
636
606
  if (pcbBoard.width !== void 0) {
637
- boardProps.push(`width="${mmStr6(pcbBoard.width)}"`);
607
+ boardProps.push(`width="${mmStr7(pcbBoard.width)}"`);
638
608
  }
639
609
  if (pcbBoard.height !== void 0) {
640
- boardProps.push(`height="${mmStr6(pcbBoard.height)}"`);
610
+ boardProps.push(`height="${mmStr7(pcbBoard.height)}"`);
641
611
  }
642
612
  if (pcbBoard.outline && pcbBoard.outline.length > 0) {
643
613
  const points = pcbBoard.outline.map((p) => `{ x: ${p.x}, y: ${p.y} }`).join(", ");
644
614
  boardProps.push(`outline={[${points}]}`);
645
615
  }
646
616
  if (pcbBoard.thickness !== void 0) {
647
- boardProps.push(`thickness="${mmStr6(pcbBoard.thickness)}"`);
617
+ boardProps.push(`thickness="${mmStr7(pcbBoard.thickness)}"`);
648
618
  }
649
619
  if (pcbBoard.num_layers !== void 0) {
650
620
  boardProps.push(`layers={${pcbBoard.num_layers}}`);
package/dist/cli/main.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  convertCircuitJsonToTscircuit
4
- } from "../chunk-UHPKLBYL.js";
4
+ } from "../chunk-UXTI3RT6.js";
5
5
 
6
6
  // cli/main.ts
7
7
  import { Command } from "commander";
@@ -16,7 +16,7 @@ var package_default = {
16
16
  "./cli": "./dist/cli/main.js"
17
17
  },
18
18
  type: "module",
19
- version: "0.0.39",
19
+ version: "0.0.41",
20
20
  scripts: {
21
21
  build: "tsup-node lib/index.ts cli/main.ts --format esm --dts",
22
22
  test: "bun test",
package/dist/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  convertCircuitJsonToTscircuit
3
- } from "../chunk-UHPKLBYL.js";
3
+ } from "../chunk-UXTI3RT6.js";
4
4
  export {
5
5
  convertCircuitJsonToTscircuit
6
6
  };
@@ -1,6 +1,6 @@
1
1
  import { su } from "@tscircuit/soup-util"
2
2
  import type { FootprintElementConverter } from "./converter-types"
3
- import { escapeJsxText } from "./helpers"
3
+ import { escapeJsxText } from "./footprint-tsx-attribute-formatters/escape-jsx-text"
4
4
 
5
5
  export const convertCopperText: FootprintElementConverter = (circuitJson) => {
6
6
  const copperTexts = su(circuitJson).pcb_copper_text.list()
@@ -1,5 +1,6 @@
1
1
  import { su } from "@tscircuit/soup-util"
2
2
  import type { FootprintElementConverter } from "./converter-types"
3
+ import { formatPcbRotationAttr } from "./footprint-tsx-attribute-formatters/format-pcb-rotation-attr"
3
4
 
4
5
  export const convertCourtyard: FootprintElementConverter = (circuitJson) => {
5
6
  const courtyardOutlines = su(circuitJson).pcb_courtyard_outline.list()
@@ -28,6 +29,9 @@ export const convertCourtyard: FootprintElementConverter = (circuitJson) => {
28
29
  if (courtyardRect.layer !== undefined) {
29
30
  attrs.push(`layer="${courtyardRect.layer}"`)
30
31
  }
32
+ if (courtyardRect.ccw_rotation !== undefined) {
33
+ attrs.push(formatPcbRotationAttr(courtyardRect.ccw_rotation).trimStart())
34
+ }
31
35
 
32
36
  elementStrings.push(`<courtyardrect ${attrs.join(" ")} />`)
33
37
  }
@@ -1,6 +1,6 @@
1
1
  import { su } from "@tscircuit/soup-util"
2
2
  import type { FootprintElementConverter } from "./converter-types"
3
- import { formatMm } from "./helpers"
3
+ import { formatMm } from "./footprint-tsx-attribute-formatters/format-mm"
4
4
 
5
5
  export const convertCutouts: FootprintElementConverter = (circuitJson) => {
6
6
  const pcbCutouts = su(circuitJson).pcb_cutout.list()
@@ -1,6 +1,6 @@
1
1
  import { su } from "@tscircuit/soup-util"
2
2
  import type { FootprintElementConverter } from "./converter-types"
3
- import { escapeJsxText } from "./helpers"
3
+ import { escapeJsxText } from "./footprint-tsx-attribute-formatters/escape-jsx-text"
4
4
 
5
5
  export const convertFabrication: FootprintElementConverter = (circuitJson) => {
6
6
  const fabricationNotePaths = su(circuitJson).pcb_fabrication_note_path.list()
@@ -1,7 +1,7 @@
1
1
  import { su } from "@tscircuit/soup-util"
2
2
  import { mmStr } from "@tscircuit/mm"
3
3
  import type { FootprintElementConverter } from "./converter-types"
4
- import { formatPcbRotationAttr } from "./helpers"
4
+ import { formatPcbRotationAttr } from "./footprint-tsx-attribute-formatters/format-pcb-rotation-attr"
5
5
 
6
6
  export const convertHoles: FootprintElementConverter = (circuitJson) => {
7
7
  const holes = su(circuitJson).pcb_hole.list()
@@ -1,6 +1,6 @@
1
1
  import { su } from "@tscircuit/soup-util"
2
2
  import type { FootprintElementConverter } from "./converter-types"
3
- import { formatMm } from "./helpers"
3
+ import { formatMm } from "./footprint-tsx-attribute-formatters/format-mm"
4
4
 
5
5
  export const convertKeepouts: FootprintElementConverter = (circuitJson) => {
6
6
  const pcbKeepouts = su(circuitJson).pcb_keepout.list()
@@ -1,6 +1,6 @@
1
1
  import { su } from "@tscircuit/soup-util"
2
2
  import type { FootprintElementConverter } from "./converter-types"
3
- import { escapeJsxText } from "./helpers"
3
+ import { escapeJsxText } from "./footprint-tsx-attribute-formatters/escape-jsx-text"
4
4
 
5
5
  export const convertNotes: FootprintElementConverter = (circuitJson) => {
6
6
  const noteTexts = su(circuitJson).pcb_note_text.list()
@@ -1,11 +1,9 @@
1
1
  import { su } from "@tscircuit/soup-util"
2
2
  import { mmStr } from "@tscircuit/mm"
3
3
  import type { FootprintElementConverter } from "./converter-types"
4
- import {
5
- formatOptionalMmAttr,
6
- formatPcbRotationAttr,
7
- formatSolderMaskAttrs,
8
- } from "./helpers"
4
+ import { formatOptionalMmAttr } from "./footprint-tsx-attribute-formatters/format-optional-mm-attr"
5
+ import { formatPcbRotationAttr } from "./footprint-tsx-attribute-formatters/format-pcb-rotation-attr"
6
+ import { formatSolderMaskAttrs } from "./footprint-tsx-attribute-formatters/format-solder-mask-attrs"
9
7
 
10
8
  export const convertPlatedHoles: FootprintElementConverter = (circuitJson) => {
11
9
  const platedHoles = su(circuitJson).pcb_plated_hole.list()
@@ -1,7 +1,7 @@
1
1
  import { su } from "@tscircuit/soup-util"
2
2
  import { mmStr } from "@tscircuit/mm"
3
3
  import type { FootprintElementConverter } from "./converter-types"
4
- import { escapeJsxText } from "./helpers"
4
+ import { escapeJsxText } from "./footprint-tsx-attribute-formatters/escape-jsx-text"
5
5
 
6
6
  export const convertSilkscreenText: FootprintElementConverter = (
7
7
  circuitJson,
@@ -1,7 +1,9 @@
1
1
  import { su } from "@tscircuit/soup-util"
2
2
  import { mmStr } from "@tscircuit/mm"
3
3
  import type { FootprintElementConverter } from "./converter-types"
4
- import { formatPcbRotationAttr } from "./helpers"
4
+ import { formatOptionalMmAttr } from "./footprint-tsx-attribute-formatters/format-optional-mm-attr"
5
+ import { formatPcbRotationAttr } from "./footprint-tsx-attribute-formatters/format-pcb-rotation-attr"
6
+ import { formatSolderMaskAttrs } from "./footprint-tsx-attribute-formatters/format-solder-mask-attrs"
5
7
 
6
8
  export const convertSmtPads: FootprintElementConverter = (circuitJson) => {
7
9
  const smtPads = su(circuitJson).pcb_smtpad.list()
@@ -9,6 +11,7 @@ export const convertSmtPads: FootprintElementConverter = (circuitJson) => {
9
11
 
10
12
  for (const smtPad of smtPads) {
11
13
  const commonAttrs: string[] = []
14
+ const solderMaskAttrs = formatSolderMaskAttrs(smtPad)
12
15
 
13
16
  if (smtPad.port_hints !== undefined) {
14
17
  commonAttrs.push(`portHints={${JSON.stringify(smtPad.port_hints)}}`)
@@ -22,88 +25,33 @@ export const convertSmtPads: FootprintElementConverter = (circuitJson) => {
22
25
  if (smtPad.layer !== undefined) {
23
26
  commonAttrs.push(`layer="${smtPad.layer}"`)
24
27
  }
25
- if (smtPad.is_covered_with_solder_mask !== undefined) {
26
- commonAttrs.push(
27
- `coveredWithSolderMask={${smtPad.is_covered_with_solder_mask}}`,
28
- )
29
- }
30
- if (smtPad.soldermask_margin !== undefined) {
31
- commonAttrs.push(`solderMaskMargin="${mmStr(smtPad.soldermask_margin)}"`)
32
- }
33
28
 
34
29
  if (smtPad.shape === "circle") {
35
30
  elementStrings.push(
36
- `<smtpad ${commonAttrs.join(" ")} radius="${mmStr(smtPad.radius ?? 0)}" shape="circle" />`,
31
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} radius="${mmStr(smtPad.radius ?? 0)}" shape="circle" />`,
37
32
  )
38
33
  } else if (smtPad.shape === "rect") {
39
- if (smtPad.rect_border_radius !== undefined) {
40
- commonAttrs.push(
41
- `rectBorderRadius="${mmStr(smtPad.rect_border_radius)}"`,
42
- )
43
- }
44
- if (smtPad.corner_radius !== undefined) {
45
- commonAttrs.push(`cornerRadius="${mmStr(smtPad.corner_radius)}"`)
46
- }
47
- if (smtPad.soldermask_margin_left !== undefined) {
48
- commonAttrs.push(
49
- `solderMaskMarginLeft="${mmStr(smtPad.soldermask_margin_left)}"`,
50
- )
51
- }
52
- if (smtPad.soldermask_margin_top !== undefined) {
53
- commonAttrs.push(
54
- `solderMaskMarginTop="${mmStr(smtPad.soldermask_margin_top)}"`,
55
- )
56
- }
57
- if (smtPad.soldermask_margin_right !== undefined) {
58
- commonAttrs.push(
59
- `solderMaskMarginRight="${mmStr(smtPad.soldermask_margin_right)}"`,
60
- )
61
- }
62
- if (smtPad.soldermask_margin_bottom !== undefined) {
63
- commonAttrs.push(
64
- `solderMaskMarginBottom="${mmStr(smtPad.soldermask_margin_bottom)}"`,
65
- )
66
- }
67
34
  elementStrings.push(
68
- `<smtpad ${commonAttrs.join(" ")} width="${mmStr(smtPad.width ?? 0)}" height="${mmStr(smtPad.height ?? 0)}" shape="rect" />`,
35
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs}${formatOptionalMmAttr("rectBorderRadius", smtPad.rect_border_radius)}${formatOptionalMmAttr("cornerRadius", smtPad.corner_radius)}${formatOptionalMmAttr("solderMaskMarginLeft", smtPad.soldermask_margin_left)}${formatOptionalMmAttr("solderMaskMarginTop", smtPad.soldermask_margin_top)}${formatOptionalMmAttr("solderMaskMarginRight", smtPad.soldermask_margin_right)}${formatOptionalMmAttr("solderMaskMarginBottom", smtPad.soldermask_margin_bottom)} width="${mmStr(smtPad.width ?? 0)}" height="${mmStr(smtPad.height ?? 0)}" shape="rect" />`,
69
36
  )
70
37
  } else if (smtPad.shape === "pill") {
71
38
  elementStrings.push(
72
- `<smtpad ${commonAttrs.join(" ")} width="${mmStr(smtPad.width ?? 0)}" height="${mmStr(smtPad.height ?? 0)}" radius="${mmStr(smtPad.radius ?? 0)}" shape="pill" />`,
39
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} width="${mmStr(smtPad.width ?? 0)}" height="${mmStr(smtPad.height ?? 0)}" radius="${mmStr(smtPad.radius ?? 0)}" shape="pill" />`,
40
+ )
41
+ } else if (smtPad.shape === "rotated_pill") {
42
+ elementStrings.push(
43
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} width="${mmStr(smtPad.width ?? 0)}" height="${mmStr(smtPad.height ?? 0)}" radius="${mmStr(smtPad.radius ?? 0)}"${formatPcbRotationAttr(smtPad.ccw_rotation)} shape="pill" />`,
73
44
  )
74
45
  } else if (smtPad.shape === "polygon") {
75
46
  elementStrings.push(
76
- `<smtpad ${commonAttrs.join(" ")} shape="polygon" points={${JSON.stringify(smtPad.points)}} />`,
47
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} shape="polygon" points={${JSON.stringify(smtPad.points)}} />`,
77
48
  )
78
49
  } else if (smtPad.shape === "rotated_rect") {
79
50
  const cornerRadius =
80
51
  smtPad.corner_radius ?? smtPad.rect_border_radius ?? undefined
81
52
 
82
- if (cornerRadius !== undefined) {
83
- commonAttrs.push(`cornerRadius="${mmStr(cornerRadius)}"`)
84
- }
85
- if (smtPad.soldermask_margin_left !== undefined) {
86
- commonAttrs.push(
87
- `solderMaskMarginLeft="${mmStr(smtPad.soldermask_margin_left)}"`,
88
- )
89
- }
90
- if (smtPad.soldermask_margin_top !== undefined) {
91
- commonAttrs.push(
92
- `solderMaskMarginTop="${mmStr(smtPad.soldermask_margin_top)}"`,
93
- )
94
- }
95
- if (smtPad.soldermask_margin_right !== undefined) {
96
- commonAttrs.push(
97
- `solderMaskMarginRight="${mmStr(smtPad.soldermask_margin_right)}"`,
98
- )
99
- }
100
- if (smtPad.soldermask_margin_bottom !== undefined) {
101
- commonAttrs.push(
102
- `solderMaskMarginBottom="${mmStr(smtPad.soldermask_margin_bottom)}"`,
103
- )
104
- }
105
53
  elementStrings.push(
106
- `<smtpad ${commonAttrs.join(" ")} width="${mmStr(smtPad.width ?? 0)}" height="${mmStr(smtPad.height ?? 0)}"${formatPcbRotationAttr(smtPad.ccw_rotation)} shape="rotated_rect" />`,
54
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs}${formatOptionalMmAttr("cornerRadius", cornerRadius)}${formatOptionalMmAttr("solderMaskMarginLeft", smtPad.soldermask_margin_left)}${formatOptionalMmAttr("solderMaskMarginTop", smtPad.soldermask_margin_top)}${formatOptionalMmAttr("solderMaskMarginRight", smtPad.soldermask_margin_right)}${formatOptionalMmAttr("solderMaskMarginBottom", smtPad.soldermask_margin_bottom)} width="${mmStr(smtPad.width ?? 0)}" height="${mmStr(smtPad.height ?? 0)}"${formatPcbRotationAttr(smtPad.ccw_rotation)} shape="rotated_rect" />`,
107
55
  )
108
56
  }
109
57
  }
@@ -0,0 +1,2 @@
1
+ export const escapeJsxText = (value: unknown): string =>
2
+ String(value ?? "").replace(/"/g, '\\"')
@@ -0,0 +1,3 @@
1
+ import { mmStr } from "@tscircuit/mm"
2
+
3
+ export const formatMm = (value: number | undefined): string => mmStr(value ?? 0)
@@ -0,0 +1,9 @@
1
+ import { mmStr } from "@tscircuit/mm"
2
+
3
+ export const formatOptionalMmAttr = (
4
+ attrName: string,
5
+ value: number | undefined,
6
+ ): string => {
7
+ if (value === undefined) return ""
8
+ return ` ${attrName}="${mmStr(value)}"`
9
+ }
@@ -0,0 +1,8 @@
1
+ export const formatPcbRotationAttr = (
2
+ rotation: string | number | undefined,
3
+ attrName = "pcbRotation",
4
+ ): string => {
5
+ if (rotation === undefined) return ""
6
+ const formatted = typeof rotation === "number" ? `${rotation}deg` : rotation
7
+ return ` ${attrName}="${formatted}"`
8
+ }
@@ -0,0 +1,14 @@
1
+ import { formatOptionalMmAttr } from "./format-optional-mm-attr"
2
+
3
+ export const formatSolderMaskAttrs = (platedHole: {
4
+ is_covered_with_solder_mask?: boolean
5
+ soldermask_margin?: number
6
+ }): string => {
7
+ let coveredWithSolderMaskAttr = ""
8
+
9
+ if (platedHole.is_covered_with_solder_mask !== undefined) {
10
+ coveredWithSolderMaskAttr = ` coveredWithSolderMask={${platedHole.is_covered_with_solder_mask}}`
11
+ }
12
+
13
+ return `${coveredWithSolderMaskAttr}${formatOptionalMmAttr("solderMaskMargin", platedHole.soldermask_margin)}`
14
+ }
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "./cli": "./dist/cli/main.js"
7
7
  },
8
8
  "type": "module",
9
- "version": "0.0.40",
9
+ "version": "0.0.42",
10
10
  "scripts": {
11
11
  "build": "tsup-node lib/index.ts cli/main.ts --format esm --dts",
12
12
  "test": "bun test",
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" data-software-used-string="@tscircuit/core@0.0.1090"><style></style><rect class="boundary" x="0" y="0" fill="#000" width="800" height="600" data-type="pcb_background" data-pcb-layer="global"/><rect class="pcb-boundary" fill="none" stroke="#fff" stroke-width="0.3" x="127.27272727272725" y="27.272727272727252" width="545.4545454545455" height="545.4545454545455" data-type="pcb_boundary" data-pcb-layer="global"/><path class="pcb-board" d="M 127.27272727272725 572.7272727272727 L 672.7272727272727 572.7272727272727 L 672.7272727272727 27.272727272727252 L 127.27272727272725 27.272727272727252 Z" fill="none" stroke="rgba(255, 255, 255, 0.5)" stroke-width="2.7272727272727275" data-type="pcb_board" data-pcb-layer="board"/><rect class="pcb-pad" fill="rgb(77, 127, 196)" x="-27.272727272727273" y="-8.181818181818182" width="54.54545454545455" height="16.363636363636363" transform="translate(372.72727272727275 354.54545454545456) rotate(-45)" data-type="pcb_smtpad" data-pcb-layer="bottom" rx="4.090909090909091" ry="4.090909090909091"/><rect class="pcb-pad" fill="rgb(77, 127, 196)" x="443.6363636363636" y="328.6363636363636" width="49.09090909090909" height="24.545454545454547" data-type="pcb_smtpad" data-pcb-layer="bottom" rx="5.454545454545455" ry="5.454545454545455"/><rect class="pcb-pad" fill="rgb(77, 127, 196)" x="-27.272727272727273" y="-8.181818181818182" width="54.54545454545455" height="16.363636363636363" transform="translate(372.72727272727275 354.54545454545456) rotate(-45)" data-type="pcb_smtpad" data-pcb-layer="bottom" rx="4.090909090909091" ry="4.090909090909091"/><rect class="pcb-pad" fill="rgb(77, 127, 196)" x="443.6363636363636" y="328.6363636363636" width="49.09090909090909" height="24.545454545454547" data-type="pcb_smtpad" data-pcb-layer="bottom" rx="5.454545454545455" ry="5.454545454545455"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="406.8181818181818" y="234.54545454545453" width="40.90909090909091" height="21.81818181818182" rx="10.90909090909091" ry="10.90909090909091" data-type="pcb_smtpad" data-pcb-layer="top"/><polygon class="pcb-pad" fill="rgb(200, 52, 52)" points="400,300 427.27272727272725,300 413.6363636363636,272.72727272727275" data-type="pcb_smtpad" data-pcb-layer="top"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="406.8181818181818" y="234.54545454545453" width="40.90909090909091" height="21.81818181818182" rx="10.90909090909091" ry="10.90909090909091" data-type="pcb_smtpad" data-pcb-layer="top"/><polygon class="pcb-pad" fill="rgb(200, 52, 52)" points="400,300 427.27272727272725,300 413.6363636363636,272.72727272727275" data-type="pcb_smtpad" data-pcb-layer="top"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" data-software-used-string="@tscircuit/core@0.0.1322"><style></style><rect class="boundary" x="0" y="0" fill="#000" width="800" height="600" data-type="pcb_background" data-pcb-layer="global"/><rect class="pcb-boundary" fill="none" stroke="#fff" stroke-width="0.3" x="127.27272727272725" y="27.272727272727252" width="545.4545454545455" height="545.4545454545455" data-type="pcb_boundary" data-pcb-layer="global"/><path class="pcb-board" d="M 127.27272727272725 572.7272727272727 L 672.7272727272727 572.7272727272727 L 672.7272727272727 27.272727272727252 L 127.27272727272725 27.272727272727252 Z" fill="none" stroke="rgba(255, 255, 255, 0.5)" stroke-width="2.7272727272727275" data-type="pcb_board" data-pcb-layer="board"/><rect class="pcb-pad" fill="rgb(77, 127, 196)" x="-27.272727272727273" y="-8.181818181818182" width="54.54545454545455" height="16.363636363636363" transform="translate(372.72727272727275 354.54545454545456) rotate(-45)" data-type="pcb_smtpad" data-pcb-layer="bottom" rx="4.090909090909091" ry="4.090909090909091"/><rect class="pcb-pad" fill="rgb(77, 127, 196)" x="443.6363636363636" y="328.6363636363636" width="49.09090909090909" height="24.545454545454547" data-type="pcb_smtpad" data-pcb-layer="bottom" rx="5.454545454545455" ry="5.454545454545455"/><rect class="pcb-pad" fill="rgb(77, 127, 196)" x="-27.272727272727273" y="-8.181818181818182" width="54.54545454545455" height="16.363636363636363" transform="translate(372.72727272727275 354.54545454545456) rotate(-45)" data-type="pcb_smtpad" data-pcb-layer="bottom" rx="4.090909090909091" ry="4.090909090909091"/><rect class="pcb-pad" fill="rgb(77, 127, 196)" x="443.6363636363636" y="328.6363636363636" width="49.09090909090909" height="24.545454545454547" data-type="pcb_smtpad" data-pcb-layer="bottom" rx="5.454545454545455" ry="5.454545454545455"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="406.8181818181818" y="234.54545454545453" width="40.90909090909091" height="21.81818181818182" rx="10.90909090909091" ry="10.90909090909091" data-type="pcb_smtpad" data-pcb-layer="top"/><polygon class="pcb-pad" fill="rgb(200, 52, 52)" points="400,300 427.27272727272725,300 413.6363636363636,272.72727272727275" data-type="pcb_smtpad" data-pcb-layer="top"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="-30.000000000000004" y="-12.272727272727273" width="60.00000000000001" height="24.545454545454547" rx="12.272727272727273" ry="12.272727272727273" data-type="pcb_smtpad" data-pcb-layer="top" transform="translate(334.54545454545456 256.3636363636364) rotate(-30)"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="406.8181818181818" y="234.54545454545453" width="40.90909090909091" height="21.81818181818182" rx="10.90909090909091" ry="10.90909090909091" data-type="pcb_smtpad" data-pcb-layer="top"/><polygon class="pcb-pad" fill="rgb(200, 52, 52)" points="400,300 427.27272727272725,300 413.6363636363636,272.72727272727275" data-type="pcb_smtpad" data-pcb-layer="top"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="-30.000000000000004" y="-12.272727272727273" width="60.00000000000001" height="24.545454545454547" rx="12.272727272727273" ry="12.272727272727273" data-type="pcb_smtpad" data-pcb-layer="top" transform="translate(334.54545454545456 256.3636363636364) rotate(-30)"/></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" data-software-used-string="@tscircuit/core@0.0.1090"><style></style><rect class="boundary" x="0" y="0" fill="#000" width="800" height="600" data-type="pcb_background" data-pcb-layer="global"/><rect class="pcb-boundary" fill="none" stroke="#fff" stroke-width="0.3" x="127.27272727272725" y="27.272727272727252" width="545.4545454545455" height="545.4545454545455" data-type="pcb_boundary" data-pcb-layer="global"/><path class="pcb-board" d="M 127.27272727272725 572.7272727272727 L 672.7272727272727 572.7272727272727 L 672.7272727272727 27.272727272727252 L 127.27272727272725 27.272727272727252 Z" fill="none" stroke="rgba(255, 255, 255, 0.5)" stroke-width="2.7272727272727275" data-type="pcb_board" data-pcb-layer="board"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="378.1818181818182" y="287.72727272727275" width="16.363636363636363" height="24.545454545454547" data-type="pcb_smtpad" data-pcb-layer="top"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="378.1818181818182" y="287.72727272727275" width="16.363636363636363" height="24.545454545454547" data-type="pcb_smtpad" data-pcb-layer="top"/><path class="pcb-fabrication-note-path" stroke="rgba(255,255,255,0.5)" fill="none" d="M 350.9090909090909 338.1818181818182 L 449.0909090909091 338.1818181818182 L 449.0909090909091 261.8181818181818 L 350.9090909090909 261.8181818181818" stroke-width="2.7272727272727275" data-pcb-component-id="pcb_component_0" data-pcb-fabrication-note-path-id="projected_fnp_pcb_courtyard_outline_0" data-type="pcb_fabrication_note_path" data-pcb-layer="overlay"/><rect x="345.45454545454544" y="259.0909090909091" width="109.09090909090912" height="81.81818181818176" class="pcb-fabrication-note-rect" data-type="pcb_fabrication_note_rect" data-pcb-fabrication-note-rect-id="projected_fnr_pcb_courtyard_rect_0" data-pcb-layer="overlay" data-pcb-component-id="pcb_component_0" fill="none" stroke="rgba(255,255,255,0.5)" stroke-width="2.7272727272727275"/><path class="pcb-fabrication-note-path" stroke="rgba(255,255,255,0.5)" fill="none" d="M 350.9090909090909 338.1818181818182 L 449.0909090909091 338.1818181818182 L 449.0909090909091 261.8181818181818 L 350.9090909090909 261.8181818181818" stroke-width="2.7272727272727275" data-pcb-component-id="pcb_component_1" data-pcb-fabrication-note-path-id="projected_fnp_pcb_courtyard_outline_1" data-type="pcb_fabrication_note_path" data-pcb-layer="overlay"/><rect x="345.45454545454544" y="259.0909090909091" width="109.09090909090912" height="81.81818181818176" class="pcb-fabrication-note-rect" data-type="pcb_fabrication_note_rect" data-pcb-fabrication-note-rect-id="projected_fnr_pcb_courtyard_rect_1" data-pcb-layer="overlay" data-pcb-component-id="pcb_component_1" fill="none" stroke="rgba(255,255,255,0.5)" stroke-width="2.7272727272727275"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" data-software-used-string="@tscircuit/core@0.0.1322"><style></style><rect class="boundary" x="0" y="0" fill="#000" width="800" height="600" data-type="pcb_background" data-pcb-layer="global"/><rect class="pcb-boundary" fill="none" stroke="#fff" stroke-width="0.3" x="127.27272727272725" y="27.272727272727252" width="545.4545454545455" height="545.4545454545455" data-type="pcb_boundary" data-pcb-layer="global"/><path class="pcb-board" d="M 127.27272727272725 572.7272727272727 L 672.7272727272727 572.7272727272727 L 672.7272727272727 27.272727272727252 L 127.27272727272725 27.272727272727252 Z" fill="none" stroke="rgba(255, 255, 255, 0.5)" stroke-width="2.7272727272727275" data-type="pcb_board" data-pcb-layer="board"/><rect x="506.3636363636364" y="245.45454545454547" width="125.45454545454545" height="98.18181818181819" class="pcb-courtyard-rect pcb-courtyard-bottom" data-pcb-courtyard-rect-id="pcb_courtyard_rect_1" data-type="pcb_courtyard_rect" data-pcb-layer="bottom" stroke-linejoin="round" fill="none" stroke="rgb(38, 233, 255)" stroke-width="1.3636363636363638"/><rect x="506.3636363636364" y="245.45454545454547" width="125.45454545454545" height="98.18181818181819" class="pcb-courtyard-rect pcb-courtyard-bottom" data-pcb-courtyard-rect-id="pcb_courtyard_rect_3" data-type="pcb_courtyard_rect" data-pcb-layer="bottom" stroke-linejoin="round" fill="none" stroke="rgb(38, 233, 255)" stroke-width="1.3636363636363638"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="378.1818181818182" y="287.72727272727275" width="16.363636363636363" height="24.545454545454547" data-type="pcb_smtpad" data-pcb-layer="top"/><rect class="pcb-pad" fill="rgb(200, 52, 52)" x="378.1818181818182" y="287.72727272727275" width="16.363636363636363" height="24.545454545454547" data-type="pcb_smtpad" data-pcb-layer="top"/><polygon points="187.27272727272728,338.1818181818182 285.45454545454544,338.1818181818182 285.45454545454544,261.8181818181818 187.27272727272728,261.8181818181818" class="pcb-courtyard-outline pcb-courtyard-top" data-pcb-courtyard-outline-id="pcb_courtyard_outline_0" data-type="pcb_courtyard_outline" data-pcb-layer="top" fill="none" stroke="#FF00FF" stroke-width="1.3636363636363638" stroke-linejoin="round"/><rect x="-54.54545454545455" y="-40.90909090909091" width="109.0909090909091" height="81.81818181818181" class="pcb-courtyard-rect pcb-courtyard-top" data-pcb-courtyard-rect-id="pcb_courtyard_rect_0" data-type="pcb_courtyard_rect" data-pcb-layer="top" stroke-linejoin="round" transform="translate(400 300) rotate(-45)" fill="none" stroke="#FF00FF" stroke-width="1.3636363636363638"/><circle cx="410.90909090909093" cy="152.72727272727272" r="57.27272727272727" class="pcb-courtyard-circle pcb-courtyard-top" data-pcb-courtyard-circle-id="pcb_courtyard_circle_0" data-type="pcb_courtyard_circle" data-pcb-layer="top" fill="none" stroke="#FF00FF" stroke-width="1.3636363636363638"/><polygon points="187.27272727272728,338.1818181818182 285.45454545454544,338.1818181818182 285.45454545454544,261.8181818181818 187.27272727272728,261.8181818181818" class="pcb-courtyard-outline pcb-courtyard-top" data-pcb-courtyard-outline-id="pcb_courtyard_outline_1" data-type="pcb_courtyard_outline" data-pcb-layer="top" fill="none" stroke="#FF00FF" stroke-width="1.3636363636363638" stroke-linejoin="round"/><rect x="-54.54545454545455" y="-40.90909090909091" width="109.0909090909091" height="81.81818181818181" class="pcb-courtyard-rect pcb-courtyard-top" data-pcb-courtyard-rect-id="pcb_courtyard_rect_2" data-type="pcb_courtyard_rect" data-pcb-layer="top" stroke-linejoin="round" transform="translate(400 300) rotate(-45)" fill="none" stroke="#FF00FF" stroke-width="1.3636363636363638"/><circle cx="410.90909090909093" cy="152.72727272727272" r="57.27272727272727" class="pcb-courtyard-circle pcb-courtyard-top" data-pcb-courtyard-circle-id="pcb_courtyard_circle_1" data-type="pcb_courtyard_circle" data-pcb-layer="top" fill="none" stroke="#FF00FF" stroke-width="1.3636363636363638"/></svg>
@@ -18,6 +18,7 @@ test("test16 support pcb smtpad shapes", async () => {
18
18
  <smtpad portHints={["2"]} layer="top" coveredWithSolderMask={true} solderMaskMargin="0.06mm" shape="polygon" points={[{"x":0,"y":0},{"x":1,"y":0},{"x":0.5,"y":1}]} />
19
19
  <smtpad portHints={["3"]} pcbX="-1mm" pcbY="-2mm" layer="bottom" coveredWithSolderMask={true} solderMaskMargin="0.04mm" cornerRadius="0.15mm" solderMaskMarginLeft="0.01mm" solderMaskMarginTop="0.02mm" solderMaskMarginRight="0.03mm" solderMaskMarginBottom="0.05mm" width="2mm" height="0.6mm" pcbRotation="45deg" shape="rotated_rect" />
20
20
  <smtpad portHints={["4"]} pcbX="2.5mm" pcbY="-1.5mm" layer="bottom" coveredWithSolderMask={true} solderMaskMargin="0.05mm" rectBorderRadius="0.25mm" cornerRadius="0.2mm" solderMaskMarginLeft="0.11mm" solderMaskMarginTop="0.12mm" solderMaskMarginRight="0.13mm" solderMaskMarginBottom="0.14mm" width="1.8mm" height="0.9mm" shape="rect" />
21
+ <smtpad portHints={["5"]} pcbX="-2.4mm" pcbY="1.6mm" layer="top" coveredWithSolderMask={true} solderMaskMargin="0.07mm" width="2.2mm" height="0.9mm" radius="0.45mm" pcbRotation="30deg" shape="pill" />
21
22
  </footprint>}
22
23
  {...props}
23
24
  />
@@ -25,6 +26,7 @@ test("test16 support pcb smtpad shapes", async () => {
25
26
  `)
26
27
  expect(tscircuit).toContain(`pcbRotation="45deg"`)
27
28
  expect(tscircuit).toContain(`shape="rotated_rect"`)
29
+ expect(tscircuit).toContain(`pcbRotation="30deg"`)
28
30
 
29
31
  // The generator emits pcbRotation for consistency, but the current smtpad
30
32
  // runtime still expects ccwRotation for rotated_rect pads.
@@ -47,7 +49,7 @@ circuit.add(
47
49
  (elm): elm is PcbSmtPad => elm.type === "pcb_smtpad",
48
50
  )
49
51
 
50
- expect(renderedSmtPads).toHaveLength(8)
52
+ expect(renderedSmtPads).toHaveLength(10)
51
53
  expect(renderedSmtPads).toEqual(
52
54
  expect.arrayContaining([
53
55
  expect.objectContaining({
@@ -66,6 +68,17 @@ circuit.add(
66
68
  is_covered_with_solder_mask: true,
67
69
  soldermask_margin: 0.05,
68
70
  }),
71
+ expect.objectContaining({
72
+ port_hints: ["5"],
73
+ layer: "top",
74
+ shape: "rotated_pill",
75
+ ccw_rotation: 30,
76
+ radius: 0.45,
77
+ width: 2.2,
78
+ height: 0.9,
79
+ is_covered_with_solder_mask: true,
80
+ soldermask_margin: 0.07,
81
+ }),
69
82
  ]),
70
83
  )
71
84
 
@@ -167,4 +180,20 @@ const circuitJson: any = [
167
180
  soldermask_margin_bottom: 0.14,
168
181
  port_hints: ["4"],
169
182
  },
183
+ {
184
+ type: "pcb_smtpad",
185
+ pcb_smtpad_id: "rotated_pill_pad",
186
+ pcb_component_id: "pcb_generic_component_0",
187
+ shape: "rotated_pill",
188
+ x: -2.4,
189
+ y: 1.6,
190
+ width: 2.2,
191
+ height: 0.9,
192
+ radius: 0.45,
193
+ ccw_rotation: 30,
194
+ layer: "top",
195
+ is_covered_with_solder_mask: true,
196
+ soldermask_margin: 0.07,
197
+ port_hints: ["5"],
198
+ },
170
199
  ]
@@ -1,4 +1,5 @@
1
1
  import { expect, test } from "bun:test"
2
+ import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
2
3
  import { convertCircuitJsonToTscircuit } from "lib"
3
4
  import { runTscircuitCode } from "tscircuit"
4
5
 
@@ -13,10 +14,10 @@ test("test8 support courtyard elements", async () => {
13
14
  <chip
14
15
  footprint={<footprint>
15
16
  <smtpad portHints={["1"]} pcbX="-0.5mm" pcbY="0mm" layer="top" width="0.6mm" height="0.9mm" shape="rect" />
16
- <courtyardoutline outline={[{"x":-1.8,"y":-1.4},{"x":1.8,"y":-1.4},{"x":1.8,"y":1.4},{"x":-1.8,"y":1.4}]} layer="top" />
17
- <courtyardrect pcbX={0} pcbY={0} width={4} height={3} layer="top" />
18
- <courtyardrect pcbX={0.2} pcbY={0.2} width={4.6} height={3.6} layer="bottom" />
19
- <courtyardcircle pcbX={0.4} pcbY={-0.3} radius={2.1} layer="top" />
17
+ <courtyardoutline outline={[{"x":-7.8,"y":-1.4},{"x":-4.2,"y":-1.4},{"x":-4.2,"y":1.4},{"x":-7.8,"y":1.4}]} layer="top" />
18
+ <courtyardrect pcbX={0} pcbY={0} width={4} height={3} layer="top" pcbRotation="45deg" />
19
+ <courtyardrect pcbX={6.2} pcbY={0.2} width={4.6} height={3.6} layer="bottom" />
20
+ <courtyardcircle pcbX={0.4} pcbY={5.4} radius={2.1} layer="top" />
20
21
  </footprint>}
21
22
  {...props}
22
23
  />
@@ -57,6 +58,25 @@ circuit.add(
57
58
  expect(
58
59
  courtyardElements.filter((elm) => elm.type === "pcb_courtyard_circle"),
59
60
  ).toHaveLength(2)
61
+ expect(
62
+ courtyardElements.filter(
63
+ (elm) =>
64
+ elm.type === "pcb_courtyard_rect" &&
65
+ elm.layer === "top" &&
66
+ elm.center?.x === 0 &&
67
+ elm.center?.y === 0 &&
68
+ elm.width === 4 &&
69
+ elm.height === 3,
70
+ ),
71
+ ).toEqual([
72
+ expect.objectContaining({ ccw_rotation: 45 }),
73
+ expect.objectContaining({ ccw_rotation: 45 }),
74
+ ])
75
+
76
+ const pcbSvg = convertCircuitJsonToPcbSvg(renderedCircuitJson, {
77
+ showCourtyards: true,
78
+ })
79
+ await expect(pcbSvg).toMatchSvgSnapshot(import.meta.path, "courtyard-pcb")
60
80
  })
61
81
 
62
82
  const circuitJson: any = [
@@ -110,10 +130,10 @@ const circuitJson: any = [
110
130
  pcb_component_id: "pcb_generic_component_0",
111
131
  layer: "top",
112
132
  outline: [
113
- { x: -1.8, y: -1.4 },
114
- { x: 1.8, y: -1.4 },
115
- { x: 1.8, y: 1.4 },
116
- { x: -1.8, y: 1.4 },
133
+ { x: -7.8, y: -1.4 },
134
+ { x: -4.2, y: -1.4 },
135
+ { x: -4.2, y: 1.4 },
136
+ { x: -7.8, y: 1.4 },
117
137
  ],
118
138
  },
119
139
  {
@@ -124,12 +144,13 @@ const circuitJson: any = [
124
144
  width: 4,
125
145
  height: 3,
126
146
  layer: "top",
147
+ ccw_rotation: 45,
127
148
  },
128
149
  {
129
150
  type: "pcb_courtyard_rect",
130
151
  pcb_courtyard_rect_id: "pcb_courtyard_rect_1",
131
152
  pcb_component_id: "pcb_generic_component_0",
132
- center: { x: 0.2, y: 0.2 },
153
+ center: { x: 6.2, y: 0.2 },
133
154
  width: 4.6,
134
155
  height: 3.6,
135
156
  layer: "bottom",
@@ -138,7 +159,7 @@ const circuitJson: any = [
138
159
  type: "pcb_courtyard_circle",
139
160
  pcb_courtyard_circle_id: "pcb_courtyard_circle_0",
140
161
  pcb_component_id: "pcb_generic_component_0",
141
- center: { x: 0.4, y: -0.3 },
162
+ center: { x: 0.4, y: 5.4 },
142
163
  radius: 2.1,
143
164
  layer: "top",
144
165
  },
@@ -1,33 +0,0 @@
1
- import { mmStr } from "@tscircuit/mm"
2
-
3
- export const escapeJsxText = (value: unknown): string =>
4
- String(value ?? "").replace(/"/g, '\\"')
5
-
6
- export const formatMm = (value: number | undefined): string => mmStr(value ?? 0)
7
-
8
- export const formatOptionalMmAttr = (
9
- attrName: string,
10
- value: number | undefined,
11
- ): string => {
12
- if (value === undefined) return ""
13
- return ` ${attrName}="${mmStr(value)}"`
14
- }
15
-
16
- export const formatSolderMaskAttrs = (platedHole: {
17
- is_covered_with_solder_mask?: boolean
18
- soldermask_margin?: number
19
- }): string =>
20
- `${
21
- platedHole.is_covered_with_solder_mask === undefined
22
- ? ""
23
- : ` coveredWithSolderMask={${platedHole.is_covered_with_solder_mask}}`
24
- }${formatOptionalMmAttr("solderMaskMargin", platedHole.soldermask_margin)}`
25
-
26
- export const formatPcbRotationAttr = (
27
- rotation: string | number | undefined,
28
- attrName = "pcbRotation",
29
- ): string => {
30
- if (rotation === undefined) return ""
31
- const formatted = typeof rotation === "number" ? `${rotation}deg` : rotation
32
- return ` ${attrName}="${formatted}"`
33
- }