altium-toolkit 1.1.1 → 1.1.3
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/package.json +1 -1
- package/src/core/altium/AltiumLayoutParser.mjs +275 -13
- package/src/core/altium/AltiumParser.mjs +240 -8
- package/src/core/altium/NaturalStringComparator.mjs +175 -0
- package/src/core/altium/ParserUtils.mjs +83 -14
- package/src/core/altium/PcbComponentPrimitiveIndexer.mjs +104 -33
- package/src/core/altium/PcbEmbeddedFontExtractor.mjs +186 -43
- package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +26 -4
- package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +28 -5
- package/src/core/altium/PcbOwnershipGraphBuilder.mjs +1 -3
- package/src/core/altium/PcbReviewMetadataBuilder.mjs +59 -28
- package/src/core/altium/PcbReviewRouteHighlightProfileBuilder.mjs +66 -29
- package/src/core/altium/PcbRouteAnalysisBuilder.mjs +50 -16
- package/src/core/altium/PrintableTextDecoder.mjs +133 -13
- package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +13 -0
- package/src/core/altium/SchematicComponentTextResolver.mjs +40 -1
- package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +36 -11
- package/src/core/altium/SchematicImageParser.mjs +291 -6
- package/src/core/altium/SchematicImplementationParser.mjs +75 -36
- package/src/core/altium/SchematicMultipartDesignatorNormalizer.mjs +164 -0
- package/src/core/altium/SchematicMultipartOwnerMatcher.mjs +2 -0
- package/src/core/altium/SchematicPinParser.mjs +175 -4
- package/src/core/altium/SchematicQaReportBuilder.mjs +115 -38
- package/src/core/altium/SchematicSheetStyleResolver.mjs +38 -0
- package/src/core/altium/SchematicTextParser.mjs +125 -11
- package/src/core/altium/SchematicTextPostProcessor.mjs +146 -102
- package/src/ui/PcbInteractionIndex.mjs +16 -1
- package/src/ui/SchematicColorResolver.mjs +78 -0
- package/src/ui/SchematicContentLayout.mjs +58 -1
- package/src/ui/SchematicImageRenderer.mjs +125 -10
- package/src/ui/SchematicJunctionRenderer.mjs +1 -1
- package/src/ui/SchematicNativeFooterPartitioner.mjs +275 -0
- package/src/ui/SchematicNoteRenderer.mjs +82 -6
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +292 -3
- package/src/ui/SchematicPinSvgRenderer.mjs +197 -15
- package/src/ui/SchematicPowerDiagramImageProcessor.mjs +970 -0
- package/src/ui/SchematicPowerDiagramLineMasks.mjs +631 -0
- package/src/ui/SchematicPowerPortRenderer.mjs +1 -1
- package/src/ui/SchematicShapeRenderer.mjs +82 -23
- package/src/ui/SchematicSvgRenderer.mjs +293 -47
package/package.json
CHANGED
|
@@ -267,7 +267,7 @@ export class AltiumLayoutParser {
|
|
|
267
267
|
* @param {{ width: number, height: number, marginWidth: number, paperSize?: string }} sheet
|
|
268
268
|
* @param {{ fields: Record<string, string | string[]> }[]} textRecords
|
|
269
269
|
* @param {{ x1: number, y1: number, x2: number, y2: number }[]} lines
|
|
270
|
-
* @param {{ x: number, y: number }[]} texts
|
|
270
|
+
* @param {{ x: number, y: number, cornerX?: number, cornerY?: number }[]} texts
|
|
271
271
|
* @param {{ x: number, y: number }[]} components
|
|
272
272
|
* @param {{ x: number, y: number }[]} pins
|
|
273
273
|
* @param {{ x: number, y: number, width: number, height: number }[]} rectangles
|
|
@@ -303,12 +303,47 @@ export class AltiumLayoutParser {
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
const margin = Math.max(Number(sheet?.marginWidth || 20), 20)
|
|
306
|
+
const nativeTemplateSheet =
|
|
307
|
+
AltiumLayoutParser.#resolveNativeStandardTemplateSheetSize(
|
|
308
|
+
sheet,
|
|
309
|
+
bounds,
|
|
310
|
+
margin
|
|
311
|
+
)
|
|
312
|
+
if (nativeTemplateSheet) {
|
|
313
|
+
return {
|
|
314
|
+
...sheet,
|
|
315
|
+
...nativeTemplateSheet
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (
|
|
320
|
+
AltiumLayoutParser.#shouldPreserveNativeStandardTemplateSize(
|
|
321
|
+
sheet,
|
|
322
|
+
bounds,
|
|
323
|
+
margin
|
|
324
|
+
)
|
|
325
|
+
) {
|
|
326
|
+
return sheet
|
|
327
|
+
}
|
|
328
|
+
|
|
306
329
|
const footerBounds = AltiumLayoutParser.#collectSchematicFooterBounds(
|
|
307
330
|
textRecords,
|
|
308
331
|
Number(sheet?.width || 0)
|
|
309
332
|
)
|
|
310
|
-
const
|
|
311
|
-
|
|
333
|
+
const footerLineBounds =
|
|
334
|
+
AltiumLayoutParser.#collectSchematicFooterLineBounds(
|
|
335
|
+
lines,
|
|
336
|
+
sheet,
|
|
337
|
+
margin
|
|
338
|
+
)
|
|
339
|
+
const requiredWidthResult =
|
|
340
|
+
AltiumLayoutParser.#resolveSchematicRequiredWidth(
|
|
341
|
+
sheet,
|
|
342
|
+
Math.max(bounds.maxX, footerBounds?.maxX || 0),
|
|
343
|
+
footerLineBounds?.maxX || 0,
|
|
344
|
+
margin
|
|
345
|
+
)
|
|
346
|
+
const requiredWidth = requiredWidthResult.width
|
|
312
347
|
const requiredHeight =
|
|
313
348
|
Math.max(bounds.maxY, footerBounds?.maxY || 0) + margin * 2
|
|
314
349
|
|
|
@@ -322,10 +357,12 @@ export class AltiumLayoutParser {
|
|
|
322
357
|
return sheet
|
|
323
358
|
}
|
|
324
359
|
|
|
325
|
-
const standardSheet =
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
360
|
+
const standardSheet = requiredWidthResult.usesNativeFrameEdge
|
|
361
|
+
? null
|
|
362
|
+
: AltiumLayoutParser.#resolveStandardSheetSize(
|
|
363
|
+
requiredWidth,
|
|
364
|
+
requiredHeight
|
|
365
|
+
)
|
|
329
366
|
|
|
330
367
|
if (standardSheet) {
|
|
331
368
|
return {
|
|
@@ -344,11 +381,12 @@ export class AltiumLayoutParser {
|
|
|
344
381
|
sheet.height,
|
|
345
382
|
requiredHeight
|
|
346
383
|
)
|
|
347
|
-
const resolvedStandardSheet =
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
384
|
+
const resolvedStandardSheet = requiredWidthResult.usesNativeFrameEdge
|
|
385
|
+
? null
|
|
386
|
+
: AltiumLayoutParser.#resolveStandardSheetSize(
|
|
387
|
+
resolvedWidth,
|
|
388
|
+
resolvedHeight
|
|
389
|
+
)
|
|
352
390
|
|
|
353
391
|
if (resolvedStandardSheet) {
|
|
354
392
|
return {
|
|
@@ -367,6 +405,126 @@ export class AltiumLayoutParser {
|
|
|
367
405
|
}
|
|
368
406
|
}
|
|
369
407
|
|
|
408
|
+
/**
|
|
409
|
+
* Resolves the standard template page named by Altium standard sheet
|
|
410
|
+
* records, preserving landscape or portrait orientation from the stored
|
|
411
|
+
* custom dimensions.
|
|
412
|
+
* @param {Record<string, string | string[]> | undefined} fields Sheet fields.
|
|
413
|
+
* @param {number} fallbackWidth Stored custom width.
|
|
414
|
+
* @param {number} fallbackHeight Stored custom height.
|
|
415
|
+
* @returns {{ width: number, height: number, paperSize: string, sourceWidth?: number, sourceHeight?: number } | null}
|
|
416
|
+
*/
|
|
417
|
+
static resolveSchematicTemplatePageSize(
|
|
418
|
+
fields,
|
|
419
|
+
fallbackWidth,
|
|
420
|
+
fallbackHeight
|
|
421
|
+
) {
|
|
422
|
+
if (parseNumericField(fields, 'SheetStyle') !== 1) {
|
|
423
|
+
return null
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const templateFileName = getField(fields, 'TemplateFileName')
|
|
427
|
+
const match = String(templateFileName || '').match(
|
|
428
|
+
/(?:^|[^a-z0-9])A([0-5])(?:[^a-z0-9]|$)/iu
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
if (!match) {
|
|
432
|
+
return null
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const paperSize = 'A' + match[1]
|
|
436
|
+
const portraitSheet = ISO_A_PORTRAIT_SHEETS.find(
|
|
437
|
+
(sheet) => sheet.label === paperSize
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
if (!portraitSheet) {
|
|
441
|
+
return null
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const normalizedFallbackWidth = Number(fallbackWidth || 0)
|
|
445
|
+
const normalizedFallbackHeight = Number(fallbackHeight || 0)
|
|
446
|
+
const portraitTemplate =
|
|
447
|
+
parseNumericField(fields, 'WorkspaceOrientation') === 1 ||
|
|
448
|
+
/portrait/iu.test(String(templateFileName || ''))
|
|
449
|
+
|
|
450
|
+
if (
|
|
451
|
+
portraitTemplate &&
|
|
452
|
+
normalizedFallbackWidth > normalizedFallbackHeight &&
|
|
453
|
+
normalizedFallbackHeight > 0
|
|
454
|
+
) {
|
|
455
|
+
return {
|
|
456
|
+
width: normalizedFallbackHeight,
|
|
457
|
+
height: normalizedFallbackWidth,
|
|
458
|
+
sourceWidth: normalizedFallbackHeight,
|
|
459
|
+
sourceHeight: normalizedFallbackWidth,
|
|
460
|
+
paperSize
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const landscape = normalizedFallbackWidth >= normalizedFallbackHeight
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
width: landscape ? portraitSheet.height : portraitSheet.width,
|
|
468
|
+
height: landscape ? portraitSheet.width : portraitSheet.height,
|
|
469
|
+
paperSize
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Resolves the page width required by recovered schematic geometry.
|
|
475
|
+
* Native template linework can use the declared custom width as the inner
|
|
476
|
+
* frame edge; adding both margins would create an artificial right gutter.
|
|
477
|
+
* @param {{ width?: number, borderOn?: boolean, titleBlockOn?: boolean, sheetStyle?: number }} sheet
|
|
478
|
+
* @param {number} maxX
|
|
479
|
+
* @param {number} footerLineMaxX
|
|
480
|
+
* @param {number} margin
|
|
481
|
+
* @returns {{ width: number, usesNativeFrameEdge: boolean }}
|
|
482
|
+
*/
|
|
483
|
+
static #resolveSchematicRequiredWidth(sheet, maxX, footerLineMaxX, margin) {
|
|
484
|
+
const reachesNativeFrameEdge =
|
|
485
|
+
Number(sheet?.sheetStyle || 0) !== 1 &&
|
|
486
|
+
Boolean(sheet?.borderOn || sheet?.titleBlockOn) &&
|
|
487
|
+
footerLineMaxX > 0 &&
|
|
488
|
+
maxX <= footerLineMaxX + 0.01
|
|
489
|
+
const rightPadding = reachesNativeFrameEdge ? margin : margin * 2
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
width: maxX + rightPadding,
|
|
493
|
+
usesNativeFrameEdge: reachesNativeFrameEdge
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Collects owned lower-page linework that represents native title-block or
|
|
499
|
+
* footer chrome rather than schematic content.
|
|
500
|
+
* @param {{ x1: number, y1: number, x2: number, y2: number, ownerIndex?: string }[]} lines
|
|
501
|
+
* @param {{ width?: number }} sheet
|
|
502
|
+
* @param {number} margin
|
|
503
|
+
* @returns {{ maxX: number } | null}
|
|
504
|
+
*/
|
|
505
|
+
static #collectSchematicFooterLineBounds(lines, sheet, margin) {
|
|
506
|
+
const footerLimit = Math.max(margin * 6, 120)
|
|
507
|
+
const sheetWidth = Math.max(Number(sheet?.width || 0), 0)
|
|
508
|
+
const footerStartX = sheetWidth > 0 ? sheetWidth * 0.5 : 0
|
|
509
|
+
const coordinates = []
|
|
510
|
+
|
|
511
|
+
for (const line of lines || []) {
|
|
512
|
+
if (!line?.ownerIndex) continue
|
|
513
|
+
if (Math.max(line.y1, line.y2) > footerLimit) continue
|
|
514
|
+
if (Math.max(line.x1, line.x2) < footerStartX) continue
|
|
515
|
+
|
|
516
|
+
coordinates.push(line.x1, line.x2)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (!coordinates.length) {
|
|
520
|
+
return null
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
maxX: Math.max(...coordinates)
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
370
528
|
/**
|
|
371
529
|
* Returns true when the parser should trust the authored custom sheet
|
|
372
530
|
* dimensions instead of shrinking the page to visible content bounds.
|
|
@@ -403,11 +561,108 @@ export class AltiumLayoutParser {
|
|
|
403
561
|
return false
|
|
404
562
|
}
|
|
405
563
|
|
|
564
|
+
/**
|
|
565
|
+
* Returns true when a standard template has already resolved to its native
|
|
566
|
+
* template coordinate frame rather than a promoted ISO sheet envelope.
|
|
567
|
+
* @param {{ width?: number, height?: number, sourceWidth?: number, sourceHeight?: number, paperSize?: string, sheetStyle?: number }} sheet
|
|
568
|
+
* @param {{ maxX: number, maxY: number }} bounds
|
|
569
|
+
* @param {number} margin
|
|
570
|
+
* @returns {boolean}
|
|
571
|
+
*/
|
|
572
|
+
static #shouldPreserveNativeStandardTemplateSize(sheet, bounds, margin) {
|
|
573
|
+
const width = Number(sheet?.width || 0)
|
|
574
|
+
const height = Number(sheet?.height || 0)
|
|
575
|
+
const sourceWidth = Number(sheet?.sourceWidth || 0)
|
|
576
|
+
const sourceHeight = Number(sheet?.sourceHeight || 0)
|
|
577
|
+
|
|
578
|
+
if (
|
|
579
|
+
Number(sheet?.sheetStyle || 0) !== 1 ||
|
|
580
|
+
!sheet?.paperSize ||
|
|
581
|
+
width <= 0 ||
|
|
582
|
+
height <= 0 ||
|
|
583
|
+
width !== sourceWidth ||
|
|
584
|
+
height !== sourceHeight
|
|
585
|
+
) {
|
|
586
|
+
return false
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return (
|
|
590
|
+
Number(bounds?.maxX || 0) <= width - margin + 0.01 &&
|
|
591
|
+
Number(bounds?.maxY || 0) <= height - margin + 0.01
|
|
592
|
+
)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Derives a native standard-template frame from embedded template graphics
|
|
597
|
+
* when those graphics overrun the stored custom dimensions but fit inside
|
|
598
|
+
* a smaller-than-ISO template envelope.
|
|
599
|
+
* @param {{ width?: number, height?: number, sourceWidth?: number, sourceHeight?: number, paperSize?: string, sheetStyle?: number }} sheet
|
|
600
|
+
* @param {{ maxX: number, maxY: number }} bounds
|
|
601
|
+
* @param {number} margin
|
|
602
|
+
* @returns {{ width: number, height: number, sourceWidth: number, sourceHeight: number } | null}
|
|
603
|
+
*/
|
|
604
|
+
static #resolveNativeStandardTemplateSheetSize(sheet, bounds, margin) {
|
|
605
|
+
const width = Number(sheet?.width || 0)
|
|
606
|
+
const height = Number(sheet?.height || 0)
|
|
607
|
+
const sourceWidth = Number(sheet?.sourceWidth || 0)
|
|
608
|
+
const sourceHeight = Number(sheet?.sourceHeight || 0)
|
|
609
|
+
|
|
610
|
+
if (
|
|
611
|
+
Number(sheet?.sheetStyle || 0) !== 1 ||
|
|
612
|
+
!sheet?.paperSize ||
|
|
613
|
+
sourceWidth <= 0 ||
|
|
614
|
+
sourceHeight <= 0 ||
|
|
615
|
+
width <= sourceWidth ||
|
|
616
|
+
height <= sourceHeight
|
|
617
|
+
) {
|
|
618
|
+
return null
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const maxX = Number(bounds?.maxX || 0)
|
|
622
|
+
const maxY = Number(bounds?.maxY || 0)
|
|
623
|
+
if (maxX <= sourceWidth && maxY <= sourceHeight) {
|
|
624
|
+
return null
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const nativeWidth = AltiumLayoutParser.#roundTemplateExtent(
|
|
628
|
+
maxX + margin
|
|
629
|
+
)
|
|
630
|
+
const nativeHeight = AltiumLayoutParser.#roundTemplateExtent(
|
|
631
|
+
maxY + margin
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
if (
|
|
635
|
+
nativeWidth <= sourceWidth ||
|
|
636
|
+
nativeHeight <= sourceHeight ||
|
|
637
|
+
nativeWidth >= width ||
|
|
638
|
+
nativeHeight >= height
|
|
639
|
+
) {
|
|
640
|
+
return null
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return {
|
|
644
|
+
width: nativeWidth,
|
|
645
|
+
height: nativeHeight,
|
|
646
|
+
sourceWidth: nativeWidth,
|
|
647
|
+
sourceHeight: nativeHeight
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Rounds a recovered template frame edge to the next schematic grid
|
|
653
|
+
* interval.
|
|
654
|
+
* @param {number} value Raw extent.
|
|
655
|
+
* @returns {number}
|
|
656
|
+
*/
|
|
657
|
+
static #roundTemplateExtent(value) {
|
|
658
|
+
return Math.ceil(Math.max(Number(value || 0), 0) / 10) * 10
|
|
659
|
+
}
|
|
660
|
+
|
|
406
661
|
/**
|
|
407
662
|
* Collects the visible coordinate envelope from recovered schematic
|
|
408
663
|
* primitives.
|
|
409
664
|
* @param {{ x1: number, y1: number, x2: number, y2: number }[]} lines
|
|
410
|
-
* @param {{ x: number, y: number }[]} texts
|
|
665
|
+
* @param {{ x: number, y: number, cornerX?: number, cornerY?: number }[]} texts
|
|
411
666
|
* @param {{ x: number, y: number }[]} components
|
|
412
667
|
* @param {{ x: number, y: number }[]} pins
|
|
413
668
|
* @param {{ x: number, y: number, width: number, height: number }[]} rectangles
|
|
@@ -434,6 +689,13 @@ export class AltiumLayoutParser {
|
|
|
434
689
|
|
|
435
690
|
for (const text of texts) {
|
|
436
691
|
coordinates.push([text.x, text.y])
|
|
692
|
+
|
|
693
|
+
if (
|
|
694
|
+
Number.isFinite(Number(text.cornerX)) &&
|
|
695
|
+
Number.isFinite(Number(text.cornerY))
|
|
696
|
+
) {
|
|
697
|
+
coordinates.push([Number(text.cornerX), Number(text.cornerY)])
|
|
698
|
+
}
|
|
437
699
|
}
|
|
438
700
|
|
|
439
701
|
for (const component of components) {
|
|
@@ -59,6 +59,7 @@ const {
|
|
|
59
59
|
extractSchematicFonts,
|
|
60
60
|
extractSchematicFontDiagnostics,
|
|
61
61
|
extractSchematicMetadata,
|
|
62
|
+
extractSchematicOwnerMetadata,
|
|
62
63
|
extractSchematicTitleBlock,
|
|
63
64
|
normalizeSchematicTextRecord
|
|
64
65
|
} = SchematicTextParser
|
|
@@ -186,7 +187,7 @@ export class AltiumParser {
|
|
|
186
187
|
AltiumParser.#collectOwnersWithImplicitDisplayMode(records)
|
|
187
188
|
const activeMultipartOwnerParts =
|
|
188
189
|
SchematicMultipartOwnerMatcher.collectActiveMultipartOwnerParts(
|
|
189
|
-
|
|
190
|
+
recordIndexAwareRecords,
|
|
190
191
|
componentRecords
|
|
191
192
|
)
|
|
192
193
|
const sheetRecord = records.find(
|
|
@@ -329,21 +330,30 @@ export class AltiumParser {
|
|
|
329
330
|
}
|
|
330
331
|
|
|
331
332
|
const metadataTexts = extractSchematicMetadata(textRecords)
|
|
333
|
+
const ownerMetadataTexts = extractSchematicOwnerMetadata(textRecords)
|
|
332
334
|
const schematicFonts = extractSchematicFonts(sheetRecord?.fields)
|
|
333
335
|
const schematicRenderDiagnostics = extractSchematicFontDiagnostics(
|
|
334
336
|
sheetRecord?.fields
|
|
335
337
|
)
|
|
336
|
-
const
|
|
338
|
+
const storedSheetWidth =
|
|
337
339
|
parseNumericField(sheetRecord?.fields, 'CustomX') || 1500
|
|
338
|
-
const
|
|
340
|
+
const storedSheetHeight =
|
|
339
341
|
parseNumericField(sheetRecord?.fields, 'CustomY') || 950
|
|
342
|
+
const templatePageSize =
|
|
343
|
+
AltiumLayoutParser.resolveSchematicTemplatePageSize(
|
|
344
|
+
sheetRecord?.fields,
|
|
345
|
+
storedSheetWidth,
|
|
346
|
+
storedSheetHeight
|
|
347
|
+
)
|
|
348
|
+
const sheetWidth = templatePageSize?.width || storedSheetWidth
|
|
349
|
+
const sheetHeight = templatePageSize?.height || storedSheetHeight
|
|
340
350
|
const sheetMargin =
|
|
341
351
|
parseNumericField(sheetRecord?.fields, 'CustomMarginWidth') || 20
|
|
342
352
|
const sheet = {
|
|
343
353
|
width: sheetWidth,
|
|
344
354
|
height: sheetHeight,
|
|
345
|
-
sourceWidth:
|
|
346
|
-
sourceHeight:
|
|
355
|
+
sourceWidth: templatePageSize?.sourceWidth || storedSheetWidth,
|
|
356
|
+
sourceHeight: templatePageSize?.sourceHeight || storedSheetHeight,
|
|
347
357
|
visibleGrid:
|
|
348
358
|
parseNumericField(sheetRecord?.fields, 'VisibleGridSize') || 10,
|
|
349
359
|
snapGrid:
|
|
@@ -363,6 +373,9 @@ export class AltiumParser {
|
|
|
363
373
|
fonts: schematicFonts,
|
|
364
374
|
sheetStyle:
|
|
365
375
|
parseNumericField(sheetRecord?.fields, 'SheetStyle') || 0,
|
|
376
|
+
...(templatePageSize?.paperSize
|
|
377
|
+
? { paperSize: templatePageSize.paperSize }
|
|
378
|
+
: {}),
|
|
366
379
|
titleBlock: extractSchematicTitleBlock(
|
|
367
380
|
textRecords,
|
|
368
381
|
metadataTexts,
|
|
@@ -415,7 +428,19 @@ export class AltiumParser {
|
|
|
415
428
|
)
|
|
416
429
|
)
|
|
417
430
|
]
|
|
418
|
-
const
|
|
431
|
+
const ownerDrawnInternalPinOwners =
|
|
432
|
+
AltiumParser.#collectInaccessibleOwnerDrawnPrimitiveOwners(
|
|
433
|
+
drawableRecords
|
|
434
|
+
)
|
|
435
|
+
const numericEndpointLabelOwners =
|
|
436
|
+
AltiumParser.#collectNumericEndpointLabelOwners(
|
|
437
|
+
componentRecords,
|
|
438
|
+
recordIndexAwareRecords
|
|
439
|
+
)
|
|
440
|
+
const pins = parseSchematicPins(pinRecords, {
|
|
441
|
+
ownerDrawnInternalPinOwners,
|
|
442
|
+
numericEndpointLabelOwners
|
|
443
|
+
})
|
|
419
444
|
const junctions = SchematicJunctionParser.parseSchematicJunctions(
|
|
420
445
|
recordIndexAwareRecords
|
|
421
446
|
)
|
|
@@ -489,7 +514,8 @@ export class AltiumParser {
|
|
|
489
514
|
record.fields,
|
|
490
515
|
metadataTexts,
|
|
491
516
|
sheet,
|
|
492
|
-
schematicFonts
|
|
517
|
+
schematicFonts,
|
|
518
|
+
ownerMetadataTexts
|
|
493
519
|
)
|
|
494
520
|
)
|
|
495
521
|
.filter(Boolean)
|
|
@@ -524,7 +550,13 @@ export class AltiumParser {
|
|
|
524
550
|
normalizedLines,
|
|
525
551
|
pins,
|
|
526
552
|
ports,
|
|
527
|
-
|
|
553
|
+
{
|
|
554
|
+
rectangles,
|
|
555
|
+
roundedRectangles,
|
|
556
|
+
ellipses,
|
|
557
|
+
arcs,
|
|
558
|
+
pies
|
|
559
|
+
}
|
|
528
560
|
),
|
|
529
561
|
normalizedLines,
|
|
530
562
|
pins,
|
|
@@ -584,6 +616,8 @@ export class AltiumParser {
|
|
|
584
616
|
|
|
585
617
|
resolvedSheet.xZones =
|
|
586
618
|
SchematicSheetStyleResolver.resolveXZones(resolvedSheet)
|
|
619
|
+
resolvedSheet.yZones =
|
|
620
|
+
SchematicSheetStyleResolver.resolveYZones(resolvedSheet)
|
|
587
621
|
delete resolvedSheet.sheetStyle
|
|
588
622
|
|
|
589
623
|
const title =
|
|
@@ -649,6 +683,11 @@ export class AltiumParser {
|
|
|
649
683
|
busEntries,
|
|
650
684
|
sheetEntries
|
|
651
685
|
})
|
|
686
|
+
AltiumParser.#suppressRedundantSinglePinPowerNetNames(
|
|
687
|
+
pins,
|
|
688
|
+
nets,
|
|
689
|
+
anchoredTexts
|
|
690
|
+
)
|
|
652
691
|
diagnostics.push(...netDiagnostics)
|
|
653
692
|
const qa = SchematicQaReportBuilder.build({
|
|
654
693
|
records: recordIndexAwareRecords,
|
|
@@ -741,6 +780,142 @@ export class AltiumParser {
|
|
|
741
780
|
})
|
|
742
781
|
}
|
|
743
782
|
|
|
783
|
+
/**
|
|
784
|
+
* Hides one-pin owner pin names when a connected power port already labels
|
|
785
|
+
* the same net, preserving the numeric pin endpoint label.
|
|
786
|
+
* @param {{ ownerIndex?: string, name?: string, labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number' }[]} pins
|
|
787
|
+
* @param {{ powerPorts?: { text?: string }[], pins?: { ownerIndex?: string, name?: string, labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number' }[] }[]} nets
|
|
788
|
+
* @param {{ x: number, y: number, text?: string, recordType?: string }[]} texts
|
|
789
|
+
* @returns {void}
|
|
790
|
+
*/
|
|
791
|
+
static #suppressRedundantSinglePinPowerNetNames(pins, nets, texts = []) {
|
|
792
|
+
const ownerPinCounts = new Map()
|
|
793
|
+
|
|
794
|
+
for (const pin of pins || []) {
|
|
795
|
+
const ownerIndex = String(pin.ownerIndex || '').trim()
|
|
796
|
+
|
|
797
|
+
if (!ownerIndex) continue
|
|
798
|
+
|
|
799
|
+
ownerPinCounts.set(
|
|
800
|
+
ownerIndex,
|
|
801
|
+
(ownerPinCounts.get(ownerIndex) || 0) + 1
|
|
802
|
+
)
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
for (const net of nets || []) {
|
|
806
|
+
const powerPortNames = new Set(
|
|
807
|
+
(net.powerPorts || [])
|
|
808
|
+
.map((powerPort) =>
|
|
809
|
+
AltiumParser.#normalizePowerNetLabel(powerPort.text)
|
|
810
|
+
)
|
|
811
|
+
.filter(Boolean)
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
if (!powerPortNames.size) continue
|
|
815
|
+
|
|
816
|
+
for (const pin of net.pins || []) {
|
|
817
|
+
if (
|
|
818
|
+
AltiumParser.#canSuppressSinglePinName(
|
|
819
|
+
pin,
|
|
820
|
+
ownerPinCounts
|
|
821
|
+
) &&
|
|
822
|
+
powerPortNames.has(
|
|
823
|
+
AltiumParser.#normalizePowerNetLabel(pin.name)
|
|
824
|
+
)
|
|
825
|
+
) {
|
|
826
|
+
pin.labelMode = 'number-only'
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const powerPorts = (texts || []).filter(
|
|
832
|
+
(text) => text?.recordType === '17'
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
for (const pin of pins || []) {
|
|
836
|
+
if (!AltiumParser.#canSuppressSinglePinName(pin, ownerPinCounts)) {
|
|
837
|
+
continue
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const pinName = AltiumParser.#normalizePowerNetLabel(pin.name)
|
|
841
|
+
const connectionPoint = AltiumParser.#resolvePinConnectionPoint(pin)
|
|
842
|
+
const matchesDirectPowerPort = powerPorts.some(
|
|
843
|
+
(powerPort) =>
|
|
844
|
+
pinName ===
|
|
845
|
+
AltiumParser.#normalizePowerNetLabel(powerPort.text) &&
|
|
846
|
+
AltiumParser.#pointsAreNear(connectionPoint, powerPort, 2)
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
if (matchesDirectPowerPort) {
|
|
850
|
+
pin.labelMode = 'number-only'
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Returns true when a pin is the only pin on its owner and currently shows
|
|
857
|
+
* a name label.
|
|
858
|
+
* @param {{ ownerIndex?: string, labelMode?: 'hidden' | 'number-only' | 'name-only' | 'name-and-number' }} pin
|
|
859
|
+
* @param {Map<string, number>} ownerPinCounts
|
|
860
|
+
* @returns {boolean}
|
|
861
|
+
*/
|
|
862
|
+
static #canSuppressSinglePinName(pin, ownerPinCounts) {
|
|
863
|
+
const ownerIndex = String(pin?.ownerIndex || '').trim()
|
|
864
|
+
const labelMode = pin?.labelMode || 'name-and-number'
|
|
865
|
+
|
|
866
|
+
return (
|
|
867
|
+
ownerIndex &&
|
|
868
|
+
ownerPinCounts.get(ownerIndex) === 1 &&
|
|
869
|
+
labelMode !== 'hidden' &&
|
|
870
|
+
labelMode !== 'number-only'
|
|
871
|
+
)
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Resolves the outer electrical endpoint for a normalized schematic pin.
|
|
876
|
+
* @param {{ x: number, y: number, length: number, orientation: 'left' | 'right' | 'top' | 'bottom' }} pin
|
|
877
|
+
* @returns {{ x: number, y: number }}
|
|
878
|
+
*/
|
|
879
|
+
static #resolvePinConnectionPoint(pin) {
|
|
880
|
+
switch (pin.orientation) {
|
|
881
|
+
case 'right':
|
|
882
|
+
return { x: pin.x + pin.length, y: pin.y }
|
|
883
|
+
case 'top':
|
|
884
|
+
return { x: pin.x, y: pin.y + pin.length }
|
|
885
|
+
case 'bottom':
|
|
886
|
+
return { x: pin.x, y: pin.y - pin.length }
|
|
887
|
+
case 'left':
|
|
888
|
+
default:
|
|
889
|
+
return { x: pin.x - pin.length, y: pin.y }
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Returns true when two schematic points are close enough to represent the
|
|
895
|
+
* same recovered connection endpoint.
|
|
896
|
+
* @param {{ x: number, y: number }} left
|
|
897
|
+
* @param {{ x: number, y: number }} right
|
|
898
|
+
* @param {number} tolerance
|
|
899
|
+
* @returns {boolean}
|
|
900
|
+
*/
|
|
901
|
+
static #pointsAreNear(left, right, tolerance) {
|
|
902
|
+
return (
|
|
903
|
+
Math.abs(Number(left.x) - Number(right.x)) <= tolerance &&
|
|
904
|
+
Math.abs(Number(left.y) - Number(right.y)) <= tolerance
|
|
905
|
+
)
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Normalizes a power-net label for structural comparisons.
|
|
910
|
+
* @param {string | undefined} label
|
|
911
|
+
* @returns {string}
|
|
912
|
+
*/
|
|
913
|
+
static #normalizePowerNetLabel(label) {
|
|
914
|
+
return String(label || '')
|
|
915
|
+
.trim()
|
|
916
|
+
.toUpperCase()
|
|
917
|
+
}
|
|
918
|
+
|
|
744
919
|
/**
|
|
745
920
|
* Finds a visible text string with a given logical name.
|
|
746
921
|
* @param {{ fields: Record<string, string | string[]> }[]} records
|
|
@@ -784,6 +959,63 @@ export class AltiumParser {
|
|
|
784
959
|
return owners
|
|
785
960
|
}
|
|
786
961
|
|
|
962
|
+
/**
|
|
963
|
+
* Collects symbol owners that draw an inaccessible body around compact
|
|
964
|
+
* internal pins.
|
|
965
|
+
* @param {{ fields: Record<string, string | string[]> }[]} records
|
|
966
|
+
* @returns {Set<string>}
|
|
967
|
+
*/
|
|
968
|
+
static #collectInaccessibleOwnerDrawnPrimitiveOwners(records) {
|
|
969
|
+
const owners = new Set()
|
|
970
|
+
|
|
971
|
+
for (const record of records) {
|
|
972
|
+
const ownerIndex = getField(record.fields, 'OwnerIndex')
|
|
973
|
+
const ownerPartId = getField(record.fields, 'OwnerPartId')
|
|
974
|
+
|
|
975
|
+
if (
|
|
976
|
+
ownerIndex &&
|
|
977
|
+
ownerPartId &&
|
|
978
|
+
ownerPartId !== '-1' &&
|
|
979
|
+
parseBoolean(record.fields.IsNotAccesible) &&
|
|
980
|
+
SchematicComponentOwnerTextResolver.isDisplayModeSelectablePrimitive(
|
|
981
|
+
record.fields
|
|
982
|
+
)
|
|
983
|
+
) {
|
|
984
|
+
owners.add(ownerIndex)
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
return owners
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Collects component owners whose passive two-pin numeric endpoints are
|
|
993
|
+
* intended to remain visible in the schematic body.
|
|
994
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} componentRecords Component records.
|
|
995
|
+
* @param {{ fields: Record<string, string | string[]>, recordIndex?: number }[]} records Indexed schematic records.
|
|
996
|
+
* @returns {Set<string>}
|
|
997
|
+
*/
|
|
998
|
+
static #collectNumericEndpointLabelOwners(componentRecords, records) {
|
|
999
|
+
const owners = new Set()
|
|
1000
|
+
|
|
1001
|
+
for (const componentRecord of componentRecords) {
|
|
1002
|
+
if (
|
|
1003
|
+
parseNumericField(componentRecord.fields, 'ComponentKind') !== 4
|
|
1004
|
+
) {
|
|
1005
|
+
continue
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
for (const ownerIndex of SchematicComponentOwnerTextResolver.resolveOwnerIndexes(
|
|
1009
|
+
componentRecord,
|
|
1010
|
+
records
|
|
1011
|
+
)) {
|
|
1012
|
+
owners.add(ownerIndex)
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
return owners
|
|
1017
|
+
}
|
|
1018
|
+
|
|
787
1019
|
/**
|
|
788
1020
|
* Returns true when one schematic record belongs to the active symbol
|
|
789
1021
|
* display mode for its owner.
|