altium-toolkit 1.1.22 → 1.1.24

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 (99) hide show
  1. package/README.md +34 -5
  2. package/docs/api.md +134 -23
  3. package/docs/model-format.md +174 -20
  4. package/docs/schemas/altium_toolkit/embedded_assets_a1.schema.json +56 -0
  5. package/docs/schemas/altium_toolkit/fixture_coverage_matrix_a1.schema.json +89 -0
  6. package/docs/schemas/altium_toolkit/geometry_bounds_a1.schema.json +86 -0
  7. package/docs/schemas/altium_toolkit/library_catalog_a1.schema.json +65 -0
  8. package/docs/schemas/altium_toolkit/library_diff_a1.schema.json +54 -0
  9. package/docs/schemas/altium_toolkit/library_inspection_a1.schema.json +94 -0
  10. package/docs/schemas/altium_toolkit/library_qa_a1.schema.json +4 -0
  11. package/docs/schemas/altium_toolkit/native_stream_inventory_a1.schema.json +66 -0
  12. package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +511 -1
  13. package/docs/schemas/altium_toolkit/parameter_record_inventory_a1.schema.json +84 -0
  14. package/docs/schemas/altium_toolkit/parser_diagnostics_a1.schema.json +63 -0
  15. package/docs/schemas/altium_toolkit/parser_value_verification_a1.schema.json +74 -0
  16. package/docs/schemas/altium_toolkit/pcb_class_report_a1.schema.json +79 -0
  17. package/docs/schemas/altium_toolkit/pcb_inspection_a1.schema.json +65 -0
  18. package/docs/schemas/altium_toolkit/pcb_net_membership_a1.schema.json +98 -0
  19. package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +3 -0
  20. package/docs/schemas/altium_toolkit/project_hierarchy_a1.schema.json +79 -0
  21. package/docs/schemas/altium_toolkit/unsupported_features_a1.schema.json +212 -0
  22. package/docs/testing.md +2 -0
  23. package/examples/README.md +21 -0
  24. package/examples/cli-utils.mjs +148 -0
  25. package/examples/corpus-smoke.mjs +523 -0
  26. package/examples/extract-bom.mjs +47 -0
  27. package/examples/generate-pnp.mjs +59 -0
  28. package/examples/inspect-board.mjs +70 -0
  29. package/examples/inspect-schematic.mjs +406 -0
  30. package/examples/library-catalog.mjs +115 -0
  31. package/examples/net-report.mjs +61 -0
  32. package/examples/validate-library.mjs +59 -0
  33. package/package.json +1 -1
  34. package/src/core/BinaryReader.mjs +213 -2
  35. package/src/core/altium/AltiumParser.mjs +352 -14
  36. package/src/core/altium/AltiumUnits.mjs +205 -0
  37. package/src/core/altium/AsciiRecordParser.mjs +9 -0
  38. package/src/core/altium/EmbeddedAssetReportBuilder.mjs +383 -0
  39. package/src/core/altium/FixtureCoverageMatrixBuilder.mjs +304 -0
  40. package/src/core/altium/GeometryBoundsReportBuilder.mjs +935 -0
  41. package/src/core/altium/LibraryCatalogArtifactBuilder.mjs +296 -0
  42. package/src/core/altium/LibraryDiffReportBuilder.mjs +260 -0
  43. package/src/core/altium/LibraryInspectionReportBuilder.mjs +156 -0
  44. package/src/core/altium/LibraryQaReportBuilder.mjs +374 -1
  45. package/src/core/altium/NativeStreamInventoryBuilder.mjs +177 -0
  46. package/src/core/altium/NormalizedModelSchema.mjs +3 -31
  47. package/src/core/altium/ParameterCollection.mjs +431 -0
  48. package/src/core/altium/ParameterRecordInventoryBuilder.mjs +274 -0
  49. package/src/core/altium/ParserCompatibilityFuzzer.mjs +106 -2
  50. package/src/core/altium/ParserDiagnosticNormalizer.mjs +213 -0
  51. package/src/core/altium/ParserErrors.mjs +90 -0
  52. package/src/core/altium/ParserFieldCoverageReportBuilder.mjs +656 -0
  53. package/src/core/altium/ParserUtils.mjs +24 -0
  54. package/src/core/altium/ParserValueVerificationReportBuilder.mjs +323 -0
  55. package/src/core/altium/PcbClassReportBuilder.mjs +366 -0
  56. package/src/core/altium/PcbInspectionReportBuilder.mjs +313 -0
  57. package/src/core/altium/PcbLayerGroups.mjs +308 -0
  58. package/src/core/altium/PcbLayerStackCustomDataParser.mjs +183 -0
  59. package/src/core/altium/PcbLayerStackInterchangeParser.mjs +473 -4
  60. package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +83 -15
  61. package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +74 -4
  62. package/src/core/altium/PcbLibModelParser.mjs +20 -4
  63. package/src/core/altium/PcbLibStreamExtractor.mjs +49 -6
  64. package/src/core/altium/PcbModelParser.mjs +223 -4
  65. package/src/core/altium/PcbNetMembershipReportBuilder.mjs +270 -0
  66. package/src/core/altium/PcbStreamExtractor.mjs +130 -6
  67. package/src/core/altium/PcbTrackPrimitiveParser.mjs +66 -2
  68. package/src/core/altium/ProjectDesignBundleBuilder.mjs +15 -0
  69. package/src/core/altium/ProjectHierarchyReportBuilder.mjs +660 -0
  70. package/src/core/altium/ProjectNetlistExporter.mjs +2 -0
  71. package/src/core/altium/RawDataPreservationReportBuilder.mjs +348 -0
  72. package/src/core/altium/SchLibModelParser.mjs +840 -0
  73. package/src/core/altium/SchLibStreamExtractor.mjs +586 -0
  74. package/src/core/altium/SchematicBusEntryParser.mjs +3 -2
  75. package/src/core/altium/SchematicCodeSymbolParser.mjs +663 -0
  76. package/src/core/altium/SchematicConnectivityQaBuilder.mjs +177 -2
  77. package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +10 -1
  78. package/src/core/altium/SchematicFieldCoverageReportBuilder.mjs +549 -0
  79. package/src/core/altium/SchematicHarnessParser.mjs +9 -3
  80. package/src/core/altium/SchematicHyperlinkParser.mjs +122 -0
  81. package/src/core/altium/SchematicNetlistBuilder.mjs +271 -8
  82. package/src/core/altium/SchematicOwnershipGraphParser.mjs +102 -3
  83. package/src/core/altium/SchematicPinParser.mjs +12 -45
  84. package/src/core/altium/SchematicPrimitiveParser.mjs +9 -14
  85. package/src/core/altium/SchematicQaReportBuilder.mjs +2 -0
  86. package/src/core/altium/SchematicRecordStreamParser.mjs +183 -0
  87. package/src/core/altium/SchematicRecordTypeRegistry.mjs +6 -1
  88. package/src/core/altium/SchematicSheetParser.mjs +8 -2
  89. package/src/core/altium/SchematicStreamExtractor.mjs +64 -25
  90. package/src/core/altium/SchematicTextOrientationResolver.mjs +76 -0
  91. package/src/core/altium/SchematicTextParser.mjs +28 -12
  92. package/src/core/altium/SchematicTextRunParser.mjs +81 -0
  93. package/src/core/altium/SchematicThumbnailParser.mjs +425 -0
  94. package/src/core/altium/UnsupportedFeatureReportBuilder.mjs +380 -0
  95. package/src/parser.mjs +35 -1
  96. package/src/renderers.mjs +1 -0
  97. package/src/ui/SchematicShapeRenderer.mjs +49 -6
  98. package/src/ui/SchematicSvgRenderer.mjs +37 -8
  99. package/src/ui/SchematicTypography.mjs +4 -3
@@ -10,11 +10,12 @@ export class SchematicConnectivityQaBuilder {
10
10
 
11
11
  /**
12
12
  * Builds connectivity QA from the normalized single-sheet net model.
13
- * @param {{ nets?: object[], texts?: object[], pins?: object[], ports?: object[], junctions?: object[] }} schematic Normalized schematic fragments.
13
+ * @param {{ nets?: object[], texts?: object[], pins?: object[], ports?: object[], junctions?: object[], lines?: object[] }} schematic Normalized schematic fragments.
14
14
  * @returns {object}
15
15
  */
16
16
  static build(schematic) {
17
17
  const nets = Array.isArray(schematic?.nets) ? schematic.nets : []
18
+ const lines = Array.isArray(schematic?.lines) ? schematic.lines : []
18
19
  const labels = (schematic?.texts || []).filter(
19
20
  (text) => text.recordType === '25'
20
21
  )
@@ -37,6 +38,10 @@ export class SchematicConnectivityQaBuilder {
37
38
  ...SchematicConnectivityQaBuilder.#ambiguousJunctionFindings(
38
39
  junctions,
39
40
  nets
41
+ ),
42
+ ...SchematicConnectivityQaBuilder.#unjunctionedTeeContactFindings(
43
+ lines,
44
+ junctions
40
45
  )
41
46
  ]
42
47
 
@@ -180,7 +185,12 @@ export class SchematicConnectivityQaBuilder {
180
185
  * @returns {object}
181
186
  */
182
187
  static #summary(nets, findings) {
183
- return {
188
+ const unjunctionedTeeContactCount =
189
+ SchematicConnectivityQaBuilder.#countCode(
190
+ findings,
191
+ 'schematic.connectivity.unjunctioned-tee-contact'
192
+ )
193
+ const summary = {
184
194
  netCount: nets.length,
185
195
  findingCount: findings.length,
186
196
  danglingLabelCount: SchematicConnectivityQaBuilder.#countCode(
@@ -204,6 +214,98 @@ export class SchematicConnectivityQaBuilder {
204
214
  'schematic.connectivity.ambiguous-junction'
205
215
  )
206
216
  }
217
+
218
+ if (unjunctionedTeeContactCount) {
219
+ summary.unjunctionedTeeContactCount = unjunctionedTeeContactCount
220
+ }
221
+
222
+ return summary
223
+ }
224
+
225
+ /**
226
+ * Reports segment endpoints that touch another segment interior without an
227
+ * authored junction at the contact point.
228
+ * @param {object[]} lines Normalized line segments.
229
+ * @param {object[]} junctions Authored junctions.
230
+ * @returns {object[]}
231
+ */
232
+ static #unjunctionedTeeContactFindings(lines, junctions) {
233
+ const findings = []
234
+ const seen = new Set()
235
+
236
+ for (
237
+ let segmentIndex = 0;
238
+ segmentIndex < lines.length;
239
+ segmentIndex += 1
240
+ ) {
241
+ const segment = lines[segmentIndex]
242
+ if (!SchematicConnectivityQaBuilder.#isUsableSegment(segment)) {
243
+ continue
244
+ }
245
+
246
+ for (const point of SchematicConnectivityQaBuilder.#segmentEndpoints(
247
+ segment
248
+ )) {
249
+ if (
250
+ junctions.some((junction) =>
251
+ SchematicConnectivityQaBuilder.#samePoint(
252
+ point,
253
+ junction
254
+ )
255
+ )
256
+ ) {
257
+ continue
258
+ }
259
+
260
+ for (
261
+ let touchedSegmentIndex = 0;
262
+ touchedSegmentIndex < lines.length;
263
+ touchedSegmentIndex += 1
264
+ ) {
265
+ if (touchedSegmentIndex === segmentIndex) {
266
+ continue
267
+ }
268
+
269
+ const touchedSegment = lines[touchedSegmentIndex]
270
+ if (
271
+ !SchematicConnectivityQaBuilder.#isUsableSegment(
272
+ touchedSegment
273
+ ) ||
274
+ SchematicConnectivityQaBuilder.#isSegmentEndpoint(
275
+ touchedSegment,
276
+ point
277
+ ) ||
278
+ !SchematicConnectivityQaBuilder.#segmentContainsPoint(
279
+ touchedSegment,
280
+ point
281
+ )
282
+ ) {
283
+ continue
284
+ }
285
+
286
+ const key =
287
+ SchematicConnectivityQaBuilder.#pointKey(point) +
288
+ ':' +
289
+ segmentIndex +
290
+ ':' +
291
+ touchedSegmentIndex
292
+ if (seen.has(key)) {
293
+ continue
294
+ }
295
+ seen.add(key)
296
+ findings.push({
297
+ code: 'schematic.connectivity.unjunctioned-tee-contact',
298
+ severity: 'warning',
299
+ x: point.x,
300
+ y: point.y,
301
+ segmentIndex,
302
+ touchedSegmentIndex
303
+ })
304
+ }
305
+ }
306
+ }
307
+
308
+ return findings
207
309
  }
208
310
 
209
311
  /**
@@ -256,6 +358,79 @@ export class SchematicConnectivityQaBuilder {
256
358
  )
257
359
  }
258
360
 
361
+ /**
362
+ * Returns true for a non-degenerate line segment.
363
+ * @param {object} segment Line segment.
364
+ * @returns {boolean}
365
+ */
366
+ static #isUsableSegment(segment) {
367
+ return !SchematicConnectivityQaBuilder.#samePoint(
368
+ { x: segment?.x1, y: segment?.y1 },
369
+ { x: segment?.x2, y: segment?.y2 }
370
+ )
371
+ }
372
+
373
+ /**
374
+ * Lists the two endpoints of a segment.
375
+ * @param {object} segment Line segment.
376
+ * @returns {{ x: number, y: number }[]}
377
+ */
378
+ static #segmentEndpoints(segment) {
379
+ return [
380
+ { x: Number(segment.x1), y: Number(segment.y1) },
381
+ { x: Number(segment.x2), y: Number(segment.y2) }
382
+ ]
383
+ }
384
+
385
+ /**
386
+ * Returns true when a point matches either segment endpoint.
387
+ * @param {object} segment Line segment.
388
+ * @param {{ x: number, y: number }} point Test point.
389
+ * @returns {boolean}
390
+ */
391
+ static #isSegmentEndpoint(segment, point) {
392
+ return SchematicConnectivityQaBuilder.#segmentEndpoints(segment).some(
393
+ (endpoint) =>
394
+ SchematicConnectivityQaBuilder.#samePoint(endpoint, point)
395
+ )
396
+ }
397
+
398
+ /**
399
+ * Returns true when a point lies on a segment.
400
+ * @param {object} segment Line segment.
401
+ * @param {{ x: number, y: number }} point Test point.
402
+ * @returns {boolean}
403
+ */
404
+ static #segmentContainsPoint(segment, point) {
405
+ const x1 = Number(segment.x1)
406
+ const y1 = Number(segment.y1)
407
+ const x2 = Number(segment.x2)
408
+ const y2 = Number(segment.y2)
409
+ const x = Number(point.x)
410
+ const y = Number(point.y)
411
+ const cross = (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1)
412
+
413
+ if (Math.abs(cross) > 0.01) {
414
+ return false
415
+ }
416
+
417
+ return (
418
+ x >= Math.min(x1, x2) - 0.01 &&
419
+ x <= Math.max(x1, x2) + 0.01 &&
420
+ y >= Math.min(y1, y2) - 0.01 &&
421
+ y <= Math.max(y1, y2) + 0.01
422
+ )
423
+ }
424
+
425
+ /**
426
+ * Builds a stable key fragment for one point.
427
+ * @param {{ x: number, y: number }} point Point.
428
+ * @returns {string}
429
+ */
430
+ static #pointKey(point) {
431
+ return Number(point.x).toFixed(2) + ',' + Number(point.y).toFixed(2)
432
+ }
433
+
259
434
  /**
260
435
  * Compares points with a small parser-tolerance.
261
436
  * @param {object} left First point.
@@ -62,6 +62,8 @@ export class SchematicDisplayModeCatalogParser {
62
62
  parseNumericField(componentRecord.fields, 'DisplayModeCount') || 1
63
63
  const currentPartId =
64
64
  parseNumericField(componentRecord.fields, 'CurrentPartId') || 1
65
+ const activeDisplayMode =
66
+ parseNumericField(componentRecord.fields, 'DisplayMode') || 1
65
67
  const partIds = SchematicDisplayModeCatalogParser.#collectPartIds(
66
68
  children,
67
69
  declaredPartCount
@@ -88,6 +90,7 @@ export class SchematicDisplayModeCatalogParser {
88
90
  SchematicDisplayModeCatalogParser.#partCatalog(
89
91
  partId,
90
92
  currentPartId,
93
+ activeDisplayMode,
91
94
  declaredDisplayModeCount,
92
95
  children
93
96
  )
@@ -156,6 +159,7 @@ export class SchematicDisplayModeCatalogParser {
156
159
  * Builds one part catalog row.
157
160
  * @param {number} partId Native part id.
158
161
  * @param {number} currentPartId Active part id.
162
+ * @param {number} activeDisplayMode Active display-mode id.
159
163
  * @param {number} declaredDisplayModeCount Declared display-mode count.
160
164
  * @param {object[]} children Owner child records.
161
165
  * @returns {object}
@@ -163,6 +167,7 @@ export class SchematicDisplayModeCatalogParser {
163
167
  static #partCatalog(
164
168
  partId,
165
169
  currentPartId,
170
+ activeDisplayMode,
166
171
  declaredDisplayModeCount,
167
172
  children
168
173
  ) {
@@ -185,6 +190,7 @@ export class SchematicDisplayModeCatalogParser {
185
190
  displayMode,
186
191
  partId,
187
192
  currentPartId,
193
+ activeDisplayMode,
188
194
  partChildren
189
195
  )
190
196
  )
@@ -219,6 +225,7 @@ export class SchematicDisplayModeCatalogParser {
219
225
  * @param {number} displayMode Native display-mode id.
220
226
  * @param {number} partId Native part id.
221
227
  * @param {number} currentPartId Active part id.
228
+ * @param {number} activeDisplayMode Active display-mode id.
222
229
  * @param {object[]} partChildren Child records for one part.
223
230
  * @returns {object}
224
231
  */
@@ -226,6 +233,7 @@ export class SchematicDisplayModeCatalogParser {
226
233
  displayMode,
227
234
  partId,
228
235
  currentPartId,
236
+ activeDisplayMode,
229
237
  partChildren
230
238
  ) {
231
239
  const displayChildren = partChildren.filter(
@@ -236,7 +244,8 @@ export class SchematicDisplayModeCatalogParser {
236
244
 
237
245
  return {
238
246
  displayMode,
239
- isActive: partId === currentPartId && displayMode === 1,
247
+ isActive:
248
+ partId === currentPartId && displayMode === activeDisplayMode,
240
249
  primitiveCount: displayChildren.length,
241
250
  pinCount:
242
251
  SchematicDisplayModeCatalogParser.#pinCount(displayChildren)