altium-toolkit 0.1.0 → 0.1.16

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 (54) hide show
  1. package/README.md +24 -6
  2. package/docs/api.md +42 -4
  3. package/docs/model-format.md +95 -5
  4. package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +553 -0
  5. package/docs/testing.md +7 -2
  6. package/package.json +21 -2
  7. package/spec/library-scope.md +7 -1
  8. package/src/core/altium/AltiumParser.mjs +22 -325
  9. package/src/core/altium/NormalizedModelSchema.mjs +28 -0
  10. package/src/core/altium/PcbArcPrimitiveParser.mjs +87 -0
  11. package/src/core/altium/PcbBinaryPrimitiveParser.mjs +43 -370
  12. package/src/core/altium/PcbBoardRegionSemanticsParser.mjs +477 -0
  13. package/src/core/altium/PcbComponentAnnotationNormalizer.mjs +290 -0
  14. package/src/core/altium/PcbComponentBodyPlacementNormalizer.mjs +52 -0
  15. package/src/core/altium/PcbComponentPrimitiveIndexer.mjs +109 -0
  16. package/src/core/altium/PcbEmbeddedFontExtractor.mjs +484 -0
  17. package/src/core/altium/PcbFillPrimitiveParser.mjs +84 -0
  18. package/src/core/altium/PcbFontMetricsParser.mjs +308 -0
  19. package/src/core/altium/PcbGeometryFlipper.mjs +244 -0
  20. package/src/core/altium/PcbLayerIdCodec.mjs +136 -0
  21. package/src/core/altium/PcbLibModelParser.mjs +202 -0
  22. package/src/core/altium/PcbLibStreamExtractor.mjs +968 -0
  23. package/src/core/altium/PcbModelParser.mjs +618 -66
  24. package/src/core/altium/PcbOutlineRecovery.mjs +4 -112
  25. package/src/core/altium/PcbPadPrimitiveParser.mjs +347 -0
  26. package/src/core/altium/PcbPadShapeCodec.mjs +158 -0
  27. package/src/core/altium/PcbPadStackParser.mjs +903 -0
  28. package/src/core/altium/PcbPrimitiveOwnershipIndexParser.mjs +60 -0
  29. package/src/core/altium/PcbPrimitiveParameterParser.mjs +212 -0
  30. package/src/core/altium/PcbPrimitiveRecordSlicer.mjs +243 -0
  31. package/src/core/altium/PcbRawRecordRegistry.mjs +831 -0
  32. package/src/core/altium/PcbRegionPrimitiveParser.mjs +317 -0
  33. package/src/core/altium/PcbRuleParser.mjs +587 -0
  34. package/src/core/altium/PcbStreamExtractor.mjs +127 -4
  35. package/src/core/altium/PcbTextPrimitiveParser.mjs +537 -0
  36. package/src/core/altium/PcbTrackPrimitiveParser.mjs +87 -0
  37. package/src/core/altium/PcbViaPrimitiveParser.mjs +88 -0
  38. package/src/core/altium/PcbViaStackParser.mjs +548 -0
  39. package/src/core/altium/PcbWideStringTableParser.mjs +108 -0
  40. package/src/core/altium/PrjPcbModelParser.mjs +797 -0
  41. package/src/core/altium/SchematicComponentTextResolver.mjs +355 -0
  42. package/src/parser.mjs +13 -0
  43. package/src/renderers.mjs +5 -0
  44. package/src/styles/altium-renderers.css +11 -6
  45. package/src/ui/PcbCopperPrimitiveSplitter.mjs +113 -0
  46. package/src/ui/PcbEdgeFacingGlyphNormalizer.mjs +6 -5
  47. package/src/ui/PcbEmbeddedFontFaceRenderer.mjs +126 -0
  48. package/src/ui/PcbFootprintPrimitiveSelector.mjs +27 -6
  49. package/src/ui/PcbRegionPrimitiveRenderer.mjs +243 -0
  50. package/src/ui/PcbSideResolvedRenderModel.mjs +336 -0
  51. package/src/ui/PcbSvgRenderer.mjs +101 -109
  52. package/src/ui/PcbTextPrimitiveRenderer.mjs +252 -0
  53. package/src/ui/SchematicSheetChromeRenderer.mjs +2 -93
  54. package/src/ui/SchematicSheetZoneRenderer.mjs +104 -0
@@ -4,7 +4,11 @@
4
4
 
5
5
  import { AsciiRecordParser } from './AsciiRecordParser.mjs'
6
6
  import { PcbBinaryPrimitiveParser } from './PcbBinaryPrimitiveParser.mjs'
7
+ import { PcbEmbeddedFontExtractor } from './PcbEmbeddedFontExtractor.mjs'
7
8
  import { PcbEmbeddedModelExtractor } from './PcbEmbeddedModelExtractor.mjs'
9
+ import { PcbPrimitiveParameterParser } from './PcbPrimitiveParameterParser.mjs'
10
+ import { PcbRawRecordRegistry } from './PcbRawRecordRegistry.mjs'
11
+ import { PcbWideStringTableParser } from './PcbWideStringTableParser.mjs'
8
12
  import { OleCompoundDocument } from '../ole/OleCompoundDocument.mjs'
9
13
  import { OleConstants } from '../ole/OleConstants.mjs'
10
14
 
@@ -41,7 +45,7 @@ export class PcbStreamExtractor {
41
45
  /**
42
46
  * Extracts PCB content directly from one OLE-backed PcbDoc buffer.
43
47
  * @param {ArrayBuffer} arrayBuffer
44
- * @returns {{ records: Array<{ raw: string, fields: Record<string, string | string[]>, sourceStream: string }>, streamNames: string[], binaryPrimitives: { fills: { x1: number, y1: number, x2: number, y2: number, layerCode: number }[], tracks: { x1: number, y1: number, x2: number, y2: number, width: number, layerCode: number }[], arcs: { x: number, y: number, radius: number, startAngle: number, endAngle: number, width: number, layerCode: number, layerId: number }[], vias: { x: number, y: number, diameter: number, holeDiameter: number }[], pads: { x: number, y: number, sizeTopX: number, sizeTopY: number, sizeMidX: number, sizeMidY: number, sizeBottomX: number, sizeBottomY: number, holeDiameter: number, shapeTop: number, shapeMid: number, shapeBottom: number, rotation: number, isPlated: boolean }[] }, diagnostics: { printableRecordCount: number, printableStreamCount: number, binaryPrimitiveCount: number } } | null}
48
+ * @returns {{ records: Array<{ raw: string, fields: Record<string, string | string[]>, sourceStream: string }>, streamNames: string[], binaryPrimitives: Record<string, object[]>, primitiveParameters: object, wideStrings: object, diagnostics: { printableRecordCount: number, printableStreamCount: number, binaryPrimitiveCount: number, primitiveParameterGroupCount: number, wideStringCount: number } } | null}
45
49
  */
46
50
  static extractFromArrayBuffer(arrayBuffer) {
47
51
  if (!PcbStreamExtractor.isCompoundDocument(arrayBuffer)) {
@@ -63,7 +67,7 @@ export class PcbStreamExtractor {
63
67
  * Extracts stream-scoped printable records and known binary primitives from
64
68
  * a stream map.
65
69
  * @param {Map<string, Uint8Array>} streams
66
- * @returns {{ records: Array<{ raw: string, fields: Record<string, string | string[]>, sourceStream: string }>, streamNames: string[], binaryPrimitives: { fills: { x1: number, y1: number, x2: number, y2: number, layerCode: number }[], tracks: { x1: number, y1: number, x2: number, y2: number, width: number, layerCode: number }[], arcs: { x: number, y: number, radius: number, startAngle: number, endAngle: number, width: number, layerCode: number, layerId: number }[], vias: { x: number, y: number, diameter: number, holeDiameter: number }[], pads: { x: number, y: number, sizeTopX: number, sizeTopY: number, sizeMidX: number, sizeMidY: number, sizeBottomX: number, sizeBottomY: number, holeDiameter: number, shapeTop: number, shapeMid: number, shapeBottom: number, rotation: number, isPlated: boolean }[] }, diagnostics: { printableRecordCount: number, printableStreamCount: number, binaryPrimitiveCount: number } }}
70
+ * @returns {{ records: Array<{ raw: string, fields: Record<string, string | string[]>, sourceStream: string }>, streamNames: string[], binaryPrimitives: Record<string, object[]>, primitiveParameters: object, wideStrings: object, diagnostics: { printableRecordCount: number, printableStreamCount: number, binaryPrimitiveCount: number, primitiveParameterGroupCount: number, wideStringCount: number } }}
67
71
  */
68
72
  static extractFromStreams(streams) {
69
73
  const records = []
@@ -74,7 +78,11 @@ export class PcbStreamExtractor {
74
78
  tracks: [],
75
79
  arcs: [],
76
80
  vias: [],
77
- pads: []
81
+ pads: [],
82
+ texts: [],
83
+ regions: [],
84
+ shapeBasedRegions: [],
85
+ boardRegions: []
78
86
  }
79
87
 
80
88
  for (const [name, bytes] of streams.entries()) {
@@ -82,6 +90,10 @@ export class PcbStreamExtractor {
82
90
  continue
83
91
  }
84
92
 
93
+ if (PcbStreamExtractor.#isBinarySidecarDataStream(name)) {
94
+ continue
95
+ }
96
+
85
97
  const recordBuffer = PcbStreamExtractor.#toArrayBuffer(bytes)
86
98
  const streamRecords = AsciiRecordParser.parse(recordBuffer).map(
87
99
  (record) => ({
@@ -109,6 +121,35 @@ export class PcbStreamExtractor {
109
121
  const fillDataBytes = streams.get('Fills6/Data')
110
122
  const padHeaderBytes = streams.get('Pads6/Header')
111
123
  const padDataBytes = streams.get('Pads6/Data')
124
+ const textHeaderBytes =
125
+ streams.get('Texts6/Header') || streams.get('Texts/Header')
126
+ const textDataBytes =
127
+ streams.get('Texts6/Data') || streams.get('Texts/Data')
128
+ const textStreamName = streams.has('Texts6/Data')
129
+ ? 'Texts6/Data'
130
+ : 'Texts/Data'
131
+ const regionHeaderBytes = streams.get('Regions6/Header')
132
+ const regionDataBytes = streams.get('Regions6/Data')
133
+ const shapeBasedRegionHeaderBytes = streams.get(
134
+ 'ShapeBasedRegions6/Header'
135
+ )
136
+ const shapeBasedRegionDataBytes = streams.get('ShapeBasedRegions6/Data')
137
+ const boardRegionHeaderBytes = streams.get('BoardRegions/Header')
138
+ const boardRegionDataBytes = streams.get('BoardRegions/Data')
139
+ const primitiveParameters = PcbPrimitiveParameterParser.parse(
140
+ streams.get('PrimitiveParameters/Data')
141
+ )
142
+ const wideStrings = PcbWideStringTableParser.parse(
143
+ streams.get('WideStrings6/Data')
144
+ )
145
+
146
+ if (primitiveParameters.groups.length) {
147
+ usedStreamNames.add('PrimitiveParameters/Data')
148
+ }
149
+
150
+ if (wideStrings.entries.length) {
151
+ usedStreamNames.add('WideStrings6/Data')
152
+ }
112
153
 
113
154
  if (arcHeaderBytes && arcDataBytes) {
114
155
  binaryPrimitives.arcs = PcbBinaryPrimitiveParser.parseArcStream(
@@ -160,8 +201,61 @@ export class PcbStreamExtractor {
160
201
  }
161
202
  }
162
203
 
204
+ if (textHeaderBytes && textDataBytes) {
205
+ binaryPrimitives.texts = PcbBinaryPrimitiveParser.parseTextStream(
206
+ textHeaderBytes,
207
+ textDataBytes,
208
+ { wideStrings }
209
+ )
210
+ if (binaryPrimitives.texts.length) {
211
+ usedStreamNames.add(textStreamName)
212
+ }
213
+ }
214
+
215
+ if (regionHeaderBytes && regionDataBytes) {
216
+ binaryPrimitives.regions =
217
+ PcbBinaryPrimitiveParser.parseRegionStream(
218
+ regionHeaderBytes,
219
+ regionDataBytes
220
+ )
221
+ if (binaryPrimitives.regions.length) {
222
+ usedStreamNames.add('Regions6/Data')
223
+ }
224
+ }
225
+
226
+ if (shapeBasedRegionHeaderBytes && shapeBasedRegionDataBytes) {
227
+ binaryPrimitives.shapeBasedRegions =
228
+ PcbBinaryPrimitiveParser.parseRegionStream(
229
+ shapeBasedRegionHeaderBytes,
230
+ shapeBasedRegionDataBytes,
231
+ { shapeBased: true }
232
+ )
233
+ if (binaryPrimitives.shapeBasedRegions.length) {
234
+ usedStreamNames.add('ShapeBasedRegions6/Data')
235
+ }
236
+ }
237
+
238
+ if (boardRegionHeaderBytes && boardRegionDataBytes) {
239
+ binaryPrimitives.boardRegions =
240
+ PcbBinaryPrimitiveParser.parseRegionStream(
241
+ boardRegionHeaderBytes,
242
+ boardRegionDataBytes
243
+ )
244
+ if (binaryPrimitives.boardRegions.length) {
245
+ usedStreamNames.add('BoardRegions/Data')
246
+ }
247
+ }
248
+
163
249
  const embeddedModels =
164
250
  PcbEmbeddedModelExtractor.extractFromStreams(streams)
251
+ const embeddedFonts =
252
+ PcbEmbeddedFontExtractor.extractFromStreams(streams)
253
+ const rawRecords = PcbRawRecordRegistry.collectPcbDocRecords(
254
+ streams,
255
+ binaryPrimitives
256
+ )
257
+
258
+ rawRecords.forEach((record) => usedStreamNames.add(record.sourceStream))
165
259
 
166
260
  if (
167
261
  embeddedModels.models.length ||
@@ -176,22 +270,40 @@ export class PcbStreamExtractor {
176
270
  )
177
271
  }
178
272
 
273
+ if (embeddedFonts.fonts.length) {
274
+ embeddedFonts.fonts.forEach((font) =>
275
+ usedStreamNames.add(font.sourceStream)
276
+ )
277
+ }
278
+
179
279
  return {
180
280
  records,
181
281
  streamNames: [...usedStreamNames].sort((left, right) =>
182
282
  left.localeCompare(right)
183
283
  ),
184
284
  binaryPrimitives,
285
+ primitiveParameters,
286
+ wideStrings,
185
287
  embeddedModels,
288
+ embeddedFonts,
289
+ rawRecords,
186
290
  diagnostics: {
187
291
  printableRecordCount: records.length,
188
292
  printableStreamCount: printableStreamNames.size,
293
+ embeddedFontCount: embeddedFonts.fonts.length,
294
+ rawRecordCount: rawRecords.length,
295
+ primitiveParameterGroupCount: primitiveParameters.groups.length,
296
+ wideStringCount: wideStrings.entries.length,
189
297
  binaryPrimitiveCount:
190
298
  binaryPrimitives.arcs.length +
191
299
  binaryPrimitives.tracks.length +
192
300
  binaryPrimitives.vias.length +
193
301
  binaryPrimitives.fills.length +
194
- binaryPrimitives.pads.length
302
+ binaryPrimitives.pads.length +
303
+ binaryPrimitives.texts.length +
304
+ binaryPrimitives.regions.length +
305
+ binaryPrimitives.shapeBasedRegions.length +
306
+ binaryPrimitives.boardRegions.length
195
307
  }
196
308
  }
197
309
  }
@@ -207,4 +319,15 @@ export class PcbStreamExtractor {
207
319
  bytes.byteOffset + bytes.byteLength
208
320
  )
209
321
  }
322
+
323
+ /**
324
+ * Returns true for binary sidecar streams with printable-looking payloads.
325
+ * @param {string} name
326
+ * @returns {boolean}
327
+ */
328
+ static #isBinarySidecarDataStream(name) {
329
+ return (
330
+ name === 'PrimitiveParameters/Data' || name === 'WideStrings6/Data'
331
+ )
332
+ }
210
333
  }