circuit-json-to-tscircuit 0.0.91 → 0.0.92

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 (65) hide show
  1. package/dist/{chunk-TYZGD7TY.js → chunk-GAMHGAGS.js} +1114 -198
  2. package/dist/cli/main.js +9 -2
  3. package/dist/lib/index.js +1 -1
  4. package/lib/board-conversion-context.ts +21 -6
  5. package/lib/board-conversion-types.ts +2 -0
  6. package/lib/board-converter/BoardConverterContext.ts +18 -0
  7. package/lib/board-converter/convert-board-circuit-json.ts +80 -0
  8. package/lib/board-converter/create-component-preserving-board-conversion-context.ts +98 -0
  9. package/lib/board-converter/get-board-child-runtime-names.ts +129 -0
  10. package/lib/board-converter/get-pcb-component-render-placement.ts +102 -0
  11. package/lib/board-converter/get-pin-labels.ts +99 -0
  12. package/lib/board-converter/get-schematic-components-for-pcb-component.ts +50 -0
  13. package/lib/board-converter/localize-pcb-component-elements.ts +270 -0
  14. package/lib/board-converter/stages/convert-board-cad-models.ts +36 -0
  15. package/lib/board-converter/stages/convert-board-nets.ts +19 -0
  16. package/lib/board-converter/stages/convert-board-properties.ts +49 -0
  17. package/lib/board-converter/stages/convert-board-schematic.ts +16 -0
  18. package/lib/board-converter/stages/convert-board-traces.ts +12 -0
  19. package/lib/board-converter/stages/convert-copper-pours.ts +12 -0
  20. package/lib/board-converter/stages/convert-pcb-components.ts +171 -0
  21. package/lib/board-converter/stages/convert-standalone-pcb-primitives.ts +46 -0
  22. package/lib/format-jsx-string-attribute.ts +9 -0
  23. package/lib/generate-board-cad-models-tsx/generate-cad-model-group-model-tsx.ts +41 -0
  24. package/lib/generate-board-cad-models-tsx/generate-cad-model-group-tsx.ts +8 -18
  25. package/lib/generate-board-cad-models-tsx/generate-cad-model-tsx.ts +6 -33
  26. package/lib/generate-board-cad-models-tsx/generate-jscad-cad-model-tsx.ts +26 -0
  27. package/lib/generate-board-cad-models-tsx/get-cad-model-groups.ts +3 -2
  28. package/lib/generate-board-cad-models-tsx/get-cad-model-placement-attributes.ts +50 -0
  29. package/lib/generate-board-cad-models-tsx/has-cad-model-source.ts +9 -0
  30. package/lib/generate-board-cad-models-tsx.ts +13 -2
  31. package/lib/generate-board-nets-tsx.ts +32 -2
  32. package/lib/generate-board-traces-tsx.ts +28 -0
  33. package/lib/generate-footprint-tsx/get-imported-port-hints.ts +10 -1
  34. package/lib/get-board-using-template.ts +3 -95
  35. package/lib/get-runtime-net-names.ts +93 -5
  36. package/package.json +8 -1
  37. package/tests/board-autorouted-pad-connectivity.test.tsx +153 -0
  38. package/tests/board-bottom-cad-model-rotation.test.ts +5 -5
  39. package/tests/board-bottom-jscad-cad-model.test.ts +67 -0
  40. package/tests/board-component-cad-models-on-multiple-layers.test.ts +81 -0
  41. package/tests/board-connected-net-aliases.test.ts +56 -0
  42. package/tests/board-duplicate-component-names.test.tsx +60 -0
  43. package/tests/board-grouped-jscad-cad-model.test.ts +135 -0
  44. package/tests/board-imported-board-name-collision.test.ts +74 -0
  45. package/tests/board-imported-cad-model-name-collision.test.ts +86 -0
  46. package/tests/board-jscad-cad-model.test.ts +83 -0
  47. package/tests/board-mixed-url-and-jscad-cad-models.test.ts +110 -0
  48. package/tests/board-near-zero-local-coordinates.test.ts +65 -0
  49. package/tests/board-net-endpoint-order.test.ts +135 -0
  50. package/tests/board-net-name-collision.test.ts +1 -1
  51. package/tests/board-net-routing-order.test.ts +50 -0
  52. package/tests/board-nonnumeric-port-placement.test.ts +130 -0
  53. package/tests/board-off-center-footprint-cad-model.test.ts +82 -0
  54. package/tests/board-rotated-pad-orientation.test.ts +120 -0
  55. package/tests/board-rotated-pill-cad-model-placement.test.ts +93 -0
  56. package/tests/board-standalone-primitives-do-not-obstruct.test.ts +34 -0
  57. package/tests/test20-board-conversion-drops-children.test.tsx +31 -30
  58. package/tests/ti-evms/__snapshots__/ti-drv8307evm-roundtrip-pcb-comparison.snap.svg +1 -1
  59. package/tests/ti-evms/__snapshots__/ti-drv8307evm-roundtrip.test.tsx.snap +4361 -4109
  60. package/tests/ti-evms/__snapshots__/ti-lm251772evm-pd-roundtrip-pcb-comparison.snap.svg +1 -1
  61. package/tests/ti-evms/__snapshots__/ti-lm251772evm-pd-roundtrip.test.tsx.snap +5557 -5171
  62. package/tests/ti-evms/__snapshots__/ti-lm5155evm-fly-roundtrip-pcb-comparison.snap.svg +1 -1
  63. package/tests/ti-evms/__snapshots__/ti-lmg342x-bb-evm-roundtrip-pcb-comparison.snap.svg +1 -1
  64. package/tests/ti-evms/__snapshots__/ti-lmg342x-bb-evm-roundtrip.test.tsx.snap +4592 -4226
  65. package/tests/ti-evms/ti-lm5155evm-fly-roundtrip.test.tsx +2823 -2608
@@ -1,19 +1,26 @@
1
- // lib/get-board-using-template.ts
2
- import { mmStr as mmStr9 } from "@tscircuit/mm";
3
- import { su as su21 } from "@tscircuit/soup-util";
4
-
5
1
  // lib/get-runtime-net-names.ts
6
2
  var getRuntimeNetNames = (circuitJson, sourceNetIdsRequiringSelector) => {
7
3
  const sourceNets = circuitJson.filter(
8
4
  (element) => element.type === "source_net"
9
5
  );
6
+ const canonicalSourceNetIdBySourceNetId = getCanonicalSourceNetIdBySourceNetId(circuitJson);
7
+ const sourceNetById = new Map(
8
+ sourceNets.map((sourceNet) => [sourceNet.source_net_id, sourceNet])
9
+ );
10
+ const canonicalSourceNets = sourceNets.filter(
11
+ (sourceNet) => canonicalSourceNetIdBySourceNetId.get(sourceNet.source_net_id) === sourceNet.source_net_id
12
+ );
10
13
  const sourceNetNamesRequiringSelector = /* @__PURE__ */ new Set();
11
- for (const sourceNet of sourceNets) {
12
- if (sourceNetIdsRequiringSelector.has(sourceNet.source_net_id)) {
13
- sourceNetNamesRequiringSelector.add(sourceNet.name);
14
+ for (const sourceNetId of sourceNetIdsRequiringSelector) {
15
+ const canonicalSourceNetId = canonicalSourceNetIdBySourceNetId.get(sourceNetId) ?? sourceNetId;
16
+ const canonicalSourceNet = sourceNetById.get(canonicalSourceNetId);
17
+ if (canonicalSourceNet) {
18
+ sourceNetNamesRequiringSelector.add(canonicalSourceNet.name);
14
19
  }
15
20
  }
16
- const sourceNetNames = [...new Set(sourceNets.map(({ name }) => name))];
21
+ const sourceNetNames = [
22
+ ...new Set(canonicalSourceNets.map(({ name }) => name))
23
+ ];
17
24
  const runtimeNameByOriginalName = /* @__PURE__ */ new Map();
18
25
  const usedRuntimeNetNames = /* @__PURE__ */ new Set();
19
26
  const namesThatRemainUnchanged = sourceNetNames.filter(
@@ -36,13 +43,74 @@ var getRuntimeNetNames = (circuitJson, sourceNetIdsRequiringSelector) => {
36
43
  }
37
44
  const runtimeNetNameBySourceNetId = /* @__PURE__ */ new Map();
38
45
  for (const sourceNet of sourceNets) {
46
+ const canonicalSourceNetId = canonicalSourceNetIdBySourceNetId.get(sourceNet.source_net_id) ?? sourceNet.source_net_id;
47
+ const canonicalSourceNet = sourceNetById.get(canonicalSourceNetId);
48
+ if (!canonicalSourceNet) continue;
39
49
  runtimeNetNameBySourceNetId.set(
40
50
  sourceNet.source_net_id,
41
- runtimeNameByOriginalName.get(sourceNet.name)
51
+ runtimeNameByOriginalName.get(canonicalSourceNet.name)
42
52
  );
43
53
  }
44
54
  return runtimeNetNameBySourceNetId;
45
55
  };
56
+ var getCanonicalSourceNetIdBySourceNetId = (circuitJson) => {
57
+ const parentSourceNetIdBySourceNetId = /* @__PURE__ */ new Map();
58
+ for (const element of circuitJson) {
59
+ if (element.type !== "source_net") continue;
60
+ parentSourceNetIdBySourceNetId.set(
61
+ element.source_net_id,
62
+ element.source_net_id
63
+ );
64
+ }
65
+ for (const element of circuitJson) {
66
+ if (element.type !== "source_trace") continue;
67
+ const sourceNetIds = element.connected_source_net_ids.filter(
68
+ (sourceNetId) => parentSourceNetIdBySourceNetId.has(sourceNetId)
69
+ );
70
+ const firstSourceNetId = sourceNetIds[0];
71
+ if (!firstSourceNetId) continue;
72
+ const canonicalSourceNetId = findCanonicalSourceNetId({
73
+ parentSourceNetIdBySourceNetId,
74
+ sourceNetId: firstSourceNetId
75
+ });
76
+ for (const sourceNetId of sourceNetIds.slice(1)) {
77
+ const aliasCanonicalSourceNetId = findCanonicalSourceNetId({
78
+ parentSourceNetIdBySourceNetId,
79
+ sourceNetId
80
+ });
81
+ if (aliasCanonicalSourceNetId === canonicalSourceNetId) continue;
82
+ parentSourceNetIdBySourceNetId.set(
83
+ aliasCanonicalSourceNetId,
84
+ canonicalSourceNetId
85
+ );
86
+ }
87
+ }
88
+ return new Map(
89
+ [...parentSourceNetIdBySourceNetId.keys()].map((sourceNetId) => [
90
+ sourceNetId,
91
+ findCanonicalSourceNetId({
92
+ parentSourceNetIdBySourceNetId,
93
+ sourceNetId
94
+ })
95
+ ])
96
+ );
97
+ };
98
+ var findCanonicalSourceNetId = ({
99
+ parentSourceNetIdBySourceNetId,
100
+ sourceNetId
101
+ }) => {
102
+ let currentSourceNetId = sourceNetId;
103
+ const visitedSourceNetIds = /* @__PURE__ */ new Set();
104
+ while (!visitedSourceNetIds.has(currentSourceNetId)) {
105
+ visitedSourceNetIds.add(currentSourceNetId);
106
+ const parentSourceNetId = parentSourceNetIdBySourceNetId.get(currentSourceNetId);
107
+ if (!parentSourceNetId || parentSourceNetId === currentSourceNetId) {
108
+ return currentSourceNetId;
109
+ }
110
+ currentSourceNetId = parentSourceNetId;
111
+ }
112
+ return currentSourceNetId;
113
+ };
46
114
  var getUniqueRuntimeNetName = (sourceNetName, usedRuntimeNetNames) => {
47
115
  const baseName = getRuntimeNetNameBase(sourceNetName);
48
116
  let candidate = baseName;
@@ -59,8 +127,11 @@ var getRuntimeNetNameBase = (sourceNetName) => {
59
127
  };
60
128
 
61
129
  // lib/board-conversion-context.ts
62
- var PCB_CHIP_NAME = "ImportedBoard";
63
- var createBoardConversionContext = (circuitJson) => {
130
+ var DEFAULT_PCB_CHIP_NAME = "ImportedBoard";
131
+ var createBoardConversionContext = ({
132
+ circuitJson,
133
+ pcbChipName = DEFAULT_PCB_CHIP_NAME
134
+ }) => {
64
135
  const sourceNetIdsRequiringSelector = getSourceNetIdsRequiringSelector(circuitJson);
65
136
  const runtimeNetNameBySourceNetId = getRuntimeNetNames(
66
137
  circuitJson,
@@ -78,22 +149,29 @@ var createBoardConversionContext = (circuitJson) => {
78
149
  circuitJson,
79
150
  runtimeNetNameBySourceNetId
80
151
  });
152
+ const pcbPortSelectorsBySourcePortId = /* @__PURE__ */ new Map();
81
153
  const pcbPortSelectorsByRuntimeNetName = /* @__PURE__ */ new Map();
82
154
  for (const [pcbPortId, sourcePortId] of sourcePortIdByPcbPortId) {
83
155
  const portHint = portHintByPcbPortId.get(pcbPortId);
84
156
  if (!portHint) continue;
157
+ const selector = `.${pcbChipName} > .${portHint}`;
158
+ const sourcePortSelectors = pcbPortSelectorsBySourcePortId.get(sourcePortId) ?? [];
159
+ sourcePortSelectors.push(selector);
160
+ pcbPortSelectorsBySourcePortId.set(sourcePortId, sourcePortSelectors);
85
161
  for (const runtimeNetName of runtimeNetNamesBySourcePortId.get(
86
162
  sourcePortId
87
163
  ) ?? []) {
88
164
  const selectors = pcbPortSelectorsByRuntimeNetName.get(runtimeNetName) ?? [];
89
- selectors.push(`.${PCB_CHIP_NAME} > .${portHint}`);
165
+ selectors.push(selector);
90
166
  pcbPortSelectorsByRuntimeNetName.set(runtimeNetName, selectors);
91
167
  }
92
168
  }
93
169
  return {
94
- pcbChipName: PCB_CHIP_NAME,
170
+ pcbChipName,
171
+ pcbPortIdsWithUnambiguousComponentSelectors: /* @__PURE__ */ new Set(),
95
172
  portHintByPcbPortId,
96
173
  runtimeNetNameBySourceNetId,
174
+ pcbPortSelectorsBySourcePortId,
97
175
  pcbPortSelectorsByRuntimeNetName
98
176
  };
99
177
  };
@@ -140,6 +218,206 @@ var getRuntimeNetNamesBySourcePortId = ({
140
218
  return result;
141
219
  };
142
220
 
221
+ // lib/board-converter/create-component-preserving-board-conversion-context.ts
222
+ function createComponentPreservingBoardConversionContext({
223
+ circuitJson,
224
+ pcbChipName,
225
+ pcbComponentRuntimeNameByPcbComponentId
226
+ }) {
227
+ const context = createBoardConversionContext({ circuitJson, pcbChipName });
228
+ const pcbComponentsById = /* @__PURE__ */ new Map();
229
+ for (const element of circuitJson) {
230
+ if (element.type === "pcb_component") {
231
+ pcbComponentsById.set(element.pcb_component_id, element);
232
+ }
233
+ }
234
+ const pcbPortSelectorsBySourcePortId = /* @__PURE__ */ new Map();
235
+ const pcbPortIdsWithUnambiguousComponentSelectors = new Set(
236
+ circuitJson.flatMap((element) => {
237
+ if (element.type !== "pcb_port") return [];
238
+ const pcbComponent = element.pcb_component_id ? pcbComponentsById.get(element.pcb_component_id) : void 0;
239
+ return pcbComponent && pcbComponentRuntimeNameByPcbComponentId.has(
240
+ pcbComponent.pcb_component_id
241
+ ) ? [element.pcb_port_id] : [];
242
+ })
243
+ );
244
+ for (const element of circuitJson) {
245
+ if (element.type !== "pcb_port") continue;
246
+ const pcbComponent = element.pcb_component_id ? pcbComponentsById.get(element.pcb_component_id) : void 0;
247
+ const portHint = context.portHintByPcbPortId.get(element.pcb_port_id);
248
+ if (!portHint) continue;
249
+ const componentName = pcbComponent ? pcbComponentRuntimeNameByPcbComponentId.get(
250
+ pcbComponent.pcb_component_id
251
+ ) : void 0;
252
+ const selectors = pcbPortSelectorsBySourcePortId.get(element.source_port_id) ?? [];
253
+ selectors.push(`.${componentName ?? context.pcbChipName} > .${portHint}`);
254
+ pcbPortSelectorsBySourcePortId.set(element.source_port_id, selectors);
255
+ }
256
+ const pcbPortSelectorsByRuntimeNetName = /* @__PURE__ */ new Map();
257
+ for (const element of circuitJson) {
258
+ if (element.type !== "source_trace") continue;
259
+ for (const sourceNetId of element.connected_source_net_ids) {
260
+ const runtimeNetName = context.runtimeNetNameBySourceNetId.get(sourceNetId);
261
+ if (!runtimeNetName) continue;
262
+ const selectors = pcbPortSelectorsByRuntimeNetName.get(runtimeNetName) ?? [];
263
+ for (const sourcePortId of element.connected_source_port_ids) {
264
+ for (const selector of pcbPortSelectorsBySourcePortId.get(
265
+ sourcePortId
266
+ ) ?? []) {
267
+ if (!selectors.includes(selector)) selectors.push(selector);
268
+ }
269
+ }
270
+ pcbPortSelectorsByRuntimeNetName.set(runtimeNetName, selectors);
271
+ }
272
+ }
273
+ return {
274
+ ...context,
275
+ pcbPortIdsWithUnambiguousComponentSelectors,
276
+ pcbPortSelectorsBySourcePortId,
277
+ pcbPortSelectorsByRuntimeNetName
278
+ };
279
+ }
280
+
281
+ // lib/generate-board-cad-models-tsx/get-cad-model-url.ts
282
+ function getCadModelUrl(cadComponent) {
283
+ if (cadComponent.model_glb_url) {
284
+ return { format: "glb", url: cadComponent.model_glb_url };
285
+ }
286
+ if (cadComponent.model_gltf_url) {
287
+ return { format: "gltf", url: cadComponent.model_gltf_url };
288
+ }
289
+ if (cadComponent.model_obj_url) {
290
+ return { format: "obj", url: cadComponent.model_obj_url };
291
+ }
292
+ if (cadComponent.model_step_url) {
293
+ return { format: "step", url: cadComponent.model_step_url };
294
+ }
295
+ if (cadComponent.model_stl_url) {
296
+ return { format: "stl", url: cadComponent.model_stl_url };
297
+ }
298
+ if (cadComponent.model_wrl_url) {
299
+ return { format: "wrl", url: cadComponent.model_wrl_url };
300
+ }
301
+ return void 0;
302
+ }
303
+
304
+ // lib/generate-board-cad-models-tsx/has-cad-model-source.ts
305
+ function hasCadModelSource(cadComponent) {
306
+ return cadComponent.model_jscad !== void 0 || getCadModelUrl(cadComponent) !== void 0;
307
+ }
308
+
309
+ // lib/generate-board-cad-models-tsx/get-cad-model-groups.ts
310
+ function getCadModelGroups(circuitJson) {
311
+ const pcbComponentById = /* @__PURE__ */ new Map();
312
+ for (const element of circuitJson) {
313
+ if (element.type === "pcb_component") {
314
+ pcbComponentById.set(element.pcb_component_id, element);
315
+ }
316
+ }
317
+ const cadModelGroupByKey = /* @__PURE__ */ new Map();
318
+ for (const element of circuitJson) {
319
+ if (element.type !== "cad_component" || !hasCadModelSource(element))
320
+ continue;
321
+ const pcbComponent = element.pcb_component_id ? pcbComponentById.get(element.pcb_component_id) : void 0;
322
+ const layer = (element.layer ?? pcbComponent?.layer) === "bottom" ? "bottom" : "top";
323
+ let parentPlacement = {
324
+ center: element.position,
325
+ ccwRotationDegrees: 0,
326
+ layer
327
+ };
328
+ let cadModelGroupKey = element.cad_component_id;
329
+ if (pcbComponent !== void 0) {
330
+ parentPlacement = {
331
+ center: pcbComponent.center,
332
+ ccwRotationDegrees: pcbComponent.rotation,
333
+ layer
334
+ };
335
+ cadModelGroupKey = `${pcbComponent.pcb_component_id}:${layer}`;
336
+ }
337
+ const cadModelGroup = cadModelGroupByKey.get(cadModelGroupKey) ?? {
338
+ cadComponents: [],
339
+ parentPlacement
340
+ };
341
+ cadModelGroup.cadComponents.push(element);
342
+ cadModelGroupByKey.set(cadModelGroupKey, cadModelGroup);
343
+ }
344
+ return [...cadModelGroupByKey.values()];
345
+ }
346
+
347
+ // lib/board-converter/get-board-child-runtime-names.ts
348
+ var getUnusedRuntimeName = ({
349
+ preferredRuntimeName,
350
+ usedRuntimeNames
351
+ }) => {
352
+ if (!usedRuntimeNames.has(preferredRuntimeName)) return preferredRuntimeName;
353
+ let duplicateNumber = 2;
354
+ while (usedRuntimeNames.has(`${preferredRuntimeName}_${duplicateNumber}`)) {
355
+ duplicateNumber += 1;
356
+ }
357
+ return `${preferredRuntimeName}_${duplicateNumber}`;
358
+ };
359
+ var getBoardChildRuntimeNames = (circuitJson) => {
360
+ const sourceComponentBySourceComponentId = /* @__PURE__ */ new Map();
361
+ for (const element of circuitJson) {
362
+ if (element.type === "source_component" && !sourceComponentBySourceComponentId.has(element.source_component_id)) {
363
+ sourceComponentBySourceComponentId.set(
364
+ element.source_component_id,
365
+ element
366
+ );
367
+ }
368
+ }
369
+ const usedRuntimeNames = /* @__PURE__ */ new Set();
370
+ const pcbComponentRuntimeNameByPcbComponentId = /* @__PURE__ */ new Map();
371
+ const pcbComponents = circuitJson.filter(
372
+ (element) => element.type === "pcb_component"
373
+ );
374
+ for (const [pcbComponentIndex, pcbComponent] of pcbComponents.entries()) {
375
+ const sourceComponent = pcbComponent.source_component_id ? sourceComponentBySourceComponentId.get(pcbComponent.source_component_id) : void 0;
376
+ const preferredRuntimeName = sourceComponent?.name ?? `imported_component_${pcbComponentIndex + 1}`;
377
+ const runtimeName = getUnusedRuntimeName({
378
+ preferredRuntimeName,
379
+ usedRuntimeNames
380
+ });
381
+ usedRuntimeNames.add(runtimeName);
382
+ pcbComponentRuntimeNameByPcbComponentId.set(
383
+ pcbComponent.pcb_component_id,
384
+ runtimeName
385
+ );
386
+ }
387
+ const pcbChipName = getUnusedRuntimeName({
388
+ preferredRuntimeName: DEFAULT_PCB_CHIP_NAME,
389
+ usedRuntimeNames
390
+ });
391
+ usedRuntimeNames.add(pcbChipName);
392
+ const standaloneCadModelRuntimeNameByCadComponentId = /* @__PURE__ */ new Map();
393
+ const standaloneCadModelGroups = getCadModelGroups(circuitJson).filter(
394
+ (cadModelGroup) => cadModelGroup.cadComponents.every(
395
+ (cadComponent) => cadComponent.pcb_component_id === void 0
396
+ )
397
+ );
398
+ for (const [
399
+ groupIndex,
400
+ cadModelGroup
401
+ ] of standaloneCadModelGroups.entries()) {
402
+ const firstCadComponent = cadModelGroup.cadComponents[0];
403
+ if (!firstCadComponent) continue;
404
+ const runtimeName = getUnusedRuntimeName({
405
+ preferredRuntimeName: `ImportedCadModel${groupIndex + 1}`,
406
+ usedRuntimeNames
407
+ });
408
+ usedRuntimeNames.add(runtimeName);
409
+ standaloneCadModelRuntimeNameByCadComponentId.set(
410
+ firstCadComponent.cad_component_id,
411
+ runtimeName
412
+ );
413
+ }
414
+ return {
415
+ pcbChipName,
416
+ pcbComponentRuntimeNameByPcbComponentId,
417
+ standaloneCadModelRuntimeNameByCadComponentId
418
+ };
419
+ };
420
+
143
421
  // lib/generate-board-cad-models-tsx/format-point-prop.ts
144
422
  function formatPointProp(point) {
145
423
  return `{ x: ${point.x}, y: ${point.y}, z: ${point.z} }`;
@@ -192,54 +470,18 @@ function getCadModelPlacement({
192
470
  };
193
471
  }
194
472
 
195
- // lib/generate-board-cad-models-tsx/get-cad-model-url.ts
196
- function getCadModelUrl(cadComponent) {
197
- if (cadComponent.model_glb_url) {
198
- return { format: "glb", url: cadComponent.model_glb_url };
199
- }
200
- if (cadComponent.model_gltf_url) {
201
- return { format: "gltf", url: cadComponent.model_gltf_url };
202
- }
203
- if (cadComponent.model_obj_url) {
204
- return { format: "obj", url: cadComponent.model_obj_url };
205
- }
206
- if (cadComponent.model_step_url) {
207
- return { format: "step", url: cadComponent.model_step_url };
208
- }
209
- if (cadComponent.model_stl_url) {
210
- return { format: "stl", url: cadComponent.model_stl_url };
211
- }
212
- if (cadComponent.model_wrl_url) {
213
- return { format: "wrl", url: cadComponent.model_wrl_url };
214
- }
215
- return void 0;
216
- }
217
-
218
- // lib/generate-board-cad-models-tsx/get-model-url-with-format-hint.ts
219
- function getModelUrlWithFormatHint({
220
- format,
221
- url
222
- }) {
223
- const pathWithoutQuery = url.split(/[?#]/u)[0] ?? "";
224
- if (pathWithoutQuery.toLowerCase().endsWith(`.${format}`)) return url;
225
- return `${url}#ext=${format}`;
226
- }
227
-
228
- // lib/generate-board-cad-models-tsx/generate-cad-model-tsx.ts
229
- function generateCadModelTsx({
473
+ // lib/generate-board-cad-models-tsx/get-cad-model-placement-attributes.ts
474
+ function getCadModelPlacementAttributes({
230
475
  cadComponent,
231
476
  parentPlacement,
232
477
  pcbBoard
233
478
  }) {
234
- const modelUrl = getCadModelUrl(cadComponent);
235
- if (!modelUrl) return void 0;
236
479
  const placement = getCadModelPlacement({
237
480
  cadComponent,
238
481
  parentPlacement,
239
482
  pcbBoard
240
483
  });
241
484
  const attributes = [
242
- `modelUrl={${JSON.stringify(getModelUrlWithFormatHint(modelUrl))}}`,
243
485
  `positionOffset={${formatPointProp(placement.positionOffset)}}`,
244
486
  `rotationOffset={${formatPointProp(placement.ccwRotationOffsetDegrees)}}`
245
487
  ];
@@ -265,32 +507,96 @@ function generateCadModelTsx({
265
507
  if (cadComponent.show_as_translucent_model === true) {
266
508
  attributes.push("showAsTranslucentModel");
267
509
  }
510
+ return attributes;
511
+ }
512
+
513
+ // lib/generate-board-cad-models-tsx/get-model-url-with-format-hint.ts
514
+ function getModelUrlWithFormatHint({
515
+ format,
516
+ url
517
+ }) {
518
+ const pathWithoutQuery = url.split(/[?#]/u)[0] ?? "";
519
+ if (pathWithoutQuery.toLowerCase().endsWith(`.${format}`)) return url;
520
+ return `${url}#ext=${format}`;
521
+ }
522
+
523
+ // lib/generate-board-cad-models-tsx/generate-cad-model-tsx.ts
524
+ function generateCadModelTsx({
525
+ cadComponent,
526
+ parentPlacement,
527
+ pcbBoard
528
+ }) {
529
+ const modelUrl = getCadModelUrl(cadComponent);
530
+ if (!modelUrl) return void 0;
531
+ const attributes = [
532
+ `modelUrl={${JSON.stringify(getModelUrlWithFormatHint(modelUrl))}}`,
533
+ ...getCadModelPlacementAttributes({
534
+ cadComponent,
535
+ parentPlacement,
536
+ pcbBoard
537
+ })
538
+ ];
268
539
  return `<cadmodel ${attributes.join(" ")} />`;
269
540
  }
270
541
 
271
- // lib/generate-board-cad-models-tsx/generate-cad-model-group-tsx.ts
272
- function generateCadModelGroupTsx({
273
- cadModelGroup,
274
- groupIndex,
542
+ // lib/generate-board-cad-models-tsx/generate-jscad-cad-model-tsx.ts
543
+ function generateJscadCadModelTsx({
544
+ cadComponent,
545
+ parentPlacement,
275
546
  pcbBoard
276
547
  }) {
277
- const { cadComponents, parentPlacement } = cadModelGroup;
278
- const modelElements = cadComponents.flatMap((cadComponent) => {
279
- const modelElement = generateCadModelTsx({
548
+ if (cadComponent.model_jscad === void 0) return void 0;
549
+ const attributes = [
550
+ `jscad={${JSON.stringify(cadComponent.model_jscad)}}`,
551
+ ...getCadModelPlacementAttributes({
280
552
  cadComponent,
281
553
  parentPlacement,
282
554
  pcbBoard
283
- });
284
- return modelElement ? [modelElement] : [];
555
+ })
556
+ ];
557
+ return `<cadmodel ${attributes.join(" ")} />`;
558
+ }
559
+
560
+ // lib/generate-board-cad-models-tsx/generate-cad-model-group-model-tsx.ts
561
+ var generateCadModelElementsTsx = ({
562
+ cadModelGroup,
563
+ pcbBoard
564
+ }) => cadModelGroup.cadComponents.flatMap((cadComponent) => {
565
+ const modelElementGenerator = cadComponent.model_jscad === void 0 ? generateCadModelTsx : generateJscadCadModelTsx;
566
+ const modelElement = modelElementGenerator({
567
+ cadComponent,
568
+ parentPlacement: cadModelGroup.parentPlacement,
569
+ pcbBoard
570
+ });
571
+ return modelElement ? [modelElement] : [];
572
+ });
573
+ function generateCadModelGroupModelTsx({
574
+ cadModelGroup,
575
+ pcbBoard
576
+ }) {
577
+ const modelElements = generateCadModelElementsTsx({
578
+ cadModelGroup,
579
+ pcbBoard
285
580
  });
286
581
  if (modelElements.length === 0) return void 0;
287
- let cadModelTsx = modelElements[0];
288
- if (modelElements.length > 1) {
289
- cadModelTsx = `<cadassembly>${modelElements.join("\n")}</cadassembly>`;
290
- }
582
+ if (modelElements.length === 1) return modelElements[0];
583
+ return `<cadassembly>${modelElements.join("\n")}</cadassembly>`;
584
+ }
585
+
586
+ // lib/generate-board-cad-models-tsx/generate-cad-model-group-tsx.ts
587
+ function generateCadModelGroupTsx({
588
+ cadModelGroup,
589
+ pcbBoard,
590
+ runtimeName
591
+ }) {
592
+ const { parentPlacement } = cadModelGroup;
593
+ const cadModelTsx = generateCadModelGroupModelTsx({
594
+ cadModelGroup,
595
+ pcbBoard
596
+ });
291
597
  if (!cadModelTsx) return void 0;
292
598
  const attributes = [
293
- `name={${JSON.stringify(`ImportedCadModel${groupIndex + 1}`)}}`,
599
+ `name={${JSON.stringify(runtimeName)}}`,
294
600
  `pcbX={${parentPlacement.center.x}}`,
295
601
  `pcbY={${parentPlacement.center.y}}`,
296
602
  `pcbRotation="${parentPlacement.ccwRotationDegrees}deg"`,
@@ -303,71 +609,131 @@ function generateCadModelGroupTsx({
303
609
  return `<chip ${attributes.join(" ")} />`;
304
610
  }
305
611
 
306
- // lib/generate-board-cad-models-tsx/get-cad-model-groups.ts
307
- function getCadModelGroups(circuitJson) {
308
- const pcbComponentById = /* @__PURE__ */ new Map();
309
- for (const element of circuitJson) {
310
- if (element.type === "pcb_component") {
311
- pcbComponentById.set(element.pcb_component_id, element);
312
- }
313
- }
314
- const cadModelGroupByKey = /* @__PURE__ */ new Map();
315
- for (const element of circuitJson) {
316
- if (element.type !== "cad_component" || !getCadModelUrl(element)) continue;
317
- const pcbComponent = element.pcb_component_id ? pcbComponentById.get(element.pcb_component_id) : void 0;
318
- const layer = (element.layer ?? pcbComponent?.layer) === "bottom" ? "bottom" : "top";
319
- let parentPlacement = {
320
- center: element.position,
321
- ccwRotationDegrees: 0,
322
- layer
323
- };
324
- let cadModelGroupKey = element.cad_component_id;
325
- if (pcbComponent !== void 0) {
326
- parentPlacement = {
327
- center: pcbComponent.center,
328
- ccwRotationDegrees: pcbComponent.rotation,
329
- layer
330
- };
331
- cadModelGroupKey = `${pcbComponent.pcb_component_id}:${layer}`;
332
- }
333
- const cadModelGroup = cadModelGroupByKey.get(cadModelGroupKey) ?? {
334
- cadComponents: [],
335
- parentPlacement
336
- };
337
- cadModelGroup.cadComponents.push(element);
338
- cadModelGroupByKey.set(cadModelGroupKey, cadModelGroup);
339
- }
340
- return [...cadModelGroupByKey.values()];
341
- }
342
-
343
- // lib/generate-board-cad-models-tsx.ts
344
- function generateBoardCadModelsTsx({
612
+ // lib/board-converter/stages/convert-board-cad-models.ts
613
+ var convertBoardCadModels = ({
345
614
  circuitJson,
346
- pcbBoard
347
- }) {
348
- if (!pcbBoard) return [];
349
- return getCadModelGroups(circuitJson).flatMap((cadModelGroup, groupIndex) => {
615
+ pcbBoard,
616
+ boardChildren,
617
+ standaloneCadModelRuntimeNameByCadComponentId
618
+ }) => {
619
+ const standaloneCadModelGroups = getCadModelGroups(circuitJson).filter(
620
+ (cadModelGroup) => cadModelGroup.cadComponents.every(
621
+ (cadComponent) => cadComponent.pcb_component_id === void 0
622
+ )
623
+ );
624
+ for (const cadModelGroup of standaloneCadModelGroups) {
625
+ const firstCadComponent = cadModelGroup.cadComponents[0];
626
+ if (!firstCadComponent) continue;
627
+ const runtimeName = standaloneCadModelRuntimeNameByCadComponentId.get(
628
+ firstCadComponent.cad_component_id
629
+ );
630
+ if (!runtimeName) {
631
+ throw new Error(
632
+ `Missing runtime name for standalone CAD model ${firstCadComponent.cad_component_id}`
633
+ );
634
+ }
350
635
  const cadModelGroupTsx = generateCadModelGroupTsx({
351
636
  cadModelGroup,
352
- groupIndex,
353
- pcbBoard
637
+ pcbBoard,
638
+ runtimeName
354
639
  });
355
- return cadModelGroupTsx ? [cadModelGroupTsx] : [];
356
- });
357
- }
640
+ if (cadModelGroupTsx) boardChildren.push(cadModelGroupTsx);
641
+ }
642
+ };
358
643
 
359
644
  // lib/generate-board-nets-tsx.ts
360
- var generateBoardNetsTsx = (context) => {
361
- const runtimeNetNames = new Set(context.runtimeNetNameBySourceNetId.values());
362
- return [...runtimeNetNames].map((runtimeNetName) => {
645
+ var generateBoardNetsTsx = (circuitJson, context) => {
646
+ const runtimeNetNames = getRuntimeNetNamesInTraceOrder(circuitJson, context);
647
+ return runtimeNetNames.map((runtimeNetName) => {
363
648
  const selectors = context.pcbPortSelectorsByRuntimeNetName.get(runtimeNetName) ?? [];
364
649
  const connectsTo = selectors.length ? ` connectsTo={${JSON.stringify(selectors)}}` : "";
365
650
  return `<net name={${JSON.stringify(runtimeNetName)}}${connectsTo} />`;
366
651
  });
367
652
  };
653
+ var getRuntimeNetNamesInTraceOrder = (circuitJson, context) => {
654
+ const orderedNames = [];
655
+ const addedNames = /* @__PURE__ */ new Set();
656
+ for (const element of circuitJson) {
657
+ if (element.type !== "source_trace") continue;
658
+ for (const sourceNetId of element.connected_source_net_ids) {
659
+ const runtimeNetName = context.runtimeNetNameBySourceNetId.get(sourceNetId);
660
+ if (!runtimeNetName || addedNames.has(runtimeNetName)) continue;
661
+ orderedNames.push(runtimeNetName);
662
+ addedNames.add(runtimeNetName);
663
+ }
664
+ }
665
+ for (const runtimeNetName of context.runtimeNetNameBySourceNetId.values()) {
666
+ if (addedNames.has(runtimeNetName)) continue;
667
+ orderedNames.push(runtimeNetName);
668
+ addedNames.add(runtimeNetName);
669
+ }
670
+ return orderedNames;
671
+ };
368
672
 
369
- // lib/generate-board-schematic-sheet-tsx.ts
673
+ // lib/board-converter/stages/convert-board-nets.ts
674
+ var convertBoardNets = ({
675
+ boardChildren,
676
+ boardProps,
677
+ circuitJson,
678
+ footprintConversionContext
679
+ }) => {
680
+ boardChildren.push(
681
+ ...generateBoardNetsTsx(circuitJson, footprintConversionContext)
682
+ );
683
+ if (circuitJson.some((element) => element.type === "pcb_trace") && !boardProps.includes("routingDisabled")) {
684
+ boardProps.push("routingDisabled");
685
+ }
686
+ };
687
+
688
+ // lib/board-converter/stages/convert-board-properties.ts
370
689
  import { mmStr } from "@tscircuit/mm";
690
+ var convertBoardProperties = ({
691
+ pcbBoard,
692
+ boardProps
693
+ }) => {
694
+ if (pcbBoard.center.x !== 0) {
695
+ boardProps.push(`pcbX={${pcbBoard.center.x}}`);
696
+ }
697
+ if (pcbBoard.center.y !== 0) {
698
+ boardProps.push(`pcbY={${pcbBoard.center.y}}`);
699
+ }
700
+ if (pcbBoard.width !== void 0) {
701
+ boardProps.push(`width="${mmStr(pcbBoard.width)}"`);
702
+ }
703
+ if (pcbBoard.height !== void 0) {
704
+ boardProps.push(`height="${mmStr(pcbBoard.height)}"`);
705
+ }
706
+ if (pcbBoard.outline && pcbBoard.outline.length > 0) {
707
+ const points = pcbBoard.outline.map((point) => `{ x: ${point.x}, y: ${point.y} }`).join(", ");
708
+ boardProps.push(`outline={[${points}]}`);
709
+ }
710
+ if (pcbBoard.thickness !== void 0) {
711
+ boardProps.push(`thickness="${mmStr(pcbBoard.thickness)}"`);
712
+ }
713
+ if (pcbBoard.num_layers !== void 0) {
714
+ boardProps.push(`layers={${pcbBoard.num_layers}}`);
715
+ }
716
+ if (pcbBoard.material !== void 0) {
717
+ boardProps.push(`material="${pcbBoard.material}"`);
718
+ }
719
+ if (pcbBoard.solder_mask_color !== void 0) {
720
+ boardProps.push(`solderMaskColor="${pcbBoard.solder_mask_color}"`);
721
+ }
722
+ if (pcbBoard.silkscreen_color !== void 0) {
723
+ boardProps.push(`silkscreenColor="${pcbBoard.silkscreen_color}"`);
724
+ }
725
+ if (pcbBoard.anchor_position !== void 0) {
726
+ boardProps.push(
727
+ `boardAnchorPosition={{ x: ${pcbBoard.anchor_position.x}, y: ${pcbBoard.anchor_position.y} }}`
728
+ );
729
+ }
730
+ if (pcbBoard.anchor_alignment !== void 0) {
731
+ boardProps.push(`boardAnchorAlignment="${pcbBoard.anchor_alignment}"`);
732
+ }
733
+ };
734
+
735
+ // lib/generate-board-schematic-sheet-tsx.ts
736
+ import { mmStr as mmStr2 } from "@tscircuit/mm";
371
737
  var DEFAULT_SHEET_DIMENSIONS_MM = {
372
738
  a4: { width: 297, height: 210 },
373
739
  ansi_b: { width: 431.8, height: 279.4 }
@@ -391,8 +757,8 @@ var generateBoardSchematicSheetTsx = ({
391
757
  const defaultDimensions = DEFAULT_SHEET_DIMENSIONS_MM[sheet.sheet_size ?? "a4"];
392
758
  const sheetWidth = sheet.sheet_width ?? defaultDimensions.width;
393
759
  const sheetHeight = sheet.sheet_height ?? defaultDimensions.height;
394
- attrs.push(`sheetWidth="${mmStr(sheetWidth)}"`);
395
- attrs.push(`sheetHeight="${mmStr(sheetHeight)}"`);
760
+ attrs.push(`sheetWidth="${mmStr2(sheetWidth)}"`);
761
+ attrs.push(`sheetHeight="${mmStr2(sheetHeight)}"`);
396
762
  return `<schematicsheet ${attrs.join(" ")}>
397
763
  ${schematicElements.join("\n")}
398
764
  </schematicsheet>`;
@@ -1589,6 +1955,51 @@ var generateBoardSchematicElements = (circuitJson) => {
1589
1955
  ];
1590
1956
  };
1591
1957
 
1958
+ // lib/board-converter/stages/convert-board-schematic.ts
1959
+ var convertBoardSchematic = ({
1960
+ boardChildren,
1961
+ circuitJson
1962
+ }) => {
1963
+ const schematicElements = generateBoardSchematicElements(circuitJson);
1964
+ boardChildren.push(
1965
+ generateBoardSchematicSheetTsx({
1966
+ circuitJson,
1967
+ schematicElements
1968
+ })
1969
+ );
1970
+ };
1971
+
1972
+ // lib/generate-board-traces-tsx.ts
1973
+ function generateBoardTracesTsx(circuitJson, context) {
1974
+ return circuitJson.flatMap((element) => {
1975
+ if (element.type !== "source_trace") return [];
1976
+ const portSelectors = element.connected_source_port_ids.flatMap(
1977
+ (sourcePortId) => context.pcbPortSelectorsBySourcePortId.get(sourcePortId) ?? []
1978
+ );
1979
+ const netSelectors = [
1980
+ ...new Set(
1981
+ element.connected_source_net_ids.flatMap((sourceNetId) => {
1982
+ const runtimeNetName = context.runtimeNetNameBySourceNetId.get(sourceNetId);
1983
+ return runtimeNetName ? [`net.${runtimeNetName}`] : [];
1984
+ })
1985
+ )
1986
+ ];
1987
+ const path = [...portSelectors, ...netSelectors];
1988
+ return path.length >= 2 ? [`<trace path={${JSON.stringify(path)}} />`] : [];
1989
+ });
1990
+ }
1991
+
1992
+ // lib/board-converter/stages/convert-board-traces.ts
1993
+ var convertBoardTraces = ({
1994
+ boardChildren,
1995
+ circuitJson,
1996
+ footprintConversionContext
1997
+ }) => {
1998
+ boardChildren.push(
1999
+ ...generateBoardTracesTsx(circuitJson, footprintConversionContext)
2000
+ );
2001
+ };
2002
+
1592
2003
  // lib/generate-copper-pours-tsx.ts
1593
2004
  import { su as su6 } from "@tscircuit/soup-util";
1594
2005
 
@@ -1798,6 +2209,26 @@ var getUnassignedCopperPourNetName = ({
1798
2209
  return name;
1799
2210
  };
1800
2211
 
2212
+ // lib/board-converter/stages/convert-copper-pours.ts
2213
+ var convertCopperPours = ({
2214
+ boardChildren,
2215
+ circuitJson,
2216
+ footprintConversionContext
2217
+ }) => {
2218
+ boardChildren.push(
2219
+ ...generateCopperPoursTsx(circuitJson, footprintConversionContext)
2220
+ );
2221
+ };
2222
+
2223
+ // lib/format-jsx-string-attribute.ts
2224
+ var formatJsxStringAttribute = (text) => {
2225
+ const stringText = String(text ?? "");
2226
+ if (!/["\\\r\n\t{}<&>]/.test(stringText)) {
2227
+ return `"${stringText}"`;
2228
+ }
2229
+ return `{${JSON.stringify(stringText)}}`;
2230
+ };
2231
+
1801
2232
  // lib/generate-footprint-tsx/convert-copper-text.ts
1802
2233
  import { su as su7 } from "@tscircuit/soup-util";
1803
2234
 
@@ -1897,8 +2328,8 @@ var convertCourtyard = (circuitJson) => {
1897
2328
  import { su as su9 } from "@tscircuit/soup-util";
1898
2329
 
1899
2330
  // lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-mm.ts
1900
- import { mmStr as mmStr2 } from "@tscircuit/mm";
1901
- var formatMm = (value) => mmStr2(value ?? 0);
2331
+ import { mmStr as mmStr3 } from "@tscircuit/mm";
2332
+ var formatMm = (value) => mmStr3(value ?? 0);
1902
2333
 
1903
2334
  // lib/generate-footprint-tsx/convert-cutouts.ts
1904
2335
  var convertCutouts = (circuitJson) => {
@@ -2043,26 +2474,26 @@ var convertFabrication = (circuitJson) => {
2043
2474
 
2044
2475
  // lib/generate-footprint-tsx/convert-holes.ts
2045
2476
  import { su as su11 } from "@tscircuit/soup-util";
2046
- import { mmStr as mmStr3 } from "@tscircuit/mm";
2477
+ import { mmStr as mmStr4 } from "@tscircuit/mm";
2047
2478
  var convertHoles = (circuitJson) => {
2048
2479
  const holes = su11(circuitJson).pcb_hole.list();
2049
2480
  const elementStrings = [];
2050
2481
  for (const hole of holes) {
2051
2482
  if (hole.hole_shape === "circle") {
2052
2483
  elementStrings.push(
2053
- `<hole pcbX="${mmStr3(hole.x)}" pcbY="${mmStr3(hole.y)}" diameter="${mmStr3(hole.hole_diameter)}" />`
2484
+ `<hole pcbX="${mmStr4(hole.x)}" pcbY="${mmStr4(hole.y)}" diameter="${mmStr4(hole.hole_diameter)}" />`
2054
2485
  );
2055
2486
  } else if (hole.hole_shape === "rect") {
2056
2487
  elementStrings.push(
2057
- `<hole pcbX="${mmStr3(hole.x)}" pcbY="${mmStr3(hole.y)}" width="${mmStr3(hole.hole_width)}" height="${mmStr3(hole.hole_height)}" shape="rect" />`
2488
+ `<hole pcbX="${mmStr4(hole.x)}" pcbY="${mmStr4(hole.y)}" width="${mmStr4(hole.hole_width)}" height="${mmStr4(hole.hole_height)}" shape="rect" />`
2058
2489
  );
2059
2490
  } else if (hole.hole_shape === "oval") {
2060
2491
  elementStrings.push(
2061
- `<hole pcbX="${mmStr3(hole.x)}" pcbY="${mmStr3(hole.y)}" width="${mmStr3(hole.hole_width)}" height="${mmStr3(hole.hole_height)}" shape="oval" />`
2492
+ `<hole pcbX="${mmStr4(hole.x)}" pcbY="${mmStr4(hole.y)}" width="${mmStr4(hole.hole_width)}" height="${mmStr4(hole.hole_height)}" shape="oval" />`
2062
2493
  );
2063
2494
  } else if (hole.hole_shape === "pill" || hole.hole_shape === "rotated_pill") {
2064
2495
  elementStrings.push(
2065
- `<hole pcbX="${mmStr3(hole.x)}" pcbY="${mmStr3(hole.y)}" width="${mmStr3(hole.hole_width)}" height="${mmStr3(hole.hole_height)}" shape="pill"${formatPcbRotationAttr("ccw_rotation" in hole ? hole.ccw_rotation : void 0)} />`
2496
+ `<hole pcbX="${mmStr4(hole.x)}" pcbY="${mmStr4(hole.y)}" width="${mmStr4(hole.hole_width)}" height="${mmStr4(hole.hole_height)}" shape="pill"${formatPcbRotationAttr("ccw_rotation" in hole ? hole.ccw_rotation : void 0)} />`
2066
2497
  );
2067
2498
  }
2068
2499
  }
@@ -2276,13 +2707,13 @@ var convertPcbTraces = (circuitJson) => su14(circuitJson).pcb_trace.list().map((
2276
2707
 
2277
2708
  // lib/generate-footprint-tsx/convert-plated-holes.ts
2278
2709
  import { su as su15 } from "@tscircuit/soup-util";
2279
- import { mmStr as mmStr5 } from "@tscircuit/mm";
2710
+ import { mmStr as mmStr6 } from "@tscircuit/mm";
2280
2711
 
2281
2712
  // lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-optional-mm-attr.ts
2282
- import { mmStr as mmStr4 } from "@tscircuit/mm";
2713
+ import { mmStr as mmStr5 } from "@tscircuit/mm";
2283
2714
  var formatOptionalMmAttr = (attrName, value) => {
2284
2715
  if (value === void 0) return "";
2285
- return ` ${attrName}="${mmStr4(value)}"`;
2716
+ return ` ${attrName}="${mmStr5(value)}"`;
2286
2717
  };
2287
2718
 
2288
2719
  // lib/generate-footprint-tsx/footprint-tsx-attribute-formatters/format-solder-mask-attrs.ts
@@ -2298,7 +2729,13 @@ var formatSolderMaskAttrs = (platedHole) => {
2298
2729
  var getImportedPortHints = (primitive, context) => {
2299
2730
  if (!primitive.pcb_port_id || !context) return primitive.port_hints;
2300
2731
  const importedHint = context.portHintByPcbPortId.get(primitive.pcb_port_id);
2301
- return importedHint ? [importedHint] : primitive.port_hints;
2732
+ if (!importedHint) return primitive.port_hints;
2733
+ if (!context.pcbPortIdsWithUnambiguousComponentSelectors.has(
2734
+ primitive.pcb_port_id
2735
+ )) {
2736
+ return [importedHint];
2737
+ }
2738
+ return [.../* @__PURE__ */ new Set([importedHint, ...primitive.port_hints ?? []])];
2302
2739
  };
2303
2740
 
2304
2741
  // lib/generate-footprint-tsx/convert-plated-holes.ts
@@ -2311,26 +2748,26 @@ var convertPlatedHoles = (circuitJson, context) => {
2311
2748
  const tagSpacing = context ? " " : " ";
2312
2749
  if (platedHole.shape === "oval" || platedHole.shape === "pill") {
2313
2750
  elementStrings.push(
2314
- `<platedhole${tagSpacing}${portHintsAttr} pcbX="${mmStr5(platedHole.x)}" pcbY="${mmStr5(platedHole.y)}"${formatSolderMaskAttrs(platedHole)} outerHeight="${mmStr5(platedHole.outer_height)}" outerWidth="${mmStr5(platedHole.outer_width)}" holeHeight="${mmStr5(platedHole.hole_height)}" holeWidth="${mmStr5(platedHole.hole_width)}" height="${mmStr5(platedHole.hole_height)}" shape="${platedHole.shape}"${formatPcbRotationAttr("ccw_rotation" in platedHole ? platedHole.ccw_rotation : void 0)} />`
2751
+ `<platedhole${tagSpacing}${portHintsAttr} pcbX="${mmStr6(platedHole.x)}" pcbY="${mmStr6(platedHole.y)}"${formatSolderMaskAttrs(platedHole)} outerHeight="${mmStr6(platedHole.outer_height)}" outerWidth="${mmStr6(platedHole.outer_width)}" holeHeight="${mmStr6(platedHole.hole_height)}" holeWidth="${mmStr6(platedHole.hole_width)}" height="${mmStr6(platedHole.hole_height)}" shape="${platedHole.shape}"${formatPcbRotationAttr("ccw_rotation" in platedHole ? platedHole.ccw_rotation : void 0)} />`
2315
2752
  );
2316
2753
  } else if (platedHole.shape === "circle") {
2317
2754
  elementStrings.push(
2318
- `<platedhole${tagSpacing}${portHintsAttr} pcbX="${mmStr5(platedHole.x)}" pcbY="${mmStr5(platedHole.y)}"${formatSolderMaskAttrs(platedHole)} outerDiameter="${mmStr5(platedHole.outer_diameter)}" holeDiameter="${mmStr5(platedHole.hole_diameter)}" shape="circle" />`
2755
+ `<platedhole${tagSpacing}${portHintsAttr} pcbX="${mmStr6(platedHole.x)}" pcbY="${mmStr6(platedHole.y)}"${formatSolderMaskAttrs(platedHole)} outerDiameter="${mmStr6(platedHole.outer_diameter)}" holeDiameter="${mmStr6(platedHole.hole_diameter)}" shape="circle" />`
2319
2756
  );
2320
2757
  } else if (platedHole.shape === "circular_hole_with_rect_pad") {
2321
2758
  elementStrings.push(
2322
- `<platedhole${tagSpacing}${portHintsAttr} pcbX="${mmStr5(platedHole.x)}" pcbY="${mmStr5(platedHole.y)}"${platedHole.hole_shape === void 0 ? "" : ` holeShape="${platedHole.hole_shape}"`}${platedHole.pad_shape === void 0 ? "" : ` padShape="${platedHole.pad_shape}"`}${formatSolderMaskAttrs(platedHole)} holeDiameter="${mmStr5(platedHole.hole_diameter)}" rectPadWidth="${mmStr5(platedHole.rect_pad_width)}" rectPadHeight="${mmStr5(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" />`
2759
+ `<platedhole${tagSpacing}${portHintsAttr} pcbX="${mmStr6(platedHole.x)}" pcbY="${mmStr6(platedHole.y)}"${platedHole.hole_shape === void 0 ? "" : ` holeShape="${platedHole.hole_shape}"`}${platedHole.pad_shape === void 0 ? "" : ` padShape="${platedHole.pad_shape}"`}${formatSolderMaskAttrs(platedHole)} holeDiameter="${mmStr6(platedHole.hole_diameter)}" rectPadWidth="${mmStr6(platedHole.rect_pad_width)}" rectPadHeight="${mmStr6(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" />`
2323
2760
  );
2324
2761
  } else if (platedHole.shape === "pill_hole_with_rect_pad" || platedHole.shape === "rotated_pill_hole_with_rect_pad") {
2325
2762
  const holeShape = platedHole.shape === "rotated_pill_hole_with_rect_pad" ? "pill" : platedHole.hole_shape;
2326
2763
  const rotation = platedHole.shape === "rotated_pill_hole_with_rect_pad" ? platedHole.hole_ccw_rotation : void 0;
2327
2764
  elementStrings.push(
2328
- `<platedhole${tagSpacing}${portHintsAttr} pcbX="${mmStr5(platedHole.x)}" pcbY="${mmStr5(platedHole.y)}"${holeShape === void 0 ? "" : ` holeShape="${holeShape}"`}${platedHole.pad_shape === void 0 ? "" : ` padShape="${platedHole.pad_shape}"`}${formatSolderMaskAttrs(platedHole)} holeWidth="${mmStr5(platedHole.hole_width)}" holeHeight="${mmStr5(platedHole.hole_height)}" rectPadWidth="${mmStr5(platedHole.rect_pad_width)}" rectPadHeight="${mmStr5(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" />`
2765
+ `<platedhole${tagSpacing}${portHintsAttr} pcbX="${mmStr6(platedHole.x)}" pcbY="${mmStr6(platedHole.y)}"${holeShape === void 0 ? "" : ` holeShape="${holeShape}"`}${platedHole.pad_shape === void 0 ? "" : ` padShape="${platedHole.pad_shape}"`}${formatSolderMaskAttrs(platedHole)} holeWidth="${mmStr6(platedHole.hole_width)}" holeHeight="${mmStr6(platedHole.hole_height)}" rectPadWidth="${mmStr6(platedHole.rect_pad_width)}" rectPadHeight="${mmStr6(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" />`
2329
2766
  );
2330
2767
  } else if (platedHole.shape === "hole_with_polygon_pad") {
2331
- const holeSizeAttrs = platedHole.hole_shape === "circle" ? ` holeDiameter="${mmStr5(platedHole.hole_diameter ?? 0)}"` : `${formatOptionalMmAttr("holeWidth", platedHole.hole_width)}${formatOptionalMmAttr("holeHeight", platedHole.hole_height)}`;
2768
+ const holeSizeAttrs = platedHole.hole_shape === "circle" ? ` holeDiameter="${mmStr6(platedHole.hole_diameter ?? 0)}"` : `${formatOptionalMmAttr("holeWidth", platedHole.hole_width)}${formatOptionalMmAttr("holeHeight", platedHole.hole_height)}`;
2332
2769
  elementStrings.push(
2333
- `<platedhole${tagSpacing}${portHintsAttr} pcbX="${mmStr5(platedHole.x)}" pcbY="${mmStr5(platedHole.y)}"${platedHole.hole_shape === void 0 ? "" : ` holeShape="${platedHole.hole_shape}"`}${formatSolderMaskAttrs(platedHole)}${holeSizeAttrs} padOutline={${JSON.stringify(platedHole.pad_outline)}} holeOffsetX="${mmStr5(platedHole.hole_offset_x ?? 0)}" holeOffsetY="${mmStr5(platedHole.hole_offset_y ?? 0)}" shape="hole_with_polygon_pad" />`
2770
+ `<platedhole${tagSpacing}${portHintsAttr} pcbX="${mmStr6(platedHole.x)}" pcbY="${mmStr6(platedHole.y)}"${platedHole.hole_shape === void 0 ? "" : ` holeShape="${platedHole.hole_shape}"`}${formatSolderMaskAttrs(platedHole)}${holeSizeAttrs} padOutline={${JSON.stringify(platedHole.pad_outline)}} holeOffsetX="${mmStr6(platedHole.hole_offset_x ?? 0)}" holeOffsetY="${mmStr6(platedHole.hole_offset_y ?? 0)}" shape="hole_with_polygon_pad" />`
2334
2771
  );
2335
2772
  }
2336
2773
  }
@@ -2535,7 +2972,7 @@ var convertSilkscreenGraphics = (circuitJson) => {
2535
2972
 
2536
2973
  // lib/generate-footprint-tsx/convert-silkscreen-text.ts
2537
2974
  import { su as su18 } from "@tscircuit/soup-util";
2538
- import { mmStr as mmStr6 } from "@tscircuit/mm";
2975
+ import { mmStr as mmStr7 } from "@tscircuit/mm";
2539
2976
  var convertSilkscreenText = (circuitJson) => {
2540
2977
  const silkscreenTexts = su18(circuitJson).pcb_silkscreen_text.list();
2541
2978
  const elementStrings = [];
@@ -2545,7 +2982,7 @@ var convertSilkscreenText = (circuitJson) => {
2545
2982
  const knockoutPadding = silkscreenText.knockout_padding;
2546
2983
  const hasUniformKnockoutPadding = knockoutPadding && knockoutPadding.left === knockoutPadding.top && knockoutPadding.top === knockoutPadding.right && knockoutPadding.right === knockoutPadding.bottom;
2547
2984
  elementStrings.push(
2548
- `<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="${mmStr6(knockoutPadding.left)}"` : ` knockoutPaddingLeft="${mmStr6(knockoutPadding.left)}" knockoutPaddingTop="${mmStr6(knockoutPadding.top)}" knockoutPaddingRight="${mmStr6(knockoutPadding.right)}" knockoutPaddingBottom="${mmStr6(knockoutPadding.bottom)}"` : ""}${silkscreenText.is_mirrored !== void 0 ? ` mirrored={${silkscreenText.is_mirrored}}` : ""}${silkscreenText.layer !== void 0 ? ` layer="${silkscreenText.layer}"` : ""} text="${escapeJsxText2(silkscreenText.text)}" />`
2985
+ `<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="${mmStr7(knockoutPadding.left)}"` : ` knockoutPaddingLeft="${mmStr7(knockoutPadding.left)}" knockoutPaddingTop="${mmStr7(knockoutPadding.top)}" knockoutPaddingRight="${mmStr7(knockoutPadding.right)}" knockoutPaddingBottom="${mmStr7(knockoutPadding.bottom)}"` : ""}${silkscreenText.is_mirrored !== void 0 ? ` mirrored={${silkscreenText.is_mirrored}}` : ""}${silkscreenText.layer !== void 0 ? ` layer="${silkscreenText.layer}"` : ""} text="${escapeJsxText2(silkscreenText.text)}" />`
2549
2986
  );
2550
2987
  }
2551
2988
  return elementStrings;
@@ -2553,7 +2990,7 @@ var convertSilkscreenText = (circuitJson) => {
2553
2990
 
2554
2991
  // lib/generate-footprint-tsx/convert-smt-pads.ts
2555
2992
  import { su as su19 } from "@tscircuit/soup-util";
2556
- import { mmStr as mmStr7 } from "@tscircuit/mm";
2993
+ import { mmStr as mmStr8 } from "@tscircuit/mm";
2557
2994
  var convertSmtPads = (circuitJson, context) => {
2558
2995
  const smtPads = su19(circuitJson).pcb_smtpad.list();
2559
2996
  const elementStrings = [];
@@ -2565,29 +3002,29 @@ var convertSmtPads = (circuitJson, context) => {
2565
3002
  commonAttrs.push(`portHints={${JSON.stringify(portHints)}}`);
2566
3003
  }
2567
3004
  if ("x" in smtPad && smtPad.x !== void 0) {
2568
- commonAttrs.push(`pcbX="${mmStr7(smtPad.x)}"`);
3005
+ commonAttrs.push(`pcbX="${mmStr8(smtPad.x)}"`);
2569
3006
  }
2570
3007
  if ("y" in smtPad && smtPad.y !== void 0) {
2571
- commonAttrs.push(`pcbY="${mmStr7(smtPad.y)}"`);
3008
+ commonAttrs.push(`pcbY="${mmStr8(smtPad.y)}"`);
2572
3009
  }
2573
3010
  if (smtPad.layer !== void 0) {
2574
3011
  commonAttrs.push(`layer="${smtPad.layer}"`);
2575
3012
  }
2576
3013
  if (smtPad.shape === "circle") {
2577
3014
  elementStrings.push(
2578
- `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} radius="${mmStr7(smtPad.radius ?? 0)}" shape="circle" />`
3015
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} radius="${mmStr8(smtPad.radius ?? 0)}" shape="circle" />`
2579
3016
  );
2580
3017
  } else if (smtPad.shape === "rect") {
2581
3018
  elementStrings.push(
2582
- `<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="${mmStr7(smtPad.width ?? 0)}" height="${mmStr7(smtPad.height ?? 0)}" shape="rect" />`
3019
+ `<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="${mmStr8(smtPad.width ?? 0)}" height="${mmStr8(smtPad.height ?? 0)}" shape="rect" />`
2583
3020
  );
2584
3021
  } else if (smtPad.shape === "pill") {
2585
3022
  elementStrings.push(
2586
- `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} width="${mmStr7(smtPad.width ?? 0)}" height="${mmStr7(smtPad.height ?? 0)}" radius="${mmStr7(smtPad.radius ?? 0)}" shape="pill" />`
3023
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} width="${mmStr8(smtPad.width ?? 0)}" height="${mmStr8(smtPad.height ?? 0)}" radius="${mmStr8(smtPad.radius ?? 0)}" shape="pill" />`
2587
3024
  );
2588
3025
  } else if (smtPad.shape === "rotated_pill") {
2589
3026
  elementStrings.push(
2590
- `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} width="${mmStr7(smtPad.width ?? 0)}" height="${mmStr7(smtPad.height ?? 0)}" radius="${mmStr7(smtPad.radius ?? 0)}"${formatPcbRotationAttr(smtPad.ccw_rotation)} shape="pill" />`
3027
+ `<smtpad ${commonAttrs.join(" ")}${solderMaskAttrs} width="${mmStr8(smtPad.width ?? 0)}" height="${mmStr8(smtPad.height ?? 0)}" radius="${mmStr8(smtPad.radius ?? 0)}"${formatPcbRotationAttr(smtPad.ccw_rotation)} shape="pill" />`
2591
3028
  );
2592
3029
  } else if (smtPad.shape === "polygon") {
2593
3030
  elementStrings.push(
@@ -2596,7 +3033,7 @@ var convertSmtPads = (circuitJson, context) => {
2596
3033
  } else if (smtPad.shape === "rotated_rect") {
2597
3034
  const cornerRadius = smtPad.corner_radius ?? smtPad.rect_border_radius ?? void 0;
2598
3035
  elementStrings.push(
2599
- `<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="${mmStr7(smtPad.width ?? 0)}" height="${mmStr7(smtPad.height ?? 0)}" ccwRotation={${smtPad.ccw_rotation ?? 0}} shape="rotated_rect" />`
3036
+ `<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="${mmStr8(smtPad.width ?? 0)}" height="${mmStr8(smtPad.height ?? 0)}" ccwRotation={${smtPad.ccw_rotation ?? 0}} shape="rotated_rect" />`
2600
3037
  );
2601
3038
  }
2602
3039
  }
@@ -2604,7 +3041,7 @@ var convertSmtPads = (circuitJson, context) => {
2604
3041
  };
2605
3042
 
2606
3043
  // lib/generate-footprint-tsx/convert-vias.ts
2607
- import { mmStr as mmStr8 } from "@tscircuit/mm";
3044
+ import { mmStr as mmStr9 } from "@tscircuit/mm";
2608
3045
  import { su as su20 } from "@tscircuit/soup-util";
2609
3046
  var convertVias = (circuitJson, context) => {
2610
3047
  return su20(circuitJson).pcb_via.list().map((via) => {
@@ -2620,7 +3057,7 @@ var convertVias = (circuitJson, context) => {
2620
3057
  const tentedAttr = tentedOnTop !== void 0 || tentedOnBottom !== void 0 ? ` tented="${tented}"` : "";
2621
3058
  const runtimeNetName = via.source_net_id ? context?.runtimeNetNameBySourceNetId.get(via.source_net_id) : void 0;
2622
3059
  const connectsToAttr = runtimeNetName ? ` connectsTo={${JSON.stringify(`net.${runtimeNetName}`)}}` : "";
2623
- return `<via pcbX="${mmStr8(via.x)}" pcbY="${mmStr8(via.y)}" holeDiameter="${mmStr8(via.hole_diameter)}" outerDiameter="${mmStr8(via.outer_diameter)}" fromLayer="${fromLayer}" toLayer="${toLayer}"${tentedAttr}${connectsToAttr} />`;
3060
+ return `<via pcbX="${mmStr9(via.x)}" pcbY="${mmStr9(via.y)}" holeDiameter="${mmStr9(via.hole_diameter)}" outerDiameter="${mmStr9(via.outer_diameter)}" fromLayer="${fromLayer}" toLayer="${toLayer}"${tentedAttr}${connectsToAttr} />`;
2624
3061
  });
2625
3062
  };
2626
3063
 
@@ -2655,80 +3092,559 @@ var generateFootprintTsx = (circuitJson, context) => {
2655
3092
  `.trim();
2656
3093
  };
2657
3094
 
2658
- // lib/get-board-using-template.ts
2659
- var getBoardUsingTemplate = ({
3095
+ // lib/board-converter/get-pcb-component-render-placement.ts
3096
+ import { getBoundsOfPcbElements } from "@tscircuit/circuit-json-util";
3097
+ import { applyToPoint as applyToPoint5, inverse, translate as translate5 } from "transformation-matrix";
3098
+ var physicalPcbElementTypes = /* @__PURE__ */ new Set([
3099
+ "pcb_copper_text",
3100
+ "pcb_cutout",
3101
+ "pcb_hole",
3102
+ "pcb_keepout",
3103
+ "pcb_plated_hole",
3104
+ "pcb_smtpad",
3105
+ "pcb_trace",
3106
+ "pcb_via"
3107
+ ]);
3108
+ var isPhysicalPcbElement = (element) => physicalPcbElementTypes.has(element.type);
3109
+ var getElementsAsRenderedByGeneratedTsx = (elements) => elements.map((element) => {
3110
+ if (element.type !== "pcb_plated_hole" || element.shape !== "rotated_pill_hole_with_rect_pad") {
3111
+ return element;
3112
+ }
3113
+ return {
3114
+ ...element,
3115
+ rect_ccw_rotation: element.hole_ccw_rotation
3116
+ };
3117
+ });
3118
+ var getPcbComponentRenderPlacement = ({
2660
3119
  circuitJson,
2661
- componentName
3120
+ pcbBoard,
3121
+ pcbComponent
2662
3122
  }) => {
2663
- const pcbBoard = su21(circuitJson).pcb_board.list()[0];
2664
- const boardProps = [];
2665
- if (pcbBoard) {
2666
- if (pcbBoard.width !== void 0) {
2667
- boardProps.push(`width="${mmStr9(pcbBoard.width)}"`);
2668
- }
2669
- if (pcbBoard.height !== void 0) {
2670
- boardProps.push(`height="${mmStr9(pcbBoard.height)}"`);
3123
+ const componentPosition = applyToPoint5(
3124
+ inverse(translate5(pcbBoard.center.x, pcbBoard.center.y)),
3125
+ pcbComponent.center
3126
+ );
3127
+ const physicalElements = circuitJson.filter(
3128
+ (element) => isPhysicalPcbElement(element) && "pcb_component_id" in element && element.pcb_component_id === pcbComponent.pcb_component_id
3129
+ );
3130
+ const physicalBounds = getBoundsOfPcbElements(
3131
+ getElementsAsRenderedByGeneratedTsx(physicalElements)
3132
+ );
3133
+ const physicalWidth = physicalBounds.maxX - physicalBounds.minX;
3134
+ const physicalHeight = physicalBounds.maxY - physicalBounds.minY;
3135
+ if (Number.isFinite(physicalWidth) && Number.isFinite(physicalHeight) && physicalWidth > 0 && physicalHeight > 0) {
3136
+ return {
3137
+ cadModelParentCenter: {
3138
+ x: (physicalBounds.minX + physicalBounds.maxX) / 2,
3139
+ y: (physicalBounds.minY + physicalBounds.maxY) / 2
3140
+ },
3141
+ componentPosition
3142
+ };
3143
+ }
3144
+ return {
3145
+ cadModelParentCenter: componentPosition,
3146
+ componentPosition
3147
+ };
3148
+ };
3149
+
3150
+ // lib/board-converter/get-pin-labels.ts
3151
+ var getCompoundPinNumber = (sourcePort) => {
3152
+ if (sourcePort.pin_number !== void 0) return sourcePort.pin_number;
3153
+ const firstPinNumber = /^\d+/u.exec(sourcePort.name)?.[0];
3154
+ return firstPinNumber ? Number.parseInt(firstPinNumber, 10) : void 0;
3155
+ };
3156
+ var getPinLabels = ({
3157
+ circuitJson,
3158
+ footprintConversionContext,
3159
+ pcbComponent,
3160
+ sourceComponents
3161
+ }) => {
3162
+ if (sourceComponents.length === 0) return void 0;
3163
+ const pinLabels = {};
3164
+ const sourceComponentIds = new Set(
3165
+ sourceComponents.map((sourceComponent) => sourceComponent.source_component_id).filter(
3166
+ (sourceComponentId) => sourceComponentId !== void 0
3167
+ )
3168
+ );
3169
+ const sourcePorts = circuitJson.filter(
3170
+ (element) => element.type === "source_port" && element.source_component_id !== void 0 && sourceComponentIds.has(element.source_component_id) && (element.pin_number !== void 0 || element.source_component_id === pcbComponent.source_component_id)
3171
+ );
3172
+ const usedPinNumbers = new Set(
3173
+ sourcePorts.map(getCompoundPinNumber).filter((pinNumber) => pinNumber !== void 0)
3174
+ );
3175
+ let nextSyntheticPinNumber = Math.max(0, ...usedPinNumbers) + 1;
3176
+ for (const sourcePort of sourcePorts) {
3177
+ const importedPortHints = circuitJson.flatMap((element) => {
3178
+ if (sourcePort.source_component_id !== pcbComponent.source_component_id || element.type !== "pcb_port" || element.source_port_id !== sourcePort.source_port_id) {
3179
+ return [];
3180
+ }
3181
+ const importedPortHint = footprintConversionContext.portHintByPcbPortId.get(element.pcb_port_id);
3182
+ return importedPortHint ? [importedPortHint] : [];
3183
+ });
3184
+ const aliases = [
3185
+ sourcePort.name,
3186
+ ...sourcePort.port_hints ?? [],
3187
+ ...importedPortHints
3188
+ ].filter(
3189
+ (alias, aliasIndex, allAliases) => alias.length > 0 && allAliases.indexOf(alias) === aliasIndex
3190
+ );
3191
+ let pinNumber = getCompoundPinNumber(sourcePort);
3192
+ if (pinNumber === void 0) {
3193
+ while (usedPinNumbers.has(nextSyntheticPinNumber)) {
3194
+ nextSyntheticPinNumber += 1;
3195
+ }
3196
+ pinNumber = nextSyntheticPinNumber;
3197
+ usedPinNumbers.add(pinNumber);
3198
+ nextSyntheticPinNumber += 1;
3199
+ }
3200
+ const pinLabelKey = `pin${pinNumber}`;
3201
+ pinLabels[pinLabelKey] = [
3202
+ ...pinLabels[pinLabelKey] ?? [],
3203
+ ...aliases
3204
+ ].filter(
3205
+ (alias, aliasIndex, allAliases) => allAliases.indexOf(alias) === aliasIndex
3206
+ );
3207
+ }
3208
+ return Object.keys(pinLabels).length > 0 ? pinLabels : void 0;
3209
+ };
3210
+
3211
+ // lib/board-converter/get-schematic-components-for-pcb-component.ts
3212
+ var getSchematicComponentsForPcbComponent = ({
3213
+ circuitJson,
3214
+ pcbComponent
3215
+ }) => {
3216
+ const sourceComponents = circuitJson.filter(
3217
+ (element) => element.type === "source_component"
3218
+ );
3219
+ const pcbSourceComponent = sourceComponents.find(
3220
+ (sourceComponent) => sourceComponent.source_component_id === pcbComponent.source_component_id
3221
+ );
3222
+ if (!pcbSourceComponent) return [];
3223
+ const matchingSourceComponentIds = new Set(
3224
+ sourceComponents.filter(
3225
+ (sourceComponent) => sourceComponent.source_component_id === pcbSourceComponent.source_component_id || pcbSourceComponent.name !== void 0 && sourceComponent.name === pcbSourceComponent.name
3226
+ ).map((sourceComponent) => sourceComponent.source_component_id).filter(
3227
+ (sourceComponentId) => sourceComponentId !== void 0
3228
+ )
3229
+ );
3230
+ return circuitJson.filter(
3231
+ (element) => element.type === "schematic_component" && element.source_component_id !== void 0 && matchingSourceComponentIds.has(element.source_component_id)
3232
+ );
3233
+ };
3234
+
3235
+ // lib/board-converter/localize-pcb-component-elements.ts
3236
+ import { transformPCBElements } from "@tscircuit/circuit-json-util";
3237
+ import {
3238
+ applyToPoint as applyToPoint6,
3239
+ compose as compose4,
3240
+ decomposeTSR,
3241
+ flipY,
3242
+ inverse as inverse2,
3243
+ rotateDEG as rotateDEG3,
3244
+ translate as translate6
3245
+ } from "transformation-matrix";
3246
+ var FLOATING_POINT_NOISE_THRESHOLD = 1e-12;
3247
+ var LOCAL_COORDINATE_DECIMAL_PLACES = 12;
3248
+ var normalizeFloatingPointNoise = (elementPropertyValue) => {
3249
+ if (Array.isArray(elementPropertyValue)) {
3250
+ for (const item of elementPropertyValue) normalizeFloatingPointNoise(item);
3251
+ return;
3252
+ }
3253
+ if (typeof elementPropertyValue !== "object" || elementPropertyValue === null)
3254
+ return;
3255
+ const record = elementPropertyValue;
3256
+ for (const [key, property] of Object.entries(record)) {
3257
+ if (typeof property === "number" && Math.abs(property) < FLOATING_POINT_NOISE_THRESHOLD) {
3258
+ record[key] = 0;
3259
+ } else {
3260
+ normalizeFloatingPointNoise(property);
2671
3261
  }
2672
- if (pcbBoard.outline && pcbBoard.outline.length > 0) {
2673
- const points = pcbBoard.outline.map((p) => `{ x: ${p.x}, y: ${p.y} }`).join(", ");
2674
- boardProps.push(`outline={[${points}]}`);
3262
+ }
3263
+ };
3264
+ var normalizeRoutableElementCoordinates = (localComponentElements) => {
3265
+ for (const element of localComponentElements) {
3266
+ if (element.type !== "pcb_smtpad" && element.type !== "pcb_plated_hole") {
3267
+ continue;
2675
3268
  }
2676
- if (pcbBoard.thickness !== void 0) {
2677
- boardProps.push(`thickness="${mmStr9(pcbBoard.thickness)}"`);
3269
+ if (!("x" in element) || !("y" in element)) continue;
3270
+ element.x = Number(element.x.toFixed(LOCAL_COORDINATE_DECIMAL_PLACES));
3271
+ element.y = Number(element.y.toFixed(LOCAL_COORDINATE_DECIMAL_PLACES));
3272
+ }
3273
+ };
3274
+ var transformSilkscreenGraphic = ({
3275
+ absoluteToComponentLocalTransform,
3276
+ graphic
3277
+ }) => {
3278
+ if (graphic.shape !== "brep") return;
3279
+ graphic.brep_shape.outer_ring.vertices = graphic.brep_shape.outer_ring.vertices.map(
3280
+ (vertex) => applyToPoint6(absoluteToComponentLocalTransform, vertex)
3281
+ );
3282
+ graphic.brep_shape.inner_rings = graphic.brep_shape.inner_rings.map(
3283
+ (ring) => ({
3284
+ ...ring,
3285
+ vertices: ring.vertices.map(
3286
+ (vertex) => applyToPoint6(absoluteToComponentLocalTransform, vertex)
3287
+ )
3288
+ })
3289
+ );
3290
+ };
3291
+ var normalizeDegrees = (degrees) => (degrees % 360 + 360) % 360;
3292
+ var getTransformedCcwRotationDegrees = ({
3293
+ absoluteCcwRotationDegrees,
3294
+ absoluteToComponentLocalRotationTransform
3295
+ }) => {
3296
+ const transformedRotation = decomposeTSR(
3297
+ compose4(
3298
+ absoluteToComponentLocalRotationTransform,
3299
+ rotateDEG3(absoluteCcwRotationDegrees)
3300
+ )
3301
+ ).rotation.angle;
3302
+ return normalizeDegrees(transformedRotation * 180 / Math.PI);
3303
+ };
3304
+ var localizePrimitiveRotations = ({
3305
+ absoluteComponentElements,
3306
+ absoluteToComponentLocalRotationTransform,
3307
+ localComponentElements
3308
+ }) => {
3309
+ const rotationPropertyNames = [
3310
+ "ccw_rotation",
3311
+ "hole_ccw_rotation",
3312
+ "rect_ccw_rotation"
3313
+ ];
3314
+ for (const [elementIndex, localElement] of localComponentElements.entries()) {
3315
+ if (localElement.type === "pcb_silkscreen_text" || localElement.type === "pcb_copper_text" || localElement.type === "pcb_fabrication_note_text") {
3316
+ continue;
2678
3317
  }
2679
- if (pcbBoard.num_layers !== void 0) {
2680
- boardProps.push(`layers={${pcbBoard.num_layers}}`);
3318
+ const absoluteElement = absoluteComponentElements[elementIndex];
3319
+ if (absoluteElement?.type !== localElement.type) continue;
3320
+ const absoluteRotations = absoluteElement;
3321
+ const localRotations = localElement;
3322
+ for (const rotationPropertyName of rotationPropertyNames) {
3323
+ const absoluteCcwRotationDegrees = absoluteRotations[rotationPropertyName];
3324
+ if (typeof absoluteCcwRotationDegrees !== "number") continue;
3325
+ localRotations[rotationPropertyName] = getTransformedCcwRotationDegrees({
3326
+ absoluteCcwRotationDegrees,
3327
+ absoluteToComponentLocalRotationTransform
3328
+ });
2681
3329
  }
2682
- if (pcbBoard.material !== void 0) {
2683
- boardProps.push(`material="${pcbBoard.material}"`);
3330
+ }
3331
+ };
3332
+ var getOppositeOuterLayer = (layer) => {
3333
+ if (layer === "top") return "bottom";
3334
+ if (layer === "bottom") return "top";
3335
+ return layer;
3336
+ };
3337
+ var localizeBottomComponentLayers = (localComponentElements) => {
3338
+ for (const localElement of localComponentElements) {
3339
+ if ("layer" in localElement && (localElement.layer === "top" || localElement.layer === "bottom")) {
3340
+ localElement.layer = getOppositeOuterLayer(localElement.layer);
2684
3341
  }
2685
- if (pcbBoard.solder_mask_color !== void 0) {
2686
- boardProps.push(`solderMaskColor="${pcbBoard.solder_mask_color}"`);
3342
+ if ("layers" in localElement && Array.isArray(localElement.layers)) {
3343
+ localElement.layers = localElement.layers.map(
3344
+ (layer) => layer === "top" || layer === "bottom" ? getOppositeOuterLayer(layer) : layer
3345
+ );
2687
3346
  }
2688
- if (pcbBoard.silkscreen_color !== void 0) {
2689
- boardProps.push(`silkscreenColor="${pcbBoard.silkscreen_color}"`);
3347
+ }
3348
+ };
3349
+ var localizeTextRotations = ({
3350
+ absoluteComponentElements,
3351
+ componentCcwRotationDegrees,
3352
+ localComponentElements
3353
+ }) => {
3354
+ for (const [elementIndex, localElement] of localComponentElements.entries()) {
3355
+ if (localElement.type !== "pcb_silkscreen_text" && localElement.type !== "pcb_copper_text" && localElement.type !== "pcb_fabrication_note_text") {
3356
+ continue;
2690
3357
  }
2691
- const resolvedAnchorPosition = pcbBoard.anchor_position ?? pcbBoard.center;
2692
- if (pcbBoard.anchor_position !== void 0 || resolvedAnchorPosition.x !== 0 || resolvedAnchorPosition.y !== 0) {
2693
- boardProps.push(
2694
- `boardAnchorPosition={{ x: ${resolvedAnchorPosition.x}, y: ${resolvedAnchorPosition.y} }}`
3358
+ const absoluteElement = absoluteComponentElements[elementIndex];
3359
+ if (absoluteElement?.type !== localElement.type) continue;
3360
+ const absoluteRotation = normalizeDegrees(absoluteElement.ccw_rotation ?? 0);
3361
+ if (localElement.type === "pcb_silkscreen_text") {
3362
+ localElement.ccw_rotation = absoluteRotation || 360;
3363
+ } else if (localElement.type === "pcb_copper_text") {
3364
+ localElement.ccw_rotation = absoluteRotation;
3365
+ } else {
3366
+ localElement.ccw_rotation = normalizeDegrees(
3367
+ absoluteRotation - componentCcwRotationDegrees
2695
3368
  );
2696
3369
  }
2697
- if (pcbBoard.anchor_alignment !== void 0) {
2698
- boardProps.push(`boardAnchorAlignment="${pcbBoard.anchor_alignment}"`);
3370
+ }
3371
+ };
3372
+ var localizePcbComponentElements = ({
3373
+ circuitJson,
3374
+ pcbComponent
3375
+ }) => {
3376
+ const absoluteComponentElements = circuitJson.filter(
3377
+ (element) => "pcb_component_id" in element && element.pcb_component_id === pcbComponent.pcb_component_id
3378
+ );
3379
+ const localComponentElements = structuredClone(absoluteComponentElements);
3380
+ const componentLocalToAbsoluteTransform = compose4(
3381
+ translate6(pcbComponent.center.x, pcbComponent.center.y),
3382
+ rotateDEG3(pcbComponent.rotation)
3383
+ );
3384
+ let absoluteToComponentLocalTransform = inverse2(
3385
+ componentLocalToAbsoluteTransform
3386
+ );
3387
+ if (pcbComponent.layer === "bottom") {
3388
+ absoluteToComponentLocalTransform = compose4(
3389
+ flipY(),
3390
+ absoluteToComponentLocalTransform
3391
+ );
3392
+ }
3393
+ transformPCBElements(
3394
+ localComponentElements,
3395
+ absoluteToComponentLocalTransform
3396
+ );
3397
+ for (const localElement of localComponentElements) {
3398
+ if (localElement.type === "pcb_silkscreen_graphic") {
3399
+ transformSilkscreenGraphic({
3400
+ absoluteToComponentLocalTransform,
3401
+ graphic: localElement
3402
+ });
2699
3403
  }
2700
3404
  }
2701
- const boardPropsStr = boardProps.join(" ");
2702
- const context = createBoardConversionContext(circuitJson);
2703
- const footprintTsx = generateFootprintTsx(circuitJson, context);
2704
- const copperPoursTsx = generateCopperPoursTsx(circuitJson, context);
2705
- const boardNetsTsx = generateBoardNetsTsx(context);
2706
- const boardCadModelsTsx = generateBoardCadModelsTsx({
2707
- circuitJson,
2708
- pcbBoard
3405
+ localizeTextRotations({
3406
+ absoluteComponentElements,
3407
+ componentCcwRotationDegrees: pcbComponent.rotation,
3408
+ localComponentElements
2709
3409
  });
2710
- const schematicElements = generateBoardSchematicElements(circuitJson);
2711
- const schematicSheetTsx = generateBoardSchematicSheetTsx({
3410
+ localizePrimitiveRotations({
3411
+ absoluteComponentElements,
3412
+ absoluteToComponentLocalRotationTransform: inverse2(
3413
+ componentLocalToAbsoluteTransform
3414
+ ),
3415
+ localComponentElements
3416
+ });
3417
+ if (pcbComponent.layer === "bottom") {
3418
+ localizeBottomComponentLayers(localComponentElements);
3419
+ }
3420
+ normalizeRoutableElementCoordinates(localComponentElements);
3421
+ normalizeFloatingPointNoise(localComponentElements);
3422
+ return localComponentElements;
3423
+ };
3424
+
3425
+ // lib/board-converter/stages/convert-pcb-components.ts
3426
+ var getComponentFootprintTsx = ({
3427
+ circuitJson,
3428
+ footprintConversionContext,
3429
+ pcbComponent
3430
+ }) => {
3431
+ const componentElements = localizePcbComponentElements({
2712
3432
  circuitJson,
2713
- schematicElements
3433
+ pcbComponent
2714
3434
  });
2715
- const children = [
2716
- ...boardNetsTsx,
2717
- footprintTsx ? `<chip name="${context.pcbChipName}" noSchematicRepresentation footprint={${footprintTsx}} />` : "",
2718
- ...copperPoursTsx,
2719
- ...boardCadModelsTsx,
2720
- schematicSheetTsx
2721
- ].filter(Boolean).join("\n");
3435
+ return generateFootprintTsx(componentElements, footprintConversionContext);
3436
+ };
3437
+ var convertPcbComponents = ({
3438
+ circuitJson,
3439
+ footprintConversionContext,
3440
+ pcbComponentRuntimeNameByPcbComponentId,
3441
+ pcbBoard,
3442
+ boardChildren
3443
+ }) => {
3444
+ const pcbComponents = circuitJson.filter(
3445
+ (element) => element.type === "pcb_component"
3446
+ );
3447
+ const cadModelGroupByPcbComponentId = /* @__PURE__ */ new Map();
3448
+ for (const cadModelGroup of getCadModelGroups(circuitJson)) {
3449
+ const pcbComponentId = cadModelGroup.cadComponents[0]?.pcb_component_id;
3450
+ if (pcbComponentId === void 0) continue;
3451
+ const existingCadModelGroup = cadModelGroupByPcbComponentId.get(pcbComponentId);
3452
+ cadModelGroupByPcbComponentId.set(
3453
+ pcbComponentId,
3454
+ existingCadModelGroup ? {
3455
+ ...existingCadModelGroup,
3456
+ cadComponents: [
3457
+ ...existingCadModelGroup.cadComponents,
3458
+ ...cadModelGroup.cadComponents
3459
+ ]
3460
+ } : cadModelGroup
3461
+ );
3462
+ }
3463
+ for (const pcbComponent of pcbComponents) {
3464
+ const sourceComponent = circuitJson.find(
3465
+ (element) => element.type === "source_component" && element.source_component_id === pcbComponent.source_component_id
3466
+ );
3467
+ const schematicComponents = getSchematicComponentsForPcbComponent({
3468
+ circuitJson,
3469
+ pcbComponent
3470
+ });
3471
+ const schematicSourceComponentIds = new Set(
3472
+ schematicComponents.map((schematicComponent) => schematicComponent.source_component_id).filter(
3473
+ (sourceComponentId) => sourceComponentId !== void 0
3474
+ )
3475
+ );
3476
+ const sourceComponents = circuitJson.filter(
3477
+ (element) => element.type === "source_component" && (element.source_component_id === pcbComponent.source_component_id || element.source_component_id !== void 0 && schematicSourceComponentIds.has(element.source_component_id))
3478
+ );
3479
+ const { cadModelParentCenter, componentPosition } = getPcbComponentRenderPlacement({
3480
+ circuitJson,
3481
+ pcbBoard,
3482
+ pcbComponent
3483
+ });
3484
+ const footprintTsx = getComponentFootprintTsx({
3485
+ circuitJson,
3486
+ footprintConversionContext,
3487
+ pcbComponent
3488
+ });
3489
+ const pinLabels = getPinLabels({
3490
+ circuitJson,
3491
+ footprintConversionContext,
3492
+ pcbComponent,
3493
+ sourceComponents
3494
+ });
3495
+ const componentName = pcbComponentRuntimeNameByPcbComponentId.get(
3496
+ pcbComponent.pcb_component_id
3497
+ );
3498
+ if (!componentName) {
3499
+ throw new Error("Expected every PCB component to have a runtime name");
3500
+ }
3501
+ const cadModelGroup = cadModelGroupByPcbComponentId.get(
3502
+ pcbComponent.pcb_component_id
3503
+ );
3504
+ const localizedCadModelGroup = cadModelGroup ? {
3505
+ ...cadModelGroup,
3506
+ parentPlacement: {
3507
+ center: cadModelParentCenter,
3508
+ ccwRotationDegrees: pcbComponent.rotation,
3509
+ layer: pcbComponent.layer === "bottom" ? "bottom" : "top"
3510
+ }
3511
+ } : void 0;
3512
+ const cadModelTsx = localizedCadModelGroup ? generateCadModelGroupModelTsx({
3513
+ cadModelGroup: localizedCadModelGroup,
3514
+ pcbBoard
3515
+ }) : void 0;
3516
+ const componentProps = [
3517
+ `name=${formatJsxStringAttribute(componentName)}`,
3518
+ `pcbX={${componentPosition.x}}`,
3519
+ `pcbY={${componentPosition.y}}`,
3520
+ `pcbRotation="${pcbComponent.rotation}deg"`,
3521
+ `layer="${pcbComponent.layer}"`,
3522
+ "noSchematicRepresentation"
3523
+ ];
3524
+ if (cadModelTsx) {
3525
+ componentProps.push(`cadModel={${cadModelTsx}}`);
3526
+ }
3527
+ if (pinLabels) {
3528
+ componentProps.push(`pinLabels={${JSON.stringify(pinLabels)}}`);
3529
+ }
3530
+ if (sourceComponent?.manufacturer_part_number) {
3531
+ componentProps.push(
3532
+ `manufacturerPartNumber=${formatJsxStringAttribute(sourceComponent.manufacturer_part_number)}`
3533
+ );
3534
+ }
3535
+ if (sourceComponent?.supplier_part_numbers && Object.keys(sourceComponent.supplier_part_numbers).length > 0) {
3536
+ componentProps.push(
3537
+ `supplierPartNumbers={${JSON.stringify(sourceComponent.supplier_part_numbers)}}`
3538
+ );
3539
+ }
3540
+ if (pcbComponent.do_not_place === true) {
3541
+ componentProps.push("doNotPlace");
3542
+ }
3543
+ if (pcbComponent.obstructs_within_bounds === false) {
3544
+ componentProps.push("obstructsWithinBounds={false}");
3545
+ }
3546
+ boardChildren.push(
3547
+ `<chip ${componentProps.join(" ")} footprint={${footprintTsx ?? "<footprint />"}} />`
3548
+ );
3549
+ }
3550
+ };
3551
+
3552
+ // lib/board-converter/stages/convert-standalone-pcb-primitives.ts
3553
+ var convertStandalonePcbPrimitives = ({
3554
+ circuitJson,
3555
+ footprintConversionContext,
3556
+ pcbBoard,
3557
+ boardChildren
3558
+ }) => {
3559
+ const existingPcbComponentIds = new Set(
3560
+ circuitJson.flatMap(
3561
+ (element) => element.type === "pcb_component" ? [element.pcb_component_id] : []
3562
+ )
3563
+ );
3564
+ const standaloneElements = circuitJson.filter(
3565
+ (element) => !("pcb_component_id" in element) || !element.pcb_component_id || !existingPcbComponentIds.has(element.pcb_component_id)
3566
+ );
3567
+ const footprintTsx = generateFootprintTsx(
3568
+ standaloneElements,
3569
+ footprintConversionContext
3570
+ );
3571
+ if (!footprintTsx) return;
3572
+ const footprintContainerProps = [
3573
+ `name="${footprintConversionContext.pcbChipName}"`,
3574
+ "noSchematicRepresentation",
3575
+ "obstructsWithinBounds={false}"
3576
+ ];
3577
+ if (pcbBoard.center.x !== 0) {
3578
+ footprintContainerProps.push(`pcbX={${-pcbBoard.center.x}}`);
3579
+ }
3580
+ if (pcbBoard.center.y !== 0) {
3581
+ footprintContainerProps.push(`pcbY={${-pcbBoard.center.y}}`);
3582
+ }
3583
+ boardChildren.push(
3584
+ `<chip${footprintContainerProps.length > 0 ? ` ${footprintContainerProps.join(" ")}` : ""} footprint={${footprintTsx}} />`
3585
+ );
3586
+ };
3587
+
3588
+ // lib/board-converter/convert-board-circuit-json.ts
3589
+ var conversionStages = [
3590
+ convertBoardProperties,
3591
+ convertPcbComponents,
3592
+ convertBoardNets,
3593
+ convertBoardTraces,
3594
+ convertCopperPours,
3595
+ convertBoardSchematic,
3596
+ convertStandalonePcbPrimitives,
3597
+ convertBoardCadModels
3598
+ ];
3599
+ var convertBoardCircuitJson = ({
3600
+ circuitJson,
3601
+ componentName
3602
+ }) => {
3603
+ const pcbBoard = circuitJson.find((element) => element.type === "pcb_board");
3604
+ if (!pcbBoard) {
3605
+ throw new Error("Board conversion requires a pcb_board element");
3606
+ }
3607
+ const {
3608
+ pcbChipName,
3609
+ pcbComponentRuntimeNameByPcbComponentId,
3610
+ standaloneCadModelRuntimeNameByCadComponentId
3611
+ } = getBoardChildRuntimeNames(circuitJson);
3612
+ const context = {
3613
+ circuitJson,
3614
+ footprintConversionContext: createComponentPreservingBoardConversionContext(
3615
+ {
3616
+ circuitJson,
3617
+ pcbChipName,
3618
+ pcbComponentRuntimeNameByPcbComponentId
3619
+ }
3620
+ ),
3621
+ pcbComponentRuntimeNameByPcbComponentId,
3622
+ standaloneCadModelRuntimeNameByCadComponentId,
3623
+ pcbBoard,
3624
+ boardProps: [],
3625
+ boardChildren: []
3626
+ };
3627
+ for (const convertStage of conversionStages) {
3628
+ convertStage(context);
3629
+ }
3630
+ const boardProps = context.boardProps.join(" ");
3631
+ const boardChildren = context.boardChildren.join("\n");
2722
3632
  return `
2723
3633
  ${componentName ? `export const ${componentName} =` : "export default"} () => (
2724
- <board${boardPropsStr ? ` ${boardPropsStr}` : ""}>
2725
- ${children}
3634
+ <board${boardProps ? ` ${boardProps}` : ""}>
3635
+ ${boardChildren.replace(/\n/g, "\n ")}
2726
3636
  </board>
2727
3637
  )
2728
3638
  ${componentName ? `export default ${componentName}` : ""}
2729
3639
  `.replace(/\n\s*\n/g, "\n").trim();
2730
3640
  };
2731
3641
 
3642
+ // lib/get-board-using-template.ts
3643
+ var getBoardUsingTemplate = ({
3644
+ circuitJson,
3645
+ componentName
3646
+ }) => convertBoardCircuitJson({ circuitJson, componentName });
3647
+
2732
3648
  // lib/get-pin-attributes.ts
2733
3649
  var attributeNames = {
2734
3650
  is_input: "isInput",