circuitjson-toolkit 1.0.16 → 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 (155) hide show
  1. package/AGENTS.md +15 -0
  2. package/NOTICE.md +31 -0
  3. package/README.md +267 -107
  4. package/docs/api.md +501 -96
  5. package/docs/capabilities.md +70 -0
  6. package/docs/migration/behaviors.md +45 -0
  7. package/docs/migration/parser.md +60 -0
  8. package/docs/migration/renderers.md +515 -0
  9. package/docs/migration/root.md +740 -0
  10. package/docs/migration.md +120 -0
  11. package/docs/model-format.md +175 -57
  12. package/docs/provenance.md +206 -0
  13. package/docs/release-notes-v1.1.0.md +154 -0
  14. package/docs/testing.md +117 -7
  15. package/package.json +31 -5
  16. package/spec/api-baseline-v1.0.17.json +1 -0
  17. package/spec/baseline-provenance-v1.0.17.json +7 -0
  18. package/spec/circuitjson-schema-snapshot.json +321 -0
  19. package/spec/circuitjson-schema-source.json +28 -0
  20. package/spec/feature-preservation.json +1 -0
  21. package/spec/library-scope.md +27 -20
  22. package/src/capabilities.mjs +1 -0
  23. package/src/core/ArchiveEntryPath.mjs +93 -0
  24. package/src/core/ArchiveLimits.mjs +31 -0
  25. package/src/core/ArchiveLimitsValidator.mjs +107 -0
  26. package/src/core/AsyncInputOwnership.mjs +56 -0
  27. package/src/core/AttachedValueLimits.mjs +67 -0
  28. package/src/core/CircuitJsonDiagnosticIndexer.mjs +184 -0
  29. package/src/core/CircuitJsonDocument.mjs +19 -61
  30. package/src/core/CircuitJsonElementTypes.mjs +10 -0
  31. package/src/core/CircuitJsonElementValidator.mjs +98 -847
  32. package/src/core/CircuitJsonIndexer.mjs +274 -194
  33. package/src/core/CircuitJsonManufacturingBuilder.mjs +167 -164
  34. package/src/core/CircuitJsonParser.mjs +75 -13
  35. package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +12 -6
  36. package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +108 -10
  37. package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +1 -1
  38. package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +44 -38
  39. package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +146 -28
  40. package/src/core/CircuitJsonPcbPrimitiveFields.mjs +70 -4
  41. package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +18 -2
  42. package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +26 -9
  43. package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +7 -6
  44. package/src/core/CircuitJsonSerializedInputAudit.mjs +87 -0
  45. package/src/core/CircuitJsonSourceMetadata.mjs +5 -1
  46. package/src/core/CircuitJsonSupportMatrixBuilder.mjs +3 -1
  47. package/src/core/CircuitJsonToolkitElementSchema.mjs +218 -0
  48. package/src/core/CircuitJsonUnitParsers.mjs +101 -0
  49. package/src/core/CircuitJsonUnits.mjs +13 -87
  50. package/src/core/CircuitJsonUpstreamSchema.mjs +9 -0
  51. package/src/core/CircuitJsonUpstreamValidator.mjs +418 -0
  52. package/src/core/CircuitJsonValidationUnits.mjs +6 -0
  53. package/src/core/ManufacturingService.mjs +323 -0
  54. package/src/core/Parser.mjs +343 -0
  55. package/src/core/ParserOptions.mjs +333 -0
  56. package/src/core/PcbBoundsSelectionModel.mjs +55 -19
  57. package/src/core/PcbDiagnosticFocusModel.mjs +42 -11
  58. package/src/core/PcbInteractionIndex.mjs +368 -0
  59. package/src/core/PcbInteractionPrimitiveModel.mjs +393 -62
  60. package/src/core/ProjectAsyncInputOwner.mjs +70 -0
  61. package/src/core/ProjectLoader.mjs +975 -0
  62. package/src/core/SimulationService.mjs +790 -0
  63. package/src/core/ToolkitCapabilities.mjs +130 -0
  64. package/src/core/ZipArchiveInspector.mjs +649 -0
  65. package/src/core/context/BinaryDataSnapshot.mjs +217 -0
  66. package/src/core/context/CircuitJsonContextIndexes.mjs +96 -0
  67. package/src/core/context/CircuitJsonDerivedCache.mjs +114 -0
  68. package/src/core/context/CircuitJsonDocumentContext.mjs +353 -0
  69. package/src/core/context/CircuitJsonLegacyModel.mjs +147 -0
  70. package/src/core/context/CircuitJsonLegacyNormalizer.mjs +847 -0
  71. package/src/core/context/CircuitJsonMetadataBoundary.mjs +76 -0
  72. package/src/core/context/CircuitJsonModelFreezeTraversal.mjs +179 -0
  73. package/src/core/context/CircuitJsonReadOnlyDocument.mjs +920 -0
  74. package/src/core/context/CircuitJsonSchematicTableNormalizer.mjs +314 -0
  75. package/src/core/context/CircuitJsonValidationAuthority.mjs +39 -0
  76. package/src/core/context/CircuitJsonValidationProof.mjs +217 -0
  77. package/src/core/context/PcbPrimitivePreparation.mjs +198 -0
  78. package/src/core/context/PcbSpatialIndex.mjs +701 -0
  79. package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +128 -0
  80. package/src/core/context/StructuredDataSnapshot.mjs +683 -0
  81. package/src/core/contracts/DocumentResult.mjs +198 -0
  82. package/src/core/contracts/ProjectResult.mjs +96 -0
  83. package/src/core/contracts/RuntimeProxyBoundary.mjs +48 -0
  84. package/src/core/contracts/ToolkitAsset.mjs +493 -0
  85. package/src/core/contracts/ToolkitDiagnostic.mjs +38 -0
  86. package/src/core/contracts/ToolkitError.mjs +176 -0
  87. package/src/core/contracts/ToolkitProgress.mjs +89 -0
  88. package/src/core/interaction/CanonicalInteractionOptions.mjs +246 -0
  89. package/src/core/interaction/PcbInteractionBounds.mjs +167 -0
  90. package/src/core/query/CircuitTraversal.mjs +343 -0
  91. package/src/core/query/ComponentGrouping.mjs +275 -0
  92. package/src/core/query/QueryNetlistBuilder.mjs +306 -0
  93. package/src/core/query/QueryService.mjs +435 -0
  94. package/src/core/query/RegexPattern.mjs +75 -0
  95. package/src/core/rendering/CanonicalBomOrder.mjs +81 -0
  96. package/src/core/rendering/CanonicalBomRows.mjs +92 -0
  97. package/src/core/rendering/CanonicalRenderOptions.mjs +498 -0
  98. package/src/core/rendering/CanonicalSvgDocument.mjs +102 -0
  99. package/src/core/rendering/PcbRenderPlan.mjs +429 -0
  100. package/src/core/rendering/SchematicSheetSelector.mjs +335 -0
  101. package/src/core/scene3d/PcbScene3dBuilder.mjs +906 -0
  102. package/src/core/scene3d/PcbScene3dPreparator.mjs +47 -0
  103. package/src/core/scene3d/Scene3dAssetIndex.mjs +284 -0
  104. package/src/core/scene3d/Scene3dBoardModel.mjs +596 -0
  105. package/src/core/scene3d/Scene3dDocumentMetadata.mjs +167 -0
  106. package/src/core/scene3d/Scene3dFreeze.mjs +37 -0
  107. package/src/core/scene3d/Scene3dIdRegistry.mjs +34 -0
  108. package/src/core/scene3d/Scene3dInputPreflight.mjs +193 -0
  109. package/src/core/scene3d/Scene3dMaterials.mjs +58 -0
  110. package/src/core/scene3d/Scene3dModelReference.mjs +134 -0
  111. package/src/core/scene3d/Scene3dOptions.mjs +363 -0
  112. package/src/core/scene3d/SceneAssetResolver.mjs +441 -0
  113. package/src/core/simulation/SimulationParameterCloner.mjs +543 -0
  114. package/src/core/worker/ParserWorkerClient.mjs +997 -0
  115. package/src/core/worker/ToolkitWorkerProtocol.mjs +412 -0
  116. package/src/core/worker/WorkerRequestData.mjs +623 -0
  117. package/src/core/worker/WorkerResponseData.mjs +905 -0
  118. package/src/extensions.mjs +37 -0
  119. package/src/index.mjs +14 -9
  120. package/src/interaction.mjs +2 -0
  121. package/src/manufacturing.mjs +1 -0
  122. package/src/parser.mjs +12 -2
  123. package/src/project.mjs +5 -0
  124. package/src/query.mjs +1 -0
  125. package/src/renderers.mjs +3 -29
  126. package/src/scene3d.mjs +3 -0
  127. package/src/simulation.mjs +1 -0
  128. package/src/styles/renderers.css +24 -0
  129. package/src/testing/ToolkitContractFixtures.mjs +124 -0
  130. package/src/testing/ToolkitLoopbackWorker.mjs +174 -0
  131. package/src/testing/runToolkitContract.mjs +705 -0
  132. package/src/testing.mjs +3 -0
  133. package/src/ui/BomTableRenderer.mjs +304 -0
  134. package/src/ui/CircuitJsonPcbBoardSvgRenderer.mjs +80 -0
  135. package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +3 -5
  136. package/src/ui/CircuitJsonPcbSvgRenderer.mjs +63 -43
  137. package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +3 -5
  138. package/src/ui/CircuitJsonSchematicDebugRenderer.mjs +164 -0
  139. package/src/ui/CircuitJsonSchematicImageSvgRenderer.mjs +210 -0
  140. package/src/ui/CircuitJsonSchematicLineRenderer.mjs +86 -0
  141. package/src/ui/CircuitJsonSchematicSheetSymbolSvgRenderer.mjs +98 -0
  142. package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +117 -17
  143. package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +67 -20
  144. package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +45 -9
  145. package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +151 -148
  146. package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +4 -292
  147. package/src/ui/PcbSvgRenderer.mjs +41 -0
  148. package/src/ui/SafeSvgPaint.mjs +26 -0
  149. package/src/ui/SafeXmlText.mjs +60 -0
  150. package/src/ui/SchematicGeometryBounds.mjs +540 -0
  151. package/src/ui/SchematicSvgRenderer.mjs +110 -0
  152. package/src/ui/SchematicTableGeometry.mjs +319 -0
  153. package/src/ui/SchematicTextAnchor.mjs +55 -0
  154. package/src/ui/SchematicTextBounds.mjs +98 -0
  155. package/src/workers/parser.worker.mjs +59 -0
@@ -12,12 +12,21 @@ export class CircuitJsonManufacturingBuilder {
12
12
  * @returns {{ pickAndPlaceRows: object[], routingDsn: string, routingGuides: object[], fabricationNotes: object[] }}
13
13
  */
14
14
  static build(circuitJson, index = CircuitJsonIndexer.index(circuitJson)) {
15
+ const pickAndPlaceRows =
16
+ CircuitJsonManufacturingBuilder.#pickAndPlaceRows(index)
17
+ const sourceNetNames =
18
+ CircuitJsonManufacturingBuilder.#sourceNetNames(index)
15
19
  return {
16
- pickAndPlaceRows:
17
- CircuitJsonManufacturingBuilder.#pickAndPlaceRows(index),
18
- routingDsn: CircuitJsonManufacturingBuilder.#routingDsn(index),
19
- routingGuides:
20
- CircuitJsonManufacturingBuilder.#routingGuides(index),
20
+ pickAndPlaceRows,
21
+ routingDsn: CircuitJsonManufacturingBuilder.#routingDsn(
22
+ index,
23
+ pickAndPlaceRows,
24
+ sourceNetNames
25
+ ),
26
+ routingGuides: CircuitJsonManufacturingBuilder.#routingGuides(
27
+ index,
28
+ sourceNetNames
29
+ ),
21
30
  fabricationNotes:
22
31
  CircuitJsonManufacturingBuilder.#fabricationNotes(index)
23
32
  }
@@ -85,13 +94,26 @@ export class CircuitJsonManufacturingBuilder {
85
94
  /**
86
95
  * Builds a compact routing exchange text payload.
87
96
  * @param {{ elementsByType?: Map<string, object[]> }} index Element index.
97
+ * @param {object[]} placements Prepared placement rows.
98
+ * @param {Map<string, string>} sourceNetNames Shared source-net lookup.
88
99
  * @returns {string}
89
100
  */
90
- static #routingDsn(index) {
101
+ static #routingDsn(index, placements, sourceNetNames) {
91
102
  const lines = ['(pcb assembly)', ' (unit mm)']
92
- lines.push(...CircuitJsonManufacturingBuilder.#boardLines(index))
93
- lines.push(...CircuitJsonManufacturingBuilder.#placementLines(index))
94
- lines.push(...CircuitJsonManufacturingBuilder.#networkLines(index))
103
+ for (const line of CircuitJsonManufacturingBuilder.#boardLines(index)) {
104
+ lines.push(line)
105
+ }
106
+ for (const line of CircuitJsonManufacturingBuilder.#placementLines(
107
+ placements
108
+ )) {
109
+ lines.push(line)
110
+ }
111
+ for (const line of CircuitJsonManufacturingBuilder.#networkLines(
112
+ index,
113
+ sourceNetNames
114
+ )) {
115
+ lines.push(line)
116
+ }
95
117
  lines.push(')')
96
118
  return lines.join('\n')
97
119
  }
@@ -139,13 +161,13 @@ export class CircuitJsonManufacturingBuilder {
139
161
 
140
162
  /**
141
163
  * Builds component placement lines.
142
- * @param {{ elementsByType?: Map<string, object[]>, sourceComponentById?: Map<string, object> }} index Element index.
164
+ * @param {object[]} rows Prepared placement rows.
143
165
  * @returns {string[]}
144
166
  */
145
- static #placementLines(index) {
167
+ static #placementLines(rows) {
146
168
  return [
147
169
  ' (placement',
148
- ...CircuitJsonManufacturingBuilder.#pickAndPlaceRows(index).map(
170
+ ...rows.map(
149
171
  (row) =>
150
172
  ' (component ' +
151
173
  CircuitJsonManufacturingBuilder.#token(row.designator) +
@@ -165,112 +187,149 @@ export class CircuitJsonManufacturingBuilder {
165
187
  /**
166
188
  * Builds network lines for nets, pads, vias, and wires.
167
189
  * @param {{ elementsByType?: Map<string, object[]> }} index Element index.
190
+ * @param {Map<string, string>} sourceNetNames Shared source-net lookup.
168
191
  * @returns {string[]}
169
192
  */
170
- static #networkLines(index) {
171
- const sourceNetNames =
172
- CircuitJsonManufacturingBuilder.#sourceNetNames(index)
173
- return [
174
- ' (network',
175
- ...CircuitJsonManufacturingBuilder.#netNames(index).flatMap(
176
- (netName) => [
177
- ' (net ' +
178
- CircuitJsonManufacturingBuilder.#token(netName),
179
- ...CircuitJsonManufacturingBuilder.#pinLines(
180
- index,
181
- netName,
182
- sourceNetNames
183
- ),
184
- ...CircuitJsonManufacturingBuilder.#drillLines(
185
- index,
186
- netName,
187
- sourceNetNames
188
- ),
189
- ...CircuitJsonManufacturingBuilder.#wireLines(
190
- index,
191
- netName,
192
- sourceNetNames
193
- ),
194
- ' )'
195
- ]
196
- ),
197
- ' )'
198
- ]
193
+ static #networkLines(index, sourceNetNames) {
194
+ const model = CircuitJsonManufacturingBuilder.#networkModel(
195
+ index,
196
+ sourceNetNames
197
+ )
198
+ const lines = [' (network']
199
+ for (const netName of model.names) {
200
+ lines.push(
201
+ ' (net ' + CircuitJsonManufacturingBuilder.#token(netName)
202
+ )
203
+ for (const family of [model.pins, model.drills, model.wires]) {
204
+ for (const line of family.get(netName) || []) lines.push(line)
205
+ }
206
+ lines.push(' )')
207
+ }
208
+ lines.push(' )')
209
+ return lines
199
210
  }
200
211
 
201
212
  /**
202
- * Builds pad pin lines for one net.
213
+ * Groups routing features by net with one pass per element family.
203
214
  * @param {{ elementsByType?: Map<string, object[]> }} index Element index.
204
- * @param {string} netName Net name.
205
- * @param {Map<string, string>} sourceNetNames Source net lookup.
206
- * @returns {string[]}
207
- */
208
- static #pinLines(index, netName, sourceNetNames) {
209
- return CircuitJsonManufacturingBuilder.#all(index, 'pcb_smtpad')
210
- .filter(
211
- (pad) =>
212
- CircuitJsonManufacturingBuilder.#netName(
213
- pad,
214
- sourceNetNames
215
- ) === netName
215
+ * @param {Map<string, string>} sourceNetNames Shared source-net lookup.
216
+ * @returns {{ names: string[], pins: Map<string, string[]>, drills: Map<string, string[]>, wires: Map<string, string[]> }} Grouped network model.
217
+ */
218
+ static #networkModel(index, sourceNetNames) {
219
+ const names = new Set([...sourceNetNames.values()].filter(Boolean))
220
+ const pins = new Map()
221
+ const drills = new Map()
222
+ const wires = new Map()
223
+ for (const pad of CircuitJsonManufacturingBuilder.#all(
224
+ index,
225
+ 'pcb_smtpad'
226
+ )) {
227
+ const netName = CircuitJsonManufacturingBuilder.#netName(
228
+ pad,
229
+ sourceNetNames
216
230
  )
217
- .map((pad) => {
218
- const point = CircuitJsonUnits.optionalPoint(
219
- pad.center || pad
220
- ) || {
221
- x: 0,
222
- y: 0
223
- }
224
- return (
225
- ' (pin ' +
226
- CircuitJsonManufacturingBuilder.#token(
227
- pad.pcb_smtpad_id || ''
228
- ) +
229
- ' ' +
230
- [
231
- CircuitJsonManufacturingBuilder.#round(point.x),
232
- CircuitJsonManufacturingBuilder.#round(point.y)
233
- ].join(' ') +
234
- ')'
231
+ if (!netName) continue
232
+ CircuitJsonManufacturingBuilder.#appendNetworkLines(
233
+ pins,
234
+ netName,
235
+ [CircuitJsonManufacturingBuilder.#pinLine(pad)],
236
+ names
237
+ )
238
+ }
239
+ for (const [type, kind] of [
240
+ ['pcb_via', 'via'],
241
+ ['pcb_plated_hole', 'plated_hole']
242
+ ]) {
243
+ for (const element of CircuitJsonManufacturingBuilder.#all(
244
+ index,
245
+ type
246
+ )) {
247
+ const netName = CircuitJsonManufacturingBuilder.#netName(
248
+ element,
249
+ sourceNetNames
250
+ )
251
+ if (!netName) continue
252
+ CircuitJsonManufacturingBuilder.#appendNetworkLines(
253
+ drills,
254
+ netName,
255
+ [CircuitJsonManufacturingBuilder.#drillLine(element, kind)],
256
+ names
235
257
  )
236
- })
258
+ }
259
+ }
260
+ for (const trace of CircuitJsonManufacturingBuilder.#all(
261
+ index,
262
+ 'pcb_trace'
263
+ )) {
264
+ const netName = CircuitJsonManufacturingBuilder.#netName(
265
+ trace,
266
+ sourceNetNames
267
+ )
268
+ if (!netName) continue
269
+ CircuitJsonManufacturingBuilder.#appendNetworkLines(
270
+ wires,
271
+ netName,
272
+ CircuitJsonManufacturingBuilder.#traceWireLines(trace),
273
+ names
274
+ )
275
+ }
276
+ for (const type of ['pcb_trace_hint', 'pcb_breakout_point']) {
277
+ for (const element of CircuitJsonManufacturingBuilder.#all(
278
+ index,
279
+ type
280
+ )) {
281
+ const name = CircuitJsonManufacturingBuilder.#netName(
282
+ element,
283
+ sourceNetNames
284
+ )
285
+ if (name) names.add(name)
286
+ }
287
+ }
288
+ return {
289
+ names: [...names].sort((left, right) => left.localeCompare(right)),
290
+ pins,
291
+ drills,
292
+ wires
293
+ }
237
294
  }
238
295
 
239
296
  /**
240
- * Builds drill feature lines for one net.
241
- * @param {{ elementsByType?: Map<string, object[]> }} index Element index.
242
- * @param {string} netName Net name.
243
- * @param {Map<string, string>} sourceNetNames Source net lookup.
244
- * @returns {string[]}
297
+ * Builds one pad pin line.
298
+ * @param {object} pad Pad element.
299
+ * @returns {string} Pin line.
245
300
  */
246
- static #drillLines(index, netName, sourceNetNames) {
247
- return [
248
- ...CircuitJsonManufacturingBuilder.#all(index, 'pcb_via')
249
- .filter(
250
- (via) =>
251
- CircuitJsonManufacturingBuilder.#netName(
252
- via,
253
- sourceNetNames
254
- ) === netName
255
- )
256
- .map((via) =>
257
- CircuitJsonManufacturingBuilder.#drillLine(via, 'via')
258
- ),
259
- ...CircuitJsonManufacturingBuilder.#all(index, 'pcb_plated_hole')
260
- .filter(
261
- (hole) =>
262
- CircuitJsonManufacturingBuilder.#netName(
263
- hole,
264
- sourceNetNames
265
- ) === netName
266
- )
267
- .map((hole) =>
268
- CircuitJsonManufacturingBuilder.#drillLine(
269
- hole,
270
- 'plated_hole'
271
- )
272
- )
273
- ].filter(Boolean)
301
+ static #pinLine(pad) {
302
+ const point = CircuitJsonUnits.optionalPoint(pad.center || pad) || {
303
+ x: 0,
304
+ y: 0
305
+ }
306
+ return (
307
+ ' (pin ' +
308
+ CircuitJsonManufacturingBuilder.#token(pad.pcb_smtpad_id || '') +
309
+ ' ' +
310
+ [
311
+ CircuitJsonManufacturingBuilder.#round(point.x),
312
+ CircuitJsonManufacturingBuilder.#round(point.y)
313
+ ].join(' ') +
314
+ ')'
315
+ )
316
+ }
317
+
318
+ /**
319
+ * Appends non-empty feature lines and records their net name.
320
+ * @param {Map<string, string[]>} target Lines grouped by net.
321
+ * @param {string} netName Net name.
322
+ * @param {string[]} lines Feature lines.
323
+ * @param {Set<string>} names Discovered net names.
324
+ * @returns {void}
325
+ */
326
+ static #appendNetworkLines(target, netName, lines, names) {
327
+ if (!netName) return
328
+ names.add(netName)
329
+ if (!target.has(netName)) target.set(netName, [])
330
+ for (const line of lines) {
331
+ if (line) target.get(netName).push(line)
332
+ }
274
333
  }
275
334
 
276
335
  /**
@@ -307,27 +366,6 @@ export class CircuitJsonManufacturingBuilder {
307
366
  )
308
367
  }
309
368
 
310
- /**
311
- * Builds routed wire lines for one net.
312
- * @param {{ elementsByType?: Map<string, object[]> }} index Element index.
313
- * @param {string} netName Net name.
314
- * @param {Map<string, string>} sourceNetNames Source net lookup.
315
- * @returns {string[]}
316
- */
317
- static #wireLines(index, netName, sourceNetNames) {
318
- return CircuitJsonManufacturingBuilder.#all(index, 'pcb_trace')
319
- .filter(
320
- (trace) =>
321
- CircuitJsonManufacturingBuilder.#netName(
322
- trace,
323
- sourceNetNames
324
- ) === netName
325
- )
326
- .flatMap((trace) =>
327
- CircuitJsonManufacturingBuilder.#traceWireLines(trace)
328
- )
329
- }
330
-
331
369
  /**
332
370
  * Builds routed wire lines for one trace.
333
371
  * @param {object} trace Trace element.
@@ -368,48 +406,13 @@ export class CircuitJsonManufacturingBuilder {
368
406
  return lines
369
407
  }
370
408
 
371
- /**
372
- * Builds sorted net names.
373
- * @param {{ elementsByType?: Map<string, object[]> }} index Element index.
374
- * @returns {string[]}
375
- */
376
- static #netNames(index) {
377
- const names = new Set()
378
- const sourceNetNames =
379
- CircuitJsonManufacturingBuilder.#sourceNetNames(index)
380
- for (const name of sourceNetNames.values()) {
381
- if (name) names.add(name)
382
- }
383
- for (const type of [
384
- 'pcb_smtpad',
385
- 'pcb_trace',
386
- 'pcb_via',
387
- 'pcb_plated_hole',
388
- 'pcb_trace_hint',
389
- 'pcb_breakout_point'
390
- ]) {
391
- for (const element of CircuitJsonManufacturingBuilder.#all(
392
- index,
393
- type
394
- )) {
395
- const name = CircuitJsonManufacturingBuilder.#netName(
396
- element,
397
- sourceNetNames
398
- )
399
- if (name) names.add(name)
400
- }
401
- }
402
- return [...names].sort((left, right) => left.localeCompare(right))
403
- }
404
-
405
409
  /**
406
410
  * Builds route guide metadata from hints and breakout points.
407
411
  * @param {{ elementsByType?: Map<string, object[]> }} index Element index.
412
+ * @param {Map<string, string>} sourceNetNames Shared source-net lookup.
408
413
  * @returns {object[]}
409
414
  */
410
- static #routingGuides(index) {
411
- const sourceNetNames =
412
- CircuitJsonManufacturingBuilder.#sourceNetNames(index)
415
+ static #routingGuides(index, sourceNetNames) {
413
416
  return [
414
417
  ...CircuitJsonManufacturingBuilder.#all(index, 'pcb_trace_hint')
415
418
  .map((hint) =>
@@ -1,8 +1,24 @@
1
- import { CircuitJsonDocument } from './CircuitJsonDocument.mjs'
2
1
  import { CircuitJsonBomBuilder } from './CircuitJsonBomBuilder.mjs'
3
2
  import { CircuitJsonIndexer } from './CircuitJsonIndexer.mjs'
4
3
  import { CircuitJsonManufacturingBuilder } from './CircuitJsonManufacturingBuilder.mjs'
5
4
  import { CircuitJsonSupportMatrixBuilder } from './CircuitJsonSupportMatrixBuilder.mjs'
5
+ import { Parser } from './Parser.mjs'
6
+ import { CircuitJsonLegacyModel } from './context/CircuitJsonLegacyModel.mjs'
7
+ import { CircuitJsonValidationProof } from './context/CircuitJsonValidationProof.mjs'
8
+
9
+ const SUPPORT_VARIANT_TYPES = new Set([
10
+ 'source_component',
11
+ 'pcb_board',
12
+ 'pcb_smtpad',
13
+ 'pcb_hole',
14
+ 'pcb_plated_hole',
15
+ 'pcb_solder_paste',
16
+ 'pcb_cutout',
17
+ 'pcb_copper_pour',
18
+ 'simulation_voltage_source',
19
+ 'simulation_current_source',
20
+ 'simulation_experiment'
21
+ ])
6
22
 
7
23
  /**
8
24
  * Parses standalone CircuitJSON files.
@@ -15,25 +31,36 @@ export class CircuitJsonParser {
15
31
  * @returns {object[]}
16
32
  */
17
33
  static parseText(text, options = {}) {
18
- let parsed
34
+ let document
19
35
  try {
20
- parsed = JSON.parse(String(text || ''))
21
- } catch (error) {
22
- throw new SyntaxError(
23
- 'CircuitJSON file is not valid JSON: ' +
24
- String(error?.message || error || 'Unknown error.')
36
+ document = Parser.parse(
37
+ {
38
+ fileName: options.fileName || '',
39
+ data: String(text || '')
40
+ },
41
+ { extensions: 'full' }
25
42
  )
43
+ } catch (error) {
44
+ throw CircuitJsonParser.#legacyError(error)
26
45
  }
27
46
 
28
- CircuitJsonDocument.assertModel(parsed)
29
- const index = CircuitJsonIndexer.index(parsed)
30
- return CircuitJsonDocument.attachMetadata(parsed, {
47
+ const parsed = CircuitJsonLegacyModel.create(document)
48
+ const index = CircuitJsonIndexer.index(
49
+ document.model,
50
+ CircuitJsonValidationProof.indexOptions(document)
51
+ )
52
+ CircuitJsonLegacyModel.setPreparedIndex(parsed, index)
53
+ return CircuitJsonLegacyModel.attachValidated(parsed, {
31
54
  fileName: options.fileName || '',
32
55
  fileType: 'circuitjson',
33
56
  kind: CircuitJsonParser.#resolveKind(index),
34
- diagnostics: index.diagnostics,
35
- bom: CircuitJsonBomBuilder.build(parsed),
36
- supportMatrix: CircuitJsonSupportMatrixBuilder.build(parsed),
57
+ diagnostics: structuredClone(index.diagnostics),
58
+ bom: CircuitJsonBomBuilder.build(
59
+ index.elementsByType.get('source_component') || []
60
+ ),
61
+ supportMatrix: CircuitJsonSupportMatrixBuilder.build(
62
+ CircuitJsonParser.#supportElements(index)
63
+ ),
37
64
  manufacturing: CircuitJsonManufacturingBuilder.build(parsed, index)
38
65
  })
39
66
  }
@@ -51,6 +78,23 @@ export class CircuitJsonParser {
51
78
  return CircuitJsonParser.parseText(text, options)
52
79
  }
53
80
 
81
+ /**
82
+ * Restores the source-derived exception type and invalid-JSON message.
83
+ * @param {unknown} error Canonical parser failure.
84
+ * @returns {Error} Legacy parser error.
85
+ */
86
+ static #legacyError(error) {
87
+ const cause = error?.cause
88
+ if (error?.code !== 'ERR_CIRCUITJSON_PARSE' || !cause) return error
89
+ if (cause.name === 'SyntaxError') {
90
+ return new SyntaxError(
91
+ 'CircuitJSON file is not valid JSON: ' + cause.message
92
+ )
93
+ }
94
+ if (cause.name === 'TypeError') return new TypeError(cause.message)
95
+ return new Error(cause.message)
96
+ }
97
+
54
98
  /**
55
99
  * Resolves a broad document kind from available elements.
56
100
  * @param {{ elementsByType?: Map<string, object[]> }} index Model index.
@@ -68,4 +112,22 @@ export class CircuitJsonParser {
68
112
 
69
113
  return 'circuitjson'
70
114
  }
115
+
116
+ /**
117
+ * Selects one presence row per type plus all variant-bearing rows.
118
+ * @param {{ elementsByType?: Map<string, object[]> }} index Full element index.
119
+ * @returns {object[]} Minimal support-matrix input with identical semantics.
120
+ */
121
+ static #supportElements(index) {
122
+ const elements = []
123
+ for (const [type, rows] of index.elementsByType || []) {
124
+ if (!rows.length) continue
125
+ if (SUPPORT_VARIANT_TYPES.has(type)) {
126
+ elements.push(...rows)
127
+ } else {
128
+ elements.push(rows[0])
129
+ }
130
+ }
131
+ return elements
132
+ }
71
133
  }
@@ -253,12 +253,18 @@ export class CircuitJsonPcbClearanceDiagnostics {
253
253
  * @returns {object | null}
254
254
  */
255
255
  static #mergeBounds(rows) {
256
- const validRows = rows.filter(Boolean)
257
- if (!validRows.length) return null
258
- const minX = Math.min(...validRows.map((bounds) => bounds.minX))
259
- const minY = Math.min(...validRows.map((bounds) => bounds.minY))
260
- const maxX = Math.max(...validRows.map((bounds) => bounds.maxX))
261
- const maxY = Math.max(...validRows.map((bounds) => bounds.maxY))
256
+ let minX = Infinity
257
+ let minY = Infinity
258
+ let maxX = -Infinity
259
+ let maxY = -Infinity
260
+ for (const bounds of rows) {
261
+ if (!bounds) continue
262
+ minX = Math.min(minX, bounds.minX)
263
+ minY = Math.min(minY, bounds.minY)
264
+ maxX = Math.max(maxX, bounds.maxX)
265
+ maxY = Math.max(maxY, bounds.maxY)
266
+ }
267
+ if (!Number.isFinite(minX)) return null
262
268
  return {
263
269
  minX: this.#round(minX),
264
270
  minY: this.#round(minY),