altium-toolkit 1.0.8 → 1.0.10
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.
- package/README.md +18 -6
- package/docs/api.md +78 -16
- package/docs/model-format.md +229 -8
- package/docs/schemas/altium_toolkit/ci_artifact_bundle_a1.schema.json +76 -0
- package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +35 -0
- package/docs/schemas/altium_toolkit/netlist_a1.schema.json +53 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +1826 -110
- package/docs/schemas/altium_toolkit/parser_compatibility_fuzz_a1.schema.json +25 -0
- package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +86 -0
- package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +63 -0
- package/docs/schemas/altium_toolkit/project_document_graph_a1.schema.json +33 -0
- package/docs/schemas/altium_toolkit/schematic_svg_semantics_a1.schema.json +50 -0
- package/docs/schemas/altium_toolkit/svg_model_cross_link_a1.schema.json +39 -0
- package/docs/testing.md +9 -3
- package/package.json +1 -1
- package/spec/library-scope.md +7 -1
- package/src/core/altium/AltiumLayoutParser.mjs +104 -8
- package/src/core/altium/AltiumParser.mjs +196 -45
- package/src/core/altium/CiArtifactBundleBuilder.mjs +202 -0
- package/src/core/altium/DraftsmanDigestParser.mjs +689 -0
- package/src/core/altium/EmbeddedFileInventoryBuilder.mjs +255 -0
- package/src/core/altium/IntLibModelParser.mjs +240 -0
- package/src/core/altium/IntLibStreamExtractor.mjs +366 -0
- package/src/core/altium/LibraryRenderManifestBuilder.mjs +417 -0
- package/src/core/altium/LibrarySearchIndex.mjs +215 -0
- package/src/core/altium/NormalizedModelSchema.mjs +36 -0
- package/src/core/altium/ParserCompatibilityFuzzer.mjs +192 -0
- package/src/core/altium/PcbCustomPadShapeParser.mjs +244 -0
- package/src/core/altium/PcbDefaultsParser.mjs +171 -0
- package/src/core/altium/PcbDimensionParser.mjs +229 -0
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +232 -6
- package/src/core/altium/PcbExtendedPrimitiveInformationParser.mjs +256 -0
- package/src/core/altium/PcbLibModelParser.mjs +235 -14
- package/src/core/altium/PcbLibStreamExtractor.mjs +62 -4
- package/src/core/altium/PcbMaskPasteResolver.mjs +354 -0
- package/src/core/altium/PcbMechanicalLayerPairParser.mjs +204 -0
- package/src/core/altium/PcbModelParser.mjs +495 -32
- package/src/core/altium/PcbOwnershipGraphBuilder.mjs +245 -0
- package/src/core/altium/PcbPadPrimitiveParser.mjs +78 -65
- package/src/core/altium/PcbPadStackParser.mjs +229 -2
- package/src/core/altium/PcbPickPlacePositionResolver.mjs +224 -0
- package/src/core/altium/PcbPrimitiveParameterParser.mjs +3 -2
- package/src/core/altium/PcbRawRecordRegistry.mjs +121 -130
- package/src/core/altium/PcbRegionPrimitiveParser.mjs +76 -3
- package/src/core/altium/PcbRouteAnalysisBuilder.mjs +730 -0
- package/src/core/altium/PcbRuleParser.mjs +354 -33
- package/src/core/altium/PcbSidecarRecordParser.mjs +177 -0
- package/src/core/altium/PcbSpecialStringResolver.mjs +220 -0
- package/src/core/altium/PcbStatisticsBuilder.mjs +541 -0
- package/src/core/altium/PcbStreamExtractor.mjs +111 -4
- package/src/core/altium/PcbTextPrimitiveParser.mjs +60 -0
- package/src/core/altium/PcbUnionParser.mjs +307 -0
- package/src/core/altium/PcbViaStackParser.mjs +98 -10
- package/src/core/altium/PcbViaStructureParser.mjs +335 -0
- package/src/core/altium/PrintableTextDecoder.mjs +53 -3
- package/src/core/altium/PrjPcbModelParser.mjs +281 -7
- package/src/core/altium/ProjectAnnotationParser.mjs +205 -0
- package/src/core/altium/ProjectDesignBundleBuilder.mjs +492 -0
- package/src/core/altium/ProjectDocumentGraphBuilder.mjs +280 -0
- package/src/core/altium/ProjectNetlistExporter.mjs +503 -0
- package/src/core/altium/ProjectOutJobDigestBuilder.mjs +109 -0
- package/src/core/altium/ProjectVariantViewBuilder.mjs +334 -0
- package/src/core/altium/SchematicBindingProvenanceParser.mjs +223 -0
- package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +312 -0
- package/src/core/altium/SchematicComponentTextResolver.mjs +72 -19
- package/src/core/altium/SchematicConnectivityQaBuilder.mjs +271 -0
- package/src/core/altium/SchematicCrossSheetConnectorParser.mjs +140 -0
- package/src/core/altium/SchematicDirectiveParser.mjs +312 -0
- package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +231 -0
- package/src/core/altium/SchematicHarnessParser.mjs +302 -0
- package/src/core/altium/SchematicImageParser.mjs +474 -3
- package/src/core/altium/SchematicImplementationParser.mjs +518 -0
- package/src/core/altium/SchematicNetlistBuilder.mjs +15 -2
- package/src/core/altium/SchematicOwnershipGraphParser.mjs +195 -0
- package/src/core/altium/SchematicPinParser.mjs +84 -1
- package/src/core/altium/SchematicPrimitiveParser.mjs +301 -0
- package/src/core/altium/SchematicProjectParameterResolver.mjs +361 -0
- package/src/core/altium/SchematicQaReportBuilder.mjs +284 -0
- package/src/core/altium/SchematicRecordTypeRegistry.mjs +137 -0
- package/src/core/altium/SchematicRepeatedChannelParser.mjs +229 -0
- package/src/core/altium/SchematicStreamExtractor.mjs +10 -1
- package/src/core/altium/SchematicTemplateParser.mjs +256 -0
- package/src/core/altium/SchematicTextParser.mjs +123 -0
- package/src/core/altium/SvgModelCrossLinkValidator.mjs +402 -0
- package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +136 -96
- package/src/core/circuit-json/CircuitJsonModelAdapterPcbElements.mjs +244 -0
- package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
- package/src/core/ole/OleCompoundDocument.mjs +20 -0
- package/src/parser.mjs +35 -0
- package/src/styles/altium-renderers.css +19 -0
- package/src/ui/PcbBarcodeTextRenderer.mjs +436 -0
- package/src/ui/PcbInteractionIndex.mjs +9 -4
- package/src/ui/PcbScene3dBuilder.mjs +137 -3
- package/src/ui/PcbScene3dModelRegistry.mjs +74 -0
- package/src/ui/PcbSvgRenderer.mjs +1252 -34
- package/src/ui/PcbTextPrimitiveRenderer.mjs +193 -7
- package/src/ui/SchematicNoteRenderer.mjs +9 -2
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +206 -0
- package/src/ui/SchematicShapeRenderer.mjs +362 -0
- package/src/ui/SchematicSvgRenderer.mjs +1442 -92
- package/src/ui/SchematicTypography.mjs +48 -5
- package/src/ui/TextGeometrySidecarBuilder.mjs +147 -0
|
@@ -4,8 +4,10 @@
|
|
|
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
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Converts between legacy renderer models and Circuit JSON element arrays.
|
|
@@ -175,7 +177,8 @@ export class CircuitJsonModelAdapter {
|
|
|
175
177
|
width: Primitives.number(component.width, 0),
|
|
176
178
|
height: Primitives.number(component.height, 0)
|
|
177
179
|
},
|
|
178
|
-
rotation: Primitives.number(component.rotation, 0)
|
|
180
|
+
rotation: Primitives.number(component.rotation, 0),
|
|
181
|
+
is_box_with_pins: true
|
|
179
182
|
})
|
|
180
183
|
}
|
|
181
184
|
|
|
@@ -204,9 +207,8 @@ export class CircuitJsonModelAdapter {
|
|
|
204
207
|
pin.name || pin.designator || pinIndex,
|
|
205
208
|
String(pinIndex + 1)
|
|
206
209
|
),
|
|
207
|
-
|
|
208
|
-
pin.pinNumber || pin.designator || pin.name
|
|
209
|
-
String(pinIndex + 1)
|
|
210
|
+
...CircuitJsonModelAdapter.#pinNumberField(
|
|
211
|
+
pin.pinNumber || pin.designator || pin.name
|
|
210
212
|
)
|
|
211
213
|
})
|
|
212
214
|
circuitJson.push({
|
|
@@ -232,7 +234,8 @@ export class CircuitJsonModelAdapter {
|
|
|
232
234
|
circuitJson.push({
|
|
233
235
|
type: 'source_net',
|
|
234
236
|
source_net_id: sourceNetId,
|
|
235
|
-
name: Primitives.string(net.name, `NET_${netIndex + 1}`)
|
|
237
|
+
name: Primitives.string(net.name, `NET_${netIndex + 1}`),
|
|
238
|
+
member_source_group_ids: []
|
|
236
239
|
})
|
|
237
240
|
}
|
|
238
241
|
|
|
@@ -269,7 +272,7 @@ export class CircuitJsonModelAdapter {
|
|
|
269
272
|
*/
|
|
270
273
|
static #appendPcb(circuitJson, model, idScope) {
|
|
271
274
|
const pcb = model.pcb || {}
|
|
272
|
-
const
|
|
275
|
+
const componentRefs = new Map()
|
|
273
276
|
const sourceNetIds = new Map()
|
|
274
277
|
const boardId = Primitives.id(idScope, ['pcb_board'])
|
|
275
278
|
|
|
@@ -295,7 +298,8 @@ export class CircuitJsonModelAdapter {
|
|
|
295
298
|
name: Primitives.string(
|
|
296
299
|
net.name || net.netName,
|
|
297
300
|
`NET_${netIndex + 1}`
|
|
298
|
-
)
|
|
301
|
+
),
|
|
302
|
+
member_source_group_ids: []
|
|
299
303
|
})
|
|
300
304
|
}
|
|
301
305
|
|
|
@@ -310,9 +314,12 @@ export class CircuitJsonModelAdapter {
|
|
|
310
314
|
'pcb_component',
|
|
311
315
|
component.designator || component.name || componentIndex
|
|
312
316
|
])
|
|
313
|
-
|
|
317
|
+
componentRefs.set(
|
|
314
318
|
Primitives.componentKey(component, componentIndex),
|
|
315
|
-
|
|
319
|
+
{
|
|
320
|
+
pcbComponentId,
|
|
321
|
+
sourceComponentId
|
|
322
|
+
}
|
|
316
323
|
)
|
|
317
324
|
circuitJson.push(
|
|
318
325
|
CircuitJsonModelAdapter.#sourceComponent(
|
|
@@ -345,7 +352,7 @@ export class CircuitJsonModelAdapter {
|
|
|
345
352
|
idScope,
|
|
346
353
|
pad,
|
|
347
354
|
padIndex,
|
|
348
|
-
|
|
355
|
+
componentRefs,
|
|
349
356
|
sourceNetIds
|
|
350
357
|
)
|
|
351
358
|
}
|
|
@@ -367,8 +374,7 @@ export class CircuitJsonModelAdapter {
|
|
|
367
374
|
circuitJson,
|
|
368
375
|
idScope,
|
|
369
376
|
via,
|
|
370
|
-
viaIndex
|
|
371
|
-
sourceNetIds
|
|
377
|
+
viaIndex
|
|
372
378
|
)
|
|
373
379
|
}
|
|
374
380
|
}
|
|
@@ -415,7 +421,7 @@ export class CircuitJsonModelAdapter {
|
|
|
415
421
|
* @param {string} idScope
|
|
416
422
|
* @param {Record<string, unknown>} pad
|
|
417
423
|
* @param {number} padIndex
|
|
418
|
-
* @param {Map<string, string>}
|
|
424
|
+
* @param {Map<string, { pcbComponentId: string, sourceComponentId: string }>} componentRefs
|
|
419
425
|
* @param {Map<string, string>} sourceNetIds
|
|
420
426
|
* @returns {void}
|
|
421
427
|
*/
|
|
@@ -424,12 +430,14 @@ export class CircuitJsonModelAdapter {
|
|
|
424
430
|
idScope,
|
|
425
431
|
pad,
|
|
426
432
|
padIndex,
|
|
427
|
-
|
|
433
|
+
componentRefs,
|
|
428
434
|
sourceNetIds
|
|
429
435
|
) {
|
|
436
|
+
const componentRef =
|
|
437
|
+
componentRefs.get(String(pad.componentIndex)) ||
|
|
438
|
+
componentRefs.get('0')
|
|
430
439
|
const sourceComponentId =
|
|
431
|
-
|
|
432
|
-
componentIds.get('0') ||
|
|
440
|
+
componentRef?.sourceComponentId ||
|
|
433
441
|
Primitives.id(idScope, ['source_component', 'unassigned'])
|
|
434
442
|
const sourcePortId = Primitives.sourcePortId(
|
|
435
443
|
idScope,
|
|
@@ -438,69 +446,74 @@ export class CircuitJsonModelAdapter {
|
|
|
438
446
|
sourceComponentId
|
|
439
447
|
)
|
|
440
448
|
const pcbPortId = Primitives.id(idScope, ['pcb_port', sourcePortId])
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
String(padIndex + 1)
|
|
454
|
-
)
|
|
455
|
-
]
|
|
456
|
-
}
|
|
457
|
-
const sourceNetId = Primitives.netIdForPrimitive(
|
|
458
|
-
idScope,
|
|
459
|
-
pad,
|
|
460
|
-
sourceNetIds
|
|
461
|
-
)
|
|
449
|
+
const pcbComponentId =
|
|
450
|
+
componentRef?.pcbComponentId ||
|
|
451
|
+
Primitives.id(idScope, ['pcb_component', 'unassigned'])
|
|
452
|
+
const center = Primitives.milPoint(pad.x, pad.y)
|
|
453
|
+
const layer = Primitives.side(pad.layer)
|
|
454
|
+
const portHints = [
|
|
455
|
+
Primitives.string(
|
|
456
|
+
pad.name || pad.pinName || pad.designator,
|
|
457
|
+
String(padIndex + 1)
|
|
458
|
+
)
|
|
459
|
+
]
|
|
460
|
+
Primitives.netIdForPrimitive(idScope, pad, sourceNetIds)
|
|
462
461
|
|
|
463
462
|
circuitJson.push({
|
|
464
463
|
type: 'source_port',
|
|
465
464
|
source_port_id: sourcePortId,
|
|
466
465
|
source_component_id: sourceComponentId,
|
|
467
|
-
name:
|
|
468
|
-
|
|
466
|
+
name: portHints[0],
|
|
467
|
+
port_hints: portHints,
|
|
468
|
+
...CircuitJsonModelAdapter.#pinNumberField(portHints[0])
|
|
469
469
|
})
|
|
470
470
|
circuitJson.push({
|
|
471
471
|
type: 'pcb_port',
|
|
472
|
-
|
|
473
|
-
|
|
472
|
+
pcb_port_id: pcbPortId,
|
|
473
|
+
source_port_id: sourcePortId,
|
|
474
|
+
pcb_component_id: pcbComponentId,
|
|
475
|
+
x: center.x,
|
|
476
|
+
y: center.y,
|
|
477
|
+
layers: Primitives.isThroughHolePad(pad)
|
|
478
|
+
? ['top', 'bottom']
|
|
479
|
+
: [layer]
|
|
474
480
|
})
|
|
475
481
|
|
|
476
482
|
if (Primitives.isThroughHolePad(pad)) {
|
|
477
|
-
circuitJson.push(
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
483
|
+
circuitJson.push(
|
|
484
|
+
pad.isPlated === false
|
|
485
|
+
? PcbElements.hole(
|
|
486
|
+
idScope,
|
|
487
|
+
pad,
|
|
488
|
+
padIndex,
|
|
489
|
+
pcbComponentId,
|
|
490
|
+
center
|
|
491
|
+
)
|
|
492
|
+
: PcbElements.platedHole(
|
|
493
|
+
idScope,
|
|
494
|
+
pad,
|
|
495
|
+
padIndex,
|
|
496
|
+
pcbComponentId,
|
|
497
|
+
pcbPortId,
|
|
498
|
+
center,
|
|
499
|
+
portHints
|
|
500
|
+
)
|
|
501
|
+
)
|
|
487
502
|
return
|
|
488
503
|
}
|
|
489
504
|
|
|
490
|
-
circuitJson.push(
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
rotation: Primitives.number(pad.rotation || pad.holeRotation, 0)
|
|
503
|
-
})
|
|
505
|
+
circuitJson.push(
|
|
506
|
+
PcbElements.smtPad(
|
|
507
|
+
idScope,
|
|
508
|
+
pad,
|
|
509
|
+
padIndex,
|
|
510
|
+
pcbComponentId,
|
|
511
|
+
pcbPortId,
|
|
512
|
+
center,
|
|
513
|
+
layer,
|
|
514
|
+
portHints
|
|
515
|
+
)
|
|
516
|
+
)
|
|
504
517
|
}
|
|
505
518
|
|
|
506
519
|
/**
|
|
@@ -523,14 +536,16 @@ export class CircuitJsonModelAdapter {
|
|
|
523
536
|
'source_trace',
|
|
524
537
|
track.netName || track.netIndex || trackIndex
|
|
525
538
|
])
|
|
539
|
+
const sourceNetId = Primitives.netIdForPrimitive(
|
|
540
|
+
idScope,
|
|
541
|
+
track,
|
|
542
|
+
sourceNetIds
|
|
543
|
+
)
|
|
526
544
|
circuitJson.push({
|
|
527
545
|
type: 'source_trace',
|
|
528
546
|
source_trace_id: sourceTraceId,
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
track,
|
|
532
|
-
sourceNetIds
|
|
533
|
-
)
|
|
547
|
+
connected_source_port_ids: [],
|
|
548
|
+
connected_source_net_ids: sourceNetId ? [sourceNetId] : []
|
|
534
549
|
})
|
|
535
550
|
circuitJson.push({
|
|
536
551
|
type: 'pcb_trace',
|
|
@@ -561,18 +576,12 @@ export class CircuitJsonModelAdapter {
|
|
|
561
576
|
* @param {string} idScope
|
|
562
577
|
* @param {Record<string, unknown>} via
|
|
563
578
|
* @param {number} viaIndex
|
|
564
|
-
* @param {Map<string, string>} sourceNetIds
|
|
565
579
|
* @returns {void}
|
|
566
580
|
*/
|
|
567
|
-
static #appendPcbVia(circuitJson, idScope, via, viaIndex
|
|
581
|
+
static #appendPcbVia(circuitJson, idScope, via, viaIndex) {
|
|
568
582
|
circuitJson.push({
|
|
569
583
|
type: 'pcb_via',
|
|
570
584
|
pcb_via_id: Primitives.id(idScope, ['pcb_via', viaIndex]),
|
|
571
|
-
source_net_id: Primitives.netIdForPrimitive(
|
|
572
|
-
idScope,
|
|
573
|
-
via,
|
|
574
|
-
sourceNetIds
|
|
575
|
-
),
|
|
576
585
|
x: Primitives.milNumber(via.x, 0),
|
|
577
586
|
y: Primitives.milNumber(via.y, 0),
|
|
578
587
|
outer_diameter: Primitives.milNumber(via.diameter, 0),
|
|
@@ -664,33 +673,47 @@ export class CircuitJsonModelAdapter {
|
|
|
664
673
|
x2: Primitives.number(line.x2, 0),
|
|
665
674
|
y2: Primitives.number(line.y2, 0),
|
|
666
675
|
stroke_width: Primitives.number(line.width, 1),
|
|
667
|
-
is_dashed: line.dashed === true
|
|
676
|
+
is_dashed: line.dashed === true,
|
|
677
|
+
color: '#000000'
|
|
668
678
|
}
|
|
669
679
|
circuitJson.push(lineElement)
|
|
670
680
|
|
|
671
681
|
if (line.kind === 'wire' || line.netName || line.netIndex) {
|
|
682
|
+
const sourceNetId =
|
|
683
|
+
netIds.get(String(line.netName)) ||
|
|
684
|
+
Primitives.sourceNetId(
|
|
685
|
+
idScope,
|
|
686
|
+
line.netName || line.netIndex || lineIndex
|
|
687
|
+
)
|
|
688
|
+
const sourceTraceId = Primitives.id(idScope, [
|
|
689
|
+
'source_trace',
|
|
690
|
+
line.netName || line.netIndex || lineIndex,
|
|
691
|
+
lineIndex
|
|
692
|
+
])
|
|
693
|
+
circuitJson.push({
|
|
694
|
+
type: 'source_trace',
|
|
695
|
+
source_trace_id: sourceTraceId,
|
|
696
|
+
connected_source_port_ids: [],
|
|
697
|
+
connected_source_net_ids: sourceNetId ? [sourceNetId] : []
|
|
698
|
+
})
|
|
672
699
|
circuitJson.push({
|
|
673
700
|
type: 'schematic_trace',
|
|
674
701
|
schematic_trace_id: Primitives.id(idScope, [
|
|
675
702
|
'schematic_trace',
|
|
676
703
|
lineIndex
|
|
677
704
|
]),
|
|
678
|
-
source_trace_id:
|
|
679
|
-
|
|
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
|
-
),
|
|
705
|
+
source_trace_id: sourceTraceId,
|
|
706
|
+
junctions: [],
|
|
688
707
|
edges: [
|
|
689
708
|
{
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
709
|
+
from: {
|
|
710
|
+
x: lineElement.x1,
|
|
711
|
+
y: lineElement.y1
|
|
712
|
+
},
|
|
713
|
+
to: {
|
|
714
|
+
x: lineElement.x2,
|
|
715
|
+
y: lineElement.y2
|
|
716
|
+
}
|
|
694
717
|
}
|
|
695
718
|
]
|
|
696
719
|
})
|
|
@@ -712,11 +735,11 @@ export class CircuitJsonModelAdapter {
|
|
|
712
735
|
)
|
|
713
736
|
const base = {
|
|
714
737
|
text: textValue,
|
|
715
|
-
|
|
716
|
-
anchor_alignment: 'center'
|
|
738
|
+
position: Primitives.point(text.x, text.y)
|
|
717
739
|
}
|
|
718
740
|
|
|
719
741
|
if (Primitives.isNetLabel(text)) {
|
|
742
|
+
const center = Primitives.point(text.x, text.y)
|
|
720
743
|
circuitJson.push({
|
|
721
744
|
type: 'schematic_net_label',
|
|
722
745
|
schematic_net_label_id: Primitives.id(idScope, [
|
|
@@ -727,7 +750,10 @@ export class CircuitJsonModelAdapter {
|
|
|
727
750
|
idScope,
|
|
728
751
|
textValue || textIndex
|
|
729
752
|
),
|
|
730
|
-
|
|
753
|
+
text: textValue,
|
|
754
|
+
center,
|
|
755
|
+
anchor_position: center,
|
|
756
|
+
anchor_side: 'top'
|
|
731
757
|
})
|
|
732
758
|
return
|
|
733
759
|
}
|
|
@@ -738,10 +764,24 @@ export class CircuitJsonModelAdapter {
|
|
|
738
764
|
'schematic_text',
|
|
739
765
|
textIndex
|
|
740
766
|
]),
|
|
741
|
-
...base
|
|
767
|
+
...base,
|
|
768
|
+
font_size: Primitives.number(text.fontSize || text.size, 0.18),
|
|
769
|
+
rotation: Primitives.number(text.rotation, 0),
|
|
770
|
+
anchor: 'center',
|
|
771
|
+
color: '#000000'
|
|
742
772
|
})
|
|
743
773
|
}
|
|
744
774
|
|
|
775
|
+
/**
|
|
776
|
+
* Returns an optional numeric pin number field.
|
|
777
|
+
* @param {unknown} value
|
|
778
|
+
* @returns {{ pin_number?: number }}
|
|
779
|
+
*/
|
|
780
|
+
static #pinNumberField(value) {
|
|
781
|
+
const pinNumber = Primitives.number(value, null)
|
|
782
|
+
return pinNumber === null ? {} : { pin_number: pinNumber }
|
|
783
|
+
}
|
|
784
|
+
|
|
745
785
|
/**
|
|
746
786
|
* Returns a source component id for one schematic pin.
|
|
747
787
|
* @param {Record<string, unknown>} pin
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { CircuitJsonModelAdapterPrimitives } from './CircuitJsonModelAdapterPrimitives.mjs'
|
|
6
|
+
|
|
7
|
+
const Primitives = CircuitJsonModelAdapterPrimitives
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Builds upstream-compatible Circuit JSON PCB element records.
|
|
11
|
+
*/
|
|
12
|
+
export class CircuitJsonModelAdapterPcbElements {
|
|
13
|
+
/**
|
|
14
|
+
* Builds an upstream-compatible SMT pad element.
|
|
15
|
+
* @param {string} idScope
|
|
16
|
+
* @param {Record<string, unknown>} pad
|
|
17
|
+
* @param {number} padIndex
|
|
18
|
+
* @param {string} pcbComponentId
|
|
19
|
+
* @param {string} pcbPortId
|
|
20
|
+
* @param {{ x: number, y: number }} center
|
|
21
|
+
* @param {string} layer
|
|
22
|
+
* @param {string[]} portHints
|
|
23
|
+
* @returns {object}
|
|
24
|
+
*/
|
|
25
|
+
static smtPad(
|
|
26
|
+
idScope,
|
|
27
|
+
pad,
|
|
28
|
+
padIndex,
|
|
29
|
+
pcbComponentId,
|
|
30
|
+
pcbPortId,
|
|
31
|
+
center,
|
|
32
|
+
layer,
|
|
33
|
+
portHints
|
|
34
|
+
) {
|
|
35
|
+
const shape = Primitives.padShape(pad)
|
|
36
|
+
const width = Primitives.milNumber(
|
|
37
|
+
pad.sizeTopX || pad.sizeX || pad.width,
|
|
38
|
+
0
|
|
39
|
+
)
|
|
40
|
+
const height = Primitives.milNumber(
|
|
41
|
+
pad.sizeTopY || pad.sizeY || pad.height,
|
|
42
|
+
0
|
|
43
|
+
)
|
|
44
|
+
const rotation = Primitives.number(pad.rotation || pad.holeRotation, 0)
|
|
45
|
+
const base = {
|
|
46
|
+
type: 'pcb_smtpad',
|
|
47
|
+
pcb_smtpad_id: Primitives.id(idScope, ['pcb_smtpad', padIndex]),
|
|
48
|
+
pcb_component_id: pcbComponentId,
|
|
49
|
+
pcb_port_id: pcbPortId,
|
|
50
|
+
x: center.x,
|
|
51
|
+
y: center.y,
|
|
52
|
+
layer,
|
|
53
|
+
port_hints: portHints
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (shape === 'circle') {
|
|
57
|
+
return {
|
|
58
|
+
...base,
|
|
59
|
+
shape,
|
|
60
|
+
radius: Primitives.round(Math.max(width, height) / 2)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (shape === 'pill') {
|
|
65
|
+
return {
|
|
66
|
+
...base,
|
|
67
|
+
shape: CircuitJsonModelAdapterPcbElements.#hasRotation(rotation)
|
|
68
|
+
? 'rotated_pill'
|
|
69
|
+
: 'pill',
|
|
70
|
+
width,
|
|
71
|
+
height,
|
|
72
|
+
radius: Primitives.round(Math.min(width, height) / 2),
|
|
73
|
+
...CircuitJsonModelAdapterPcbElements.#ccwRotationField(
|
|
74
|
+
rotation
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
...base,
|
|
81
|
+
shape: CircuitJsonModelAdapterPcbElements.#hasRotation(rotation)
|
|
82
|
+
? 'rotated_rect'
|
|
83
|
+
: 'rect',
|
|
84
|
+
width,
|
|
85
|
+
height,
|
|
86
|
+
...CircuitJsonModelAdapterPcbElements.#ccwRotationField(rotation)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Builds an upstream-compatible non-plated hole element.
|
|
92
|
+
* @param {string} idScope
|
|
93
|
+
* @param {Record<string, unknown>} pad
|
|
94
|
+
* @param {number} padIndex
|
|
95
|
+
* @param {string} pcbComponentId
|
|
96
|
+
* @param {{ x: number, y: number }} center
|
|
97
|
+
* @returns {object}
|
|
98
|
+
*/
|
|
99
|
+
static hole(idScope, pad, padIndex, pcbComponentId, center) {
|
|
100
|
+
const shape = Primitives.padShape(pad)
|
|
101
|
+
const width = Primitives.milNumber(
|
|
102
|
+
pad.sizeTopX || pad.sizeX || pad.width || pad.diameter,
|
|
103
|
+
0
|
|
104
|
+
)
|
|
105
|
+
const height = Primitives.milNumber(
|
|
106
|
+
pad.sizeTopY || pad.sizeY || pad.height || pad.diameter,
|
|
107
|
+
0
|
|
108
|
+
)
|
|
109
|
+
const holeDiameter = Primitives.milNumber(pad.holeDiameter, 0)
|
|
110
|
+
const base = {
|
|
111
|
+
type: 'pcb_hole',
|
|
112
|
+
pcb_hole_id: Primitives.id(idScope, ['pcb_hole', padIndex]),
|
|
113
|
+
pcb_component_id: pcbComponentId,
|
|
114
|
+
x: center.x,
|
|
115
|
+
y: center.y
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (shape === 'circle') {
|
|
119
|
+
return {
|
|
120
|
+
...base,
|
|
121
|
+
hole_shape: 'circle',
|
|
122
|
+
hole_diameter: holeDiameter
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
...base,
|
|
128
|
+
hole_shape: shape === 'pill' ? 'pill' : 'rect',
|
|
129
|
+
hole_width: holeDiameter || width,
|
|
130
|
+
hole_height: holeDiameter || height
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Builds an upstream-compatible plated hole element.
|
|
136
|
+
* @param {string} idScope
|
|
137
|
+
* @param {Record<string, unknown>} pad
|
|
138
|
+
* @param {number} padIndex
|
|
139
|
+
* @param {string} pcbComponentId
|
|
140
|
+
* @param {string} pcbPortId
|
|
141
|
+
* @param {{ x: number, y: number }} center
|
|
142
|
+
* @param {string[]} portHints
|
|
143
|
+
* @returns {object}
|
|
144
|
+
*/
|
|
145
|
+
static platedHole(
|
|
146
|
+
idScope,
|
|
147
|
+
pad,
|
|
148
|
+
padIndex,
|
|
149
|
+
pcbComponentId,
|
|
150
|
+
pcbPortId,
|
|
151
|
+
center,
|
|
152
|
+
portHints
|
|
153
|
+
) {
|
|
154
|
+
const shape = Primitives.padShape(pad)
|
|
155
|
+
const width = Primitives.milNumber(
|
|
156
|
+
pad.sizeTopX || pad.sizeX || pad.width || pad.diameter,
|
|
157
|
+
0
|
|
158
|
+
)
|
|
159
|
+
const height = Primitives.milNumber(
|
|
160
|
+
pad.sizeTopY || pad.sizeY || pad.height || pad.diameter,
|
|
161
|
+
0
|
|
162
|
+
)
|
|
163
|
+
const holeDiameter = Primitives.milNumber(pad.holeDiameter, 0)
|
|
164
|
+
const rotation = Primitives.number(pad.rotation || pad.holeRotation, 0)
|
|
165
|
+
const base = {
|
|
166
|
+
type: 'pcb_plated_hole',
|
|
167
|
+
pcb_plated_hole_id: Primitives.id(idScope, [
|
|
168
|
+
'pcb_plated_hole',
|
|
169
|
+
padIndex
|
|
170
|
+
]),
|
|
171
|
+
pcb_component_id: pcbComponentId,
|
|
172
|
+
pcb_port_id: pcbPortId,
|
|
173
|
+
x: center.x,
|
|
174
|
+
y: center.y,
|
|
175
|
+
layers: ['top', 'bottom'],
|
|
176
|
+
port_hints: portHints
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (shape === 'circle') {
|
|
180
|
+
return {
|
|
181
|
+
...base,
|
|
182
|
+
shape,
|
|
183
|
+
outer_diameter: Primitives.round(Math.max(width, height)),
|
|
184
|
+
hole_diameter: holeDiameter
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (shape === 'pill') {
|
|
189
|
+
return {
|
|
190
|
+
...base,
|
|
191
|
+
shape,
|
|
192
|
+
outer_width: width,
|
|
193
|
+
outer_height: height,
|
|
194
|
+
hole_width: holeDiameter,
|
|
195
|
+
hole_height: holeDiameter,
|
|
196
|
+
ccw_rotation: rotation
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
...base,
|
|
202
|
+
shape: 'circular_hole_with_rect_pad',
|
|
203
|
+
hole_shape: 'circle',
|
|
204
|
+
pad_shape: 'rect',
|
|
205
|
+
hole_diameter: holeDiameter,
|
|
206
|
+
rect_pad_width: width,
|
|
207
|
+
rect_pad_height: height,
|
|
208
|
+
...CircuitJsonModelAdapterPcbElements.#rectCcwRotationField(
|
|
209
|
+
rotation
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Returns true when a rotation value should use a rotated pad shape.
|
|
216
|
+
* @param {number | null} rotation
|
|
217
|
+
* @returns {boolean}
|
|
218
|
+
*/
|
|
219
|
+
static #hasRotation(rotation) {
|
|
220
|
+
return Math.abs(rotation || 0) > 0.000001
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Returns an optional counter-clockwise rotation field.
|
|
225
|
+
* @param {number | null} rotation
|
|
226
|
+
* @returns {{ ccw_rotation?: number }}
|
|
227
|
+
*/
|
|
228
|
+
static #ccwRotationField(rotation) {
|
|
229
|
+
return CircuitJsonModelAdapterPcbElements.#hasRotation(rotation)
|
|
230
|
+
? { ccw_rotation: rotation || 0 }
|
|
231
|
+
: {}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Returns an optional rectangular pad rotation field.
|
|
236
|
+
* @param {number | null} rotation
|
|
237
|
+
* @returns {{ rect_ccw_rotation?: number }}
|
|
238
|
+
*/
|
|
239
|
+
static #rectCcwRotationField(rotation) {
|
|
240
|
+
return CircuitJsonModelAdapterPcbElements.#hasRotation(rotation)
|
|
241
|
+
? { rect_ccw_rotation: rotation || 0 }
|
|
242
|
+
: {}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -30,6 +30,7 @@ export class OleCompoundDocument {
|
|
|
30
30
|
constructor(arrayBuffer) {
|
|
31
31
|
this.#reader = new BinaryReader(arrayBuffer)
|
|
32
32
|
this.#header = this.#parseHeader()
|
|
33
|
+
this.#assertSectorAlignedFile()
|
|
33
34
|
this.#fatEntries = this.#parseFatEntries()
|
|
34
35
|
this.#directoryEntries = this.#parseDirectoryEntries()
|
|
35
36
|
this.#miniFatEntries = this.#parseMiniFatEntries()
|
|
@@ -137,6 +138,25 @@ export class OleCompoundDocument {
|
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Ensures the byte stream still matches the OLE sector grid.
|
|
143
|
+
*/
|
|
144
|
+
#assertSectorAlignedFile() {
|
|
145
|
+
const payloadByteLength =
|
|
146
|
+
this.#reader.byteLength - OleConstants.HEADER_BYTE_LENGTH
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
payloadByteLength < 0 ||
|
|
150
|
+
payloadByteLength % this.#header.sectorByteLength !== 0
|
|
151
|
+
) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
'OLE compound document byte length is not sector-aligned. ' +
|
|
154
|
+
'The file may be truncated or line-ending normalized; ' +
|
|
155
|
+
'keep Altium binary files committed and served as binary.'
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
140
160
|
/**
|
|
141
161
|
* Parses all FAT entries.
|
|
142
162
|
* @returns {number[]}
|