altium-toolkit 1.0.9 → 1.1.0

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 (79) hide show
  1. package/docs/api.md +6 -2
  2. package/docs/model-format.md +29 -4
  3. package/docs/schemas/altium_toolkit/ci_artifact_bundle_a1.schema.json +80 -0
  4. package/docs/schemas/altium_toolkit/contract_gate_a1.schema.json +34 -0
  5. package/docs/schemas/altium_toolkit/draftsman_board_view_cache_a1.schema.json +115 -0
  6. package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +166 -0
  7. package/docs/schemas/altium_toolkit/host_capabilities_a1.schema.json +39 -0
  8. package/docs/schemas/altium_toolkit/library_merge_plan_a1.schema.json +56 -0
  9. package/docs/schemas/altium_toolkit/library_qa_a1.schema.json +70 -0
  10. package/docs/schemas/altium_toolkit/netlist_a1.schema.json +6 -0
  11. package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +856 -7
  12. package/docs/schemas/altium_toolkit/parser_compatibility_fuzz_a1.schema.json +25 -0
  13. package/docs/schemas/altium_toolkit/pcb_bom_profile_a1.schema.json +48 -0
  14. package/docs/schemas/altium_toolkit/pcb_layer_stack_a1.schema.json +98 -0
  15. package/docs/schemas/altium_toolkit/pcb_layer_stack_fidelity_a1.schema.json +66 -0
  16. package/docs/schemas/altium_toolkit/pcb_placed_footprint_extraction_a1.schema.json +31 -0
  17. package/docs/schemas/altium_toolkit/pcb_review_metadata_a1.schema.json +62 -0
  18. package/docs/schemas/altium_toolkit/pcb_rigid_flex_topology_a1.schema.json +52 -0
  19. package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +27 -0
  20. package/docs/schemas/altium_toolkit/pcblib_parity_a1.schema.json +24 -0
  21. package/docs/schemas/altium_toolkit/project_bom_pnp_reconciliation_a1.schema.json +63 -0
  22. package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +6 -0
  23. package/docs/schemas/altium_toolkit/project_document_graph_a1.schema.json +33 -0
  24. package/docs/schemas/altium_toolkit/project_outjob_digest_a1.schema.json +46 -0
  25. package/docs/schemas/altium_toolkit/project_script_a1.schema.json +50 -0
  26. package/docs/schemas/altium_toolkit/schematic_render_ops_a1.schema.json +55 -0
  27. package/docs/schemas/altium_toolkit/schematic_template_extraction_a1.schema.json +37 -0
  28. package/docs/schemas/altium_toolkit/svg_model_cross_link_a1.schema.json +39 -0
  29. package/package.json +1 -1
  30. package/src/core/altium/AltiumParser.mjs +12 -2
  31. package/src/core/altium/CiArtifactBundleBuilder.mjs +213 -0
  32. package/src/core/altium/ContractGateReportBuilder.mjs +351 -0
  33. package/src/core/altium/DraftsmanBoardViewMetadataBuilder.mjs +653 -0
  34. package/src/core/altium/DraftsmanDigestParser.mjs +928 -0
  35. package/src/core/altium/DraftsmanImagePayloadManifestBuilder.mjs +178 -0
  36. package/src/core/altium/HostCapabilityDiagnosticsBuilder.mjs +271 -0
  37. package/src/core/altium/LibraryQaReportBuilder.mjs +504 -0
  38. package/src/core/altium/LibraryRenderManifestBuilder.mjs +172 -2
  39. package/src/core/altium/ParserCompatibilityFuzzer.mjs +192 -0
  40. package/src/core/altium/PcbBomProfileBuilder.mjs +263 -0
  41. package/src/core/altium/PcbComponentKindPolicy.mjs +146 -0
  42. package/src/core/altium/PcbLayerStackFidelityReportBuilder.mjs +141 -0
  43. package/src/core/altium/PcbLayerStackInterchangeParser.mjs +453 -0
  44. package/src/core/altium/PcbLayerStackQueryHelper.mjs +195 -0
  45. package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +906 -0
  46. package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +488 -0
  47. package/src/core/altium/PcbLibModelParser.mjs +2 -0
  48. package/src/core/altium/PcbLibParityReportBuilder.mjs +242 -0
  49. package/src/core/altium/PcbModelParser.mjs +211 -22
  50. package/src/core/altium/PcbPadStackParser.mjs +171 -2
  51. package/src/core/altium/PcbPickPlacePositionResolver.mjs +11 -1
  52. package/src/core/altium/PcbPlacedFootprintManifestBuilder.mjs +338 -0
  53. package/src/core/altium/PcbPolygonRecordParser.mjs +120 -0
  54. package/src/core/altium/PcbRegionPrimitiveParser.mjs +71 -2
  55. package/src/core/altium/PcbReviewDrillMetadataBuilder.mjs +301 -0
  56. package/src/core/altium/PcbReviewMetadataBuilder.mjs +373 -0
  57. package/src/core/altium/PcbReviewPolygonRealizationBuilder.mjs +269 -0
  58. package/src/core/altium/PcbReviewRouteHighlightProfileBuilder.mjs +298 -0
  59. package/src/core/altium/PcbRigidFlexTopologyBuilder.mjs +171 -0
  60. package/src/core/altium/PcbRouteAnalysisBuilder.mjs +730 -0
  61. package/src/core/altium/PcbStatisticsBuilder.mjs +9 -0
  62. package/src/core/altium/PrintableTextDecoder.mjs +70 -6
  63. package/src/core/altium/PrjPcbModelParser.mjs +69 -2
  64. package/src/core/altium/PrjScrModelParser.mjs +386 -0
  65. package/src/core/altium/ProjectBomPnpReconciliationBuilder.mjs +237 -0
  66. package/src/core/altium/ProjectDesignBundleBuilder.mjs +76 -2
  67. package/src/core/altium/ProjectDocumentGraphBuilder.mjs +280 -0
  68. package/src/core/altium/ProjectNetlistExporter.mjs +5 -1
  69. package/src/core/altium/ProjectOutJobDigestBuilder.mjs +424 -13
  70. package/src/core/altium/SvgModelCrossLinkValidator.mjs +435 -0
  71. package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +300 -96
  72. package/src/core/circuit-json/CircuitJsonModelAdapterPcbElements.mjs +244 -0
  73. package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
  74. package/src/parser.mjs +21 -0
  75. package/src/ui/PcbFootprintPrimitiveSelector.mjs +13 -1
  76. package/src/ui/PcbScene3dBuilder.mjs +26 -4
  77. package/src/ui/PcbSvgRenderer.mjs +65 -0
  78. package/src/ui/SchematicRenderOpsSidecarBuilder.mjs +554 -0
  79. package/src/ui/SchematicSvgRenderer.mjs +48 -2
@@ -4,8 +4,72 @@
4
4
 
5
5
  import { CircuitJsonModelSchema } from './CircuitJsonModelSchema.mjs'
6
6
  import { CircuitJsonModelAdapterPrimitives } from './CircuitJsonModelAdapterPrimitives.mjs'
7
+ import { CircuitJsonModelAdapterPcbElements } from './CircuitJsonModelAdapterPcbElements.mjs'
7
8
 
8
9
  const Primitives = CircuitJsonModelAdapterPrimitives
10
+ const PcbElements = CircuitJsonModelAdapterPcbElements
11
+ const ALTIUM_TOOLKIT_SIDECARS = [
12
+ {
13
+ type: 'altium_toolkit_pcb_layer_stack',
14
+ paths: [['pcb', 'layerStackReadModel']],
15
+ schema: 'altium-toolkit.pcb.layer-stack.a1'
16
+ },
17
+ {
18
+ type: 'altium_toolkit_pcb_rigid_flex_topology',
19
+ paths: [['pcb', 'rigidFlexTopology']],
20
+ schema: 'altium-toolkit.pcb.rigid-flex-topology.a1'
21
+ },
22
+ {
23
+ type: 'altium_toolkit_pcb_review_metadata',
24
+ paths: [['pcb', 'reviewMetadata']],
25
+ schema: 'altium-toolkit.pcb.review-metadata.a1'
26
+ },
27
+ {
28
+ type: 'altium_toolkit_pcb_placed_footprint_extraction',
29
+ paths: [['pcb', 'footprintExtractionManifest']],
30
+ schema: 'altium-toolkit.pcb.placed-footprint-extraction.a1'
31
+ },
32
+ {
33
+ type: 'altium_toolkit_pcblib_parity',
34
+ paths: [['pcbLibrary', 'parityReport']],
35
+ schema: 'altium-toolkit.pcblib.parity.a1'
36
+ },
37
+ {
38
+ type: 'altium_toolkit_project_outjob_digest',
39
+ paths: [['project', 'outJobDigest']],
40
+ schema: 'altium-toolkit.project.outjob-digest.a1'
41
+ },
42
+ {
43
+ type: 'altium_toolkit_project_document_graph',
44
+ paths: [['project', 'documentGraph'], ['documentGraph']],
45
+ schema: 'altium-toolkit.project.document-graph.a1'
46
+ },
47
+ {
48
+ type: 'altium_toolkit_project_bom_pnp_reconciliation',
49
+ paths: [['reconciliation']],
50
+ schema: 'altium-toolkit.project.bom-pnp-reconciliation.a1'
51
+ },
52
+ {
53
+ type: 'altium_toolkit_draftsman_image_payloads',
54
+ paths: [['draftsman', 'imagePayloads']],
55
+ schema: 'altium-toolkit.draftsman.image-payloads.a1'
56
+ },
57
+ {
58
+ type: 'altium_toolkit_draftsman_board_view_metadata',
59
+ paths: [['draftsman', 'boardViewMetadata']],
60
+ schema: 'altium-toolkit.draftsman.board-view-cache.a1'
61
+ },
62
+ {
63
+ type: 'altium_toolkit_contract_gate',
64
+ paths: [['contractGate']],
65
+ schema: 'altium-toolkit.contract-gate.a1'
66
+ },
67
+ {
68
+ type: 'altium_toolkit_host_capabilities',
69
+ paths: [['hostCapabilities']],
70
+ schema: 'altium-toolkit.host-capabilities.a1'
71
+ }
72
+ ]
9
73
 
10
74
  /**
11
75
  * Converts between legacy renderer models and Circuit JSON element arrays.
@@ -53,6 +117,7 @@ export class CircuitJsonModelAdapter {
53
117
  }
54
118
 
55
119
  CircuitJsonModelAdapter.#appendBom(circuitJson, model, idScope)
120
+ CircuitJsonModelAdapter.#appendSidecars(circuitJson, model, idScope)
56
121
  CircuitJsonModelAdapter.#attachCompatibility(circuitJson, model)
57
122
 
58
123
  return CircuitJsonModelSchema.attach(circuitJson)
@@ -175,7 +240,8 @@ export class CircuitJsonModelAdapter {
175
240
  width: Primitives.number(component.width, 0),
176
241
  height: Primitives.number(component.height, 0)
177
242
  },
178
- rotation: Primitives.number(component.rotation, 0)
243
+ rotation: Primitives.number(component.rotation, 0),
244
+ is_box_with_pins: true
179
245
  })
180
246
  }
181
247
 
@@ -204,9 +270,8 @@ export class CircuitJsonModelAdapter {
204
270
  pin.name || pin.designator || pinIndex,
205
271
  String(pinIndex + 1)
206
272
  ),
207
- pin_number: Primitives.string(
208
- pin.pinNumber || pin.designator || pin.name,
209
- String(pinIndex + 1)
273
+ ...CircuitJsonModelAdapter.#pinNumberField(
274
+ pin.pinNumber || pin.designator || pin.name
210
275
  )
211
276
  })
212
277
  circuitJson.push({
@@ -232,7 +297,8 @@ export class CircuitJsonModelAdapter {
232
297
  circuitJson.push({
233
298
  type: 'source_net',
234
299
  source_net_id: sourceNetId,
235
- name: Primitives.string(net.name, `NET_${netIndex + 1}`)
300
+ name: Primitives.string(net.name, `NET_${netIndex + 1}`),
301
+ member_source_group_ids: []
236
302
  })
237
303
  }
238
304
 
@@ -269,7 +335,7 @@ export class CircuitJsonModelAdapter {
269
335
  */
270
336
  static #appendPcb(circuitJson, model, idScope) {
271
337
  const pcb = model.pcb || {}
272
- const componentIds = new Map()
338
+ const componentRefs = new Map()
273
339
  const sourceNetIds = new Map()
274
340
  const boardId = Primitives.id(idScope, ['pcb_board'])
275
341
 
@@ -295,7 +361,8 @@ export class CircuitJsonModelAdapter {
295
361
  name: Primitives.string(
296
362
  net.name || net.netName,
297
363
  `NET_${netIndex + 1}`
298
- )
364
+ ),
365
+ member_source_group_ids: []
299
366
  })
300
367
  }
301
368
 
@@ -310,9 +377,12 @@ export class CircuitJsonModelAdapter {
310
377
  'pcb_component',
311
378
  component.designator || component.name || componentIndex
312
379
  ])
313
- componentIds.set(
380
+ componentRefs.set(
314
381
  Primitives.componentKey(component, componentIndex),
315
- sourceComponentId
382
+ {
383
+ pcbComponentId,
384
+ sourceComponentId
385
+ }
316
386
  )
317
387
  circuitJson.push(
318
388
  CircuitJsonModelAdapter.#sourceComponent(
@@ -345,7 +415,7 @@ export class CircuitJsonModelAdapter {
345
415
  idScope,
346
416
  pad,
347
417
  padIndex,
348
- componentIds,
418
+ componentRefs,
349
419
  sourceNetIds
350
420
  )
351
421
  }
@@ -367,8 +437,7 @@ export class CircuitJsonModelAdapter {
367
437
  circuitJson,
368
438
  idScope,
369
439
  via,
370
- viaIndex,
371
- sourceNetIds
440
+ viaIndex
372
441
  )
373
442
  }
374
443
  }
@@ -415,7 +484,7 @@ export class CircuitJsonModelAdapter {
415
484
  * @param {string} idScope
416
485
  * @param {Record<string, unknown>} pad
417
486
  * @param {number} padIndex
418
- * @param {Map<string, string>} componentIds
487
+ * @param {Map<string, { pcbComponentId: string, sourceComponentId: string }>} componentRefs
419
488
  * @param {Map<string, string>} sourceNetIds
420
489
  * @returns {void}
421
490
  */
@@ -424,12 +493,14 @@ export class CircuitJsonModelAdapter {
424
493
  idScope,
425
494
  pad,
426
495
  padIndex,
427
- componentIds,
496
+ componentRefs,
428
497
  sourceNetIds
429
498
  ) {
499
+ const componentRef =
500
+ componentRefs.get(String(pad.componentIndex)) ||
501
+ componentRefs.get('0')
430
502
  const sourceComponentId =
431
- componentIds.get(String(pad.componentIndex)) ||
432
- componentIds.get('0') ||
503
+ componentRef?.sourceComponentId ||
433
504
  Primitives.id(idScope, ['source_component', 'unassigned'])
434
505
  const sourcePortId = Primitives.sourcePortId(
435
506
  idScope,
@@ -438,69 +509,74 @@ export class CircuitJsonModelAdapter {
438
509
  sourceComponentId
439
510
  )
440
511
  const pcbPortId = Primitives.id(idScope, ['pcb_port', sourcePortId])
441
- const common = {
442
- source_port_id: sourcePortId,
443
- pcb_port_id: pcbPortId,
444
- pcb_component_id: Primitives.id(idScope, [
445
- 'pcb_component',
446
- pad.componentIndex ?? 'unassigned'
447
- ]),
448
- center: Primitives.milPoint(pad.x, pad.y),
449
- layer: Primitives.side(pad.layer),
450
- port_hints: [
451
- Primitives.string(
452
- pad.name || pad.pinName || pad.designator,
453
- String(padIndex + 1)
454
- )
455
- ]
456
- }
457
- const sourceNetId = Primitives.netIdForPrimitive(
458
- idScope,
459
- pad,
460
- sourceNetIds
461
- )
512
+ const pcbComponentId =
513
+ componentRef?.pcbComponentId ||
514
+ Primitives.id(idScope, ['pcb_component', 'unassigned'])
515
+ const center = Primitives.milPoint(pad.x, pad.y)
516
+ const layer = Primitives.side(pad.layer)
517
+ const portHints = [
518
+ Primitives.string(
519
+ pad.name || pad.pinName || pad.designator,
520
+ String(padIndex + 1)
521
+ )
522
+ ]
523
+ Primitives.netIdForPrimitive(idScope, pad, sourceNetIds)
462
524
 
463
525
  circuitJson.push({
464
526
  type: 'source_port',
465
527
  source_port_id: sourcePortId,
466
528
  source_component_id: sourceComponentId,
467
- name: common.port_hints[0],
468
- pin_number: common.port_hints[0]
529
+ name: portHints[0],
530
+ port_hints: portHints,
531
+ ...CircuitJsonModelAdapter.#pinNumberField(portHints[0])
469
532
  })
470
533
  circuitJson.push({
471
534
  type: 'pcb_port',
472
- ...common,
473
- source_net_id: sourceNetId
535
+ pcb_port_id: pcbPortId,
536
+ source_port_id: sourcePortId,
537
+ pcb_component_id: pcbComponentId,
538
+ x: center.x,
539
+ y: center.y,
540
+ layers: Primitives.isThroughHolePad(pad)
541
+ ? ['top', 'bottom']
542
+ : [layer]
474
543
  })
475
544
 
476
545
  if (Primitives.isThroughHolePad(pad)) {
477
- circuitJson.push({
478
- type: pad.isPlated === false ? 'pcb_hole' : 'pcb_plated_hole',
479
- ...common,
480
- outer_diameter: Primitives.milNumber(
481
- pad.sizeTopX || pad.sizeX || pad.diameter,
482
- 0
483
- ),
484
- hole_diameter: Primitives.milNumber(pad.holeDiameter, 0),
485
- shape: Primitives.padShape(pad)
486
- })
546
+ circuitJson.push(
547
+ pad.isPlated === false
548
+ ? PcbElements.hole(
549
+ idScope,
550
+ pad,
551
+ padIndex,
552
+ pcbComponentId,
553
+ center
554
+ )
555
+ : PcbElements.platedHole(
556
+ idScope,
557
+ pad,
558
+ padIndex,
559
+ pcbComponentId,
560
+ pcbPortId,
561
+ center,
562
+ portHints
563
+ )
564
+ )
487
565
  return
488
566
  }
489
567
 
490
- circuitJson.push({
491
- type: 'pcb_smtpad',
492
- ...common,
493
- shape: Primitives.padShape(pad),
494
- width: Primitives.milNumber(
495
- pad.sizeTopX || pad.sizeX || pad.width,
496
- 0
497
- ),
498
- height: Primitives.milNumber(
499
- pad.sizeTopY || pad.sizeY || pad.height,
500
- 0
501
- ),
502
- rotation: Primitives.number(pad.rotation || pad.holeRotation, 0)
503
- })
568
+ circuitJson.push(
569
+ PcbElements.smtPad(
570
+ idScope,
571
+ pad,
572
+ padIndex,
573
+ pcbComponentId,
574
+ pcbPortId,
575
+ center,
576
+ layer,
577
+ portHints
578
+ )
579
+ )
504
580
  }
505
581
 
506
582
  /**
@@ -523,14 +599,16 @@ export class CircuitJsonModelAdapter {
523
599
  'source_trace',
524
600
  track.netName || track.netIndex || trackIndex
525
601
  ])
602
+ const sourceNetId = Primitives.netIdForPrimitive(
603
+ idScope,
604
+ track,
605
+ sourceNetIds
606
+ )
526
607
  circuitJson.push({
527
608
  type: 'source_trace',
528
609
  source_trace_id: sourceTraceId,
529
- source_net_id: Primitives.netIdForPrimitive(
530
- idScope,
531
- track,
532
- sourceNetIds
533
- )
610
+ connected_source_port_ids: [],
611
+ connected_source_net_ids: sourceNetId ? [sourceNetId] : []
534
612
  })
535
613
  circuitJson.push({
536
614
  type: 'pcb_trace',
@@ -561,18 +639,12 @@ export class CircuitJsonModelAdapter {
561
639
  * @param {string} idScope
562
640
  * @param {Record<string, unknown>} via
563
641
  * @param {number} viaIndex
564
- * @param {Map<string, string>} sourceNetIds
565
642
  * @returns {void}
566
643
  */
567
- static #appendPcbVia(circuitJson, idScope, via, viaIndex, sourceNetIds) {
644
+ static #appendPcbVia(circuitJson, idScope, via, viaIndex) {
568
645
  circuitJson.push({
569
646
  type: 'pcb_via',
570
647
  pcb_via_id: Primitives.id(idScope, ['pcb_via', viaIndex]),
571
- source_net_id: Primitives.netIdForPrimitive(
572
- idScope,
573
- via,
574
- sourceNetIds
575
- ),
576
648
  x: Primitives.milNumber(via.x, 0),
577
649
  y: Primitives.milNumber(via.y, 0),
578
650
  outer_diameter: Primitives.milNumber(via.diameter, 0),
@@ -642,6 +714,107 @@ export class CircuitJsonModelAdapter {
642
714
  }
643
715
  }
644
716
 
717
+ /**
718
+ * Appends serialized Altium Toolkit sidecar elements.
719
+ * @param {object[]} circuitJson
720
+ * @param {Record<string, unknown>} model
721
+ * @param {string} idScope
722
+ * @returns {void}
723
+ */
724
+ static #appendSidecars(circuitJson, model, idScope) {
725
+ for (const descriptor of ALTIUM_TOOLKIT_SIDECARS) {
726
+ const payload = CircuitJsonModelAdapter.#sidecarPayload(
727
+ model,
728
+ descriptor.paths
729
+ )
730
+ if (!payload) continue
731
+
732
+ circuitJson.push(
733
+ CircuitJsonModelAdapter.#sidecarElement(
734
+ descriptor,
735
+ payload,
736
+ model,
737
+ idScope
738
+ )
739
+ )
740
+ }
741
+ }
742
+
743
+ /**
744
+ * Builds one custom Altium Toolkit Circuit JSON sidecar element.
745
+ * @param {{ type: string, schema: string }} descriptor
746
+ * @param {Record<string, unknown>} payload
747
+ * @param {Record<string, unknown>} model
748
+ * @param {string} idScope
749
+ * @returns {object}
750
+ */
751
+ static #sidecarElement(descriptor, payload, model, idScope) {
752
+ return {
753
+ type: descriptor.type,
754
+ altium_toolkit_sidecar_id: Primitives.id(idScope, [
755
+ 'altium_toolkit_sidecar',
756
+ descriptor.type
757
+ ]),
758
+ source_document: CircuitJsonModelAdapter.#sourceDocument(model),
759
+ schema: Primitives.string(payload.schema, descriptor.schema),
760
+ payload
761
+ }
762
+ }
763
+
764
+ /**
765
+ * Returns the first available sidecar payload from candidate model paths.
766
+ * @param {Record<string, unknown>} model
767
+ * @param {string[][]} paths
768
+ * @returns {Record<string, unknown> | null}
769
+ */
770
+ static #sidecarPayload(model, paths) {
771
+ for (const path of paths) {
772
+ const payload = CircuitJsonModelAdapter.#valueAtPath(model, path)
773
+ if (CircuitJsonModelAdapter.#isSidecarPayload(payload)) {
774
+ return payload
775
+ }
776
+ }
777
+ return null
778
+ }
779
+
780
+ /**
781
+ * Returns a nested object value.
782
+ * @param {Record<string, unknown>} value
783
+ * @param {string[]} path
784
+ * @returns {unknown}
785
+ */
786
+ static #valueAtPath(value, path) {
787
+ return path.reduce(
788
+ (current, key) =>
789
+ current && typeof current === 'object'
790
+ ? current[key]
791
+ : undefined,
792
+ value
793
+ )
794
+ }
795
+
796
+ /**
797
+ * Returns true when a value can be serialized as a sidecar payload.
798
+ * @param {unknown} value
799
+ * @returns {value is Record<string, unknown>}
800
+ */
801
+ static #isSidecarPayload(value) {
802
+ return !!value && typeof value === 'object' && !Array.isArray(value)
803
+ }
804
+
805
+ /**
806
+ * Builds the source document identity for sidecar elements.
807
+ * @param {Record<string, unknown>} model
808
+ * @returns {{ kind: string, file_type: string, file_name: string }}
809
+ */
810
+ static #sourceDocument(model) {
811
+ return {
812
+ kind: Primitives.string(model.kind, 'document'),
813
+ file_type: Primitives.string(model.fileType, ''),
814
+ file_name: Primitives.string(model.fileName, '')
815
+ }
816
+ }
817
+
645
818
  /**
646
819
  * Appends one schematic line or trace.
647
820
  * @param {object[]} circuitJson
@@ -664,33 +837,47 @@ export class CircuitJsonModelAdapter {
664
837
  x2: Primitives.number(line.x2, 0),
665
838
  y2: Primitives.number(line.y2, 0),
666
839
  stroke_width: Primitives.number(line.width, 1),
667
- is_dashed: line.dashed === true
840
+ is_dashed: line.dashed === true,
841
+ color: '#000000'
668
842
  }
669
843
  circuitJson.push(lineElement)
670
844
 
671
845
  if (line.kind === 'wire' || line.netName || line.netIndex) {
846
+ const sourceNetId =
847
+ netIds.get(String(line.netName)) ||
848
+ Primitives.sourceNetId(
849
+ idScope,
850
+ line.netName || line.netIndex || lineIndex
851
+ )
852
+ const sourceTraceId = Primitives.id(idScope, [
853
+ 'source_trace',
854
+ line.netName || line.netIndex || lineIndex,
855
+ lineIndex
856
+ ])
857
+ circuitJson.push({
858
+ type: 'source_trace',
859
+ source_trace_id: sourceTraceId,
860
+ connected_source_port_ids: [],
861
+ connected_source_net_ids: sourceNetId ? [sourceNetId] : []
862
+ })
672
863
  circuitJson.push({
673
864
  type: 'schematic_trace',
674
865
  schematic_trace_id: Primitives.id(idScope, [
675
866
  'schematic_trace',
676
867
  lineIndex
677
868
  ]),
678
- source_trace_id: Primitives.id(idScope, [
679
- 'source_trace',
680
- line.netName || line.netIndex || lineIndex
681
- ]),
682
- source_net_id:
683
- netIds.get(String(line.netName)) ||
684
- Primitives.sourceNetId(
685
- idScope,
686
- line.netName || line.netIndex || lineIndex
687
- ),
869
+ source_trace_id: sourceTraceId,
870
+ junctions: [],
688
871
  edges: [
689
872
  {
690
- x1: lineElement.x1,
691
- y1: lineElement.y1,
692
- x2: lineElement.x2,
693
- y2: lineElement.y2
873
+ from: {
874
+ x: lineElement.x1,
875
+ y: lineElement.y1
876
+ },
877
+ to: {
878
+ x: lineElement.x2,
879
+ y: lineElement.y2
880
+ }
694
881
  }
695
882
  ]
696
883
  })
@@ -712,11 +899,11 @@ export class CircuitJsonModelAdapter {
712
899
  )
713
900
  const base = {
714
901
  text: textValue,
715
- anchor_position: Primitives.point(text.x, text.y),
716
- anchor_alignment: 'center'
902
+ position: Primitives.point(text.x, text.y)
717
903
  }
718
904
 
719
905
  if (Primitives.isNetLabel(text)) {
906
+ const center = Primitives.point(text.x, text.y)
720
907
  circuitJson.push({
721
908
  type: 'schematic_net_label',
722
909
  schematic_net_label_id: Primitives.id(idScope, [
@@ -727,7 +914,10 @@ export class CircuitJsonModelAdapter {
727
914
  idScope,
728
915
  textValue || textIndex
729
916
  ),
730
- ...base
917
+ text: textValue,
918
+ center,
919
+ anchor_position: center,
920
+ anchor_side: 'top'
731
921
  })
732
922
  return
733
923
  }
@@ -738,10 +928,24 @@ export class CircuitJsonModelAdapter {
738
928
  'schematic_text',
739
929
  textIndex
740
930
  ]),
741
- ...base
931
+ ...base,
932
+ font_size: Primitives.number(text.fontSize || text.size, 0.18),
933
+ rotation: Primitives.number(text.rotation, 0),
934
+ anchor: 'center',
935
+ color: '#000000'
742
936
  })
743
937
  }
744
938
 
939
+ /**
940
+ * Returns an optional numeric pin number field.
941
+ * @param {unknown} value
942
+ * @returns {{ pin_number?: number }}
943
+ */
944
+ static #pinNumberField(value) {
945
+ const pinNumber = Primitives.number(value, null)
946
+ return pinNumber === null ? {} : { pin_number: pinNumber }
947
+ }
948
+
745
949
  /**
746
950
  * Returns a source component id for one schematic pin.
747
951
  * @param {Record<string, unknown>} pin