altium-toolkit 1.1.2 → 1.1.22
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/docs/api.md +37 -0
- package/docs/model-format.md +18 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +2 -2
- package/docs/testing.md +5 -0
- package/package.json +1 -1
- package/spec/library-scope.md +5 -0
- package/src/core/altium/AltiumLayoutParser.mjs +275 -13
- package/src/core/altium/AltiumLibraryBatchExporter.mjs +206 -0
- package/src/core/altium/AltiumLibraryRecordBuilder.mjs +293 -0
- package/src/core/altium/AltiumParser.mjs +245 -10
- package/src/core/altium/AltiumPcbLibExporter.mjs +101 -0
- package/src/core/altium/AltiumSchLibExporter.mjs +57 -0
- package/src/core/altium/AsciiRecordParser.mjs +43 -11
- package/src/core/altium/PcbComponentKindPolicy.mjs +9 -9
- package/src/core/altium/PcbEmbeddedFontExtractor.mjs +186 -43
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +22 -3
- package/src/core/altium/PcbOutlineRecovery.mjs +94 -0
- 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/SchematicDirectiveParser.mjs +5 -17
- package/src/core/altium/SchematicImageParser.mjs +291 -6
- package/src/core/altium/SchematicMultipartDesignatorNormalizer.mjs +164 -0
- package/src/core/altium/SchematicMultipartOwnerMatcher.mjs +2 -0
- package/src/core/altium/SchematicNoErcSymbolResolver.mjs +36 -0
- package/src/core/altium/SchematicPinParser.mjs +262 -24
- package/src/core/altium/SchematicPrimitiveParser.mjs +116 -8
- package/src/core/altium/SchematicSheetStyleResolver.mjs +38 -0
- package/src/core/altium/SchematicStreamExtractor.mjs +62 -15
- package/src/core/altium/SchematicTextParser.mjs +125 -11
- package/src/core/altium/SchematicTextPostProcessor.mjs +146 -102
- package/src/core/altium/SourceBundleExporter.mjs +156 -0
- package/src/core/altium/SourceComponentBundleNormalizer.mjs +295 -0
- package/src/core/altium/SourceComponentClient.mjs +239 -0
- package/src/core/ole/OleCompoundDocumentWriter.mjs +449 -0
- package/src/parser.mjs +8 -0
- package/src/styles/altium-renderers.css +6 -6
- package/src/ui/PcbArcUtils.mjs +19 -2
- package/src/ui/PcbScene3dBuilder.mjs +202 -20
- package/src/ui/PcbScene3dModelRegistry.mjs +28 -18
- package/src/ui/PcbScene3dPlacementSideResolver.mjs +48 -6
- package/src/ui/SchematicColorResolver.mjs +263 -0
- package/src/ui/SchematicContentLayout.mjs +58 -1
- package/src/ui/SchematicDirectiveRenderer.mjs +133 -22
- package/src/ui/SchematicImageRenderer.mjs +125 -10
- package/src/ui/SchematicJunctionRenderer.mjs +1 -1
- package/src/ui/SchematicLineColorResolver.mjs +88 -0
- package/src/ui/SchematicNativeFooterPartitioner.mjs +275 -0
- package/src/ui/SchematicNoteRenderer.mjs +87 -7
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +560 -10
- package/src/ui/SchematicOwnerPinMarkerLineThemer.mjs +155 -0
- package/src/ui/SchematicPinSvgRenderer.mjs +397 -48
- 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 +109 -24
- package/src/ui/SchematicSvgRenderer.mjs +1210 -71
|
@@ -71,9 +71,16 @@ export class PcbScene3dBuilder {
|
|
|
71
71
|
? boardOutline.segments
|
|
72
72
|
: []
|
|
73
73
|
}
|
|
74
|
+
const componentBodyModels = componentBodies.map((componentBody) =>
|
|
75
|
+
PcbScene3dBuilder.#resolveComponentBodyModel(
|
|
76
|
+
componentBody,
|
|
77
|
+
modelRegistry
|
|
78
|
+
)
|
|
79
|
+
)
|
|
74
80
|
const bodyMatches = PcbScene3dBuilder.#resolveComponentBodyMatches(
|
|
75
81
|
componentBodies,
|
|
76
|
-
components
|
|
82
|
+
components,
|
|
83
|
+
componentBodyModels
|
|
77
84
|
)
|
|
78
85
|
const topSilkscreen = PcbScene3dBuilder.#buildSilkscreenSide(
|
|
79
86
|
primitiveLayers,
|
|
@@ -127,11 +134,11 @@ export class PcbScene3dBuilder {
|
|
|
127
134
|
PcbScene3dBuilder.#buildExternalPlacement(
|
|
128
135
|
componentBody,
|
|
129
136
|
bodyMatches[index],
|
|
137
|
+
componentBodyModels[index],
|
|
130
138
|
components,
|
|
131
139
|
pads,
|
|
132
140
|
board,
|
|
133
|
-
thicknessMil
|
|
134
|
-
modelRegistry
|
|
141
|
+
thicknessMil
|
|
135
142
|
)
|
|
136
143
|
)
|
|
137
144
|
.filter(Boolean),
|
|
@@ -210,29 +217,37 @@ export class PcbScene3dBuilder {
|
|
|
210
217
|
}
|
|
211
218
|
}
|
|
212
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Resolves one component-body model through the active registry.
|
|
222
|
+
* @param {{ modelId?: string, checksum?: number | null, name?: string }} componentBody Component body metadata.
|
|
223
|
+
* @param {{ resolveComponentBodyModel?: (componentBody: any) => { origin: string, name: string, format: string, payloadText?: string, sourceStream?: string, relativePath?: string } | null } | null} modelRegistry Model registry.
|
|
224
|
+
* @returns {{ origin: string, name: string, format: string, payloadText?: string, sourceStream?: string, relativePath?: string } | null}
|
|
225
|
+
*/
|
|
226
|
+
static #resolveComponentBodyModel(componentBody, modelRegistry) {
|
|
227
|
+
return modelRegistry?.resolveComponentBodyModel?.(componentBody) || null
|
|
228
|
+
}
|
|
229
|
+
|
|
213
230
|
/**
|
|
214
231
|
* Builds one explicit external-model placement from normalized component
|
|
215
232
|
* body metadata.
|
|
216
233
|
* @param {{ modelId?: string, checksum?: number | null, embedded?: boolean, name?: string, identifier?: string, layer?: string, positionMil?: { x?: number, y?: number }, rotationDeg?: number, modelRotationDeg?: { x?: number, y?: number, z?: number }, dzMil?: number }} componentBody
|
|
217
234
|
* @param {{ designator: string, x: number, y: number, layer?: string, pattern?: string, rotation?: number, height?: number | null } | null} matchedComponent
|
|
235
|
+
* @param {{ origin: string, name: string, format: string, payloadText?: string, sourceStream?: string, relativePath?: string } | null} resolvedModel
|
|
218
236
|
* @param {{ designator: string, x: number, y: number, layer?: string, pattern?: string, source?: string, modelPath?: string }[]} components
|
|
219
237
|
* @param {{ x: number, y: number, sizeTopX?: number, sizeTopY?: number, sizeMidX?: number, sizeMidY?: number, sizeBottomX?: number, sizeBottomY?: number }[]} pads
|
|
220
238
|
* @param {{ centerX: number, centerY: number }} board
|
|
221
239
|
* @param {number} thicknessMil
|
|
222
|
-
* @param {{ resolveComponentBodyModel?: (componentBody: any) => { origin: string, name: string, format: string, payloadText?: string, sourceStream?: string, relativePath?: string } | null } | null} modelRegistry
|
|
223
240
|
* @returns {{ designator: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, bodyPositionMil: { x: number, y: number }, bodyRotationDeg: number, modelTransform: { rotationDeg: { x: number, y: number, z: number }, dzMil: number }, externalModel: { origin: string, name: string, format: string, payloadText?: string, sourceStream?: string, relativePath?: string } } | null}
|
|
224
241
|
*/
|
|
225
242
|
static #buildExternalPlacement(
|
|
226
243
|
componentBody,
|
|
227
244
|
matchedComponent,
|
|
245
|
+
resolvedModel,
|
|
228
246
|
components,
|
|
229
247
|
pads,
|
|
230
248
|
board,
|
|
231
|
-
thicknessMil
|
|
232
|
-
modelRegistry
|
|
249
|
+
thicknessMil
|
|
233
250
|
) {
|
|
234
|
-
const resolvedModel =
|
|
235
|
-
modelRegistry?.resolveComponentBodyModel?.(componentBody) || null
|
|
236
251
|
if (!resolvedModel) {
|
|
237
252
|
return null
|
|
238
253
|
}
|
|
@@ -247,7 +262,8 @@ export class PcbScene3dBuilder {
|
|
|
247
262
|
const mountSide = PcbScene3dPlacementSideResolver.resolvePlacementSide(
|
|
248
263
|
componentBody,
|
|
249
264
|
matchedComponent,
|
|
250
|
-
components
|
|
265
|
+
components,
|
|
266
|
+
board
|
|
251
267
|
)
|
|
252
268
|
const halfBoardThickness = thicknessMil / 2
|
|
253
269
|
const sourcePosition =
|
|
@@ -283,7 +299,9 @@ export class PcbScene3dBuilder {
|
|
|
283
299
|
bodyRotationDeg: Number(componentBody.rotationDeg || 0),
|
|
284
300
|
modelTransform: {
|
|
285
301
|
rotationDeg: modelRotation,
|
|
286
|
-
dzMil:
|
|
302
|
+
dzMil: PcbScene3dBuilder.#resolveComponentBodyVerticalOffset(
|
|
303
|
+
componentBody
|
|
304
|
+
)
|
|
287
305
|
},
|
|
288
306
|
projection: PcbScene3dBuilder.#resolveProjectionDiagnostics(
|
|
289
307
|
componentBody,
|
|
@@ -295,6 +313,22 @@ export class PcbScene3dBuilder {
|
|
|
295
313
|
}
|
|
296
314
|
}
|
|
297
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Resolves the vertical offset that should remain after the viewer seats
|
|
318
|
+
* raw model bounds on the board face.
|
|
319
|
+
* @param {{ dzMil?: number, standoffHeightMil?: number | null }} componentBody Component-body placement metadata.
|
|
320
|
+
* @returns {number}
|
|
321
|
+
*/
|
|
322
|
+
static #resolveComponentBodyVerticalOffset(componentBody) {
|
|
323
|
+
const standoffHeightMil = Number(componentBody?.standoffHeightMil)
|
|
324
|
+
if (Number.isFinite(standoffHeightMil)) {
|
|
325
|
+
return standoffHeightMil < 0 ? standoffHeightMil : 0
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const dzMil = Number(componentBody?.dzMil)
|
|
329
|
+
return Number.isFinite(dzMil) && dzMil < 0 ? dzMil : 0
|
|
330
|
+
}
|
|
331
|
+
|
|
298
332
|
/**
|
|
299
333
|
* Explains which footprint projection source informed one external model.
|
|
300
334
|
* @param {object} componentBody Normalized component body row.
|
|
@@ -420,19 +454,34 @@ export class PcbScene3dBuilder {
|
|
|
420
454
|
* components.
|
|
421
455
|
* @param {{ modelId?: string, name?: string, identifier?: string, positionMil?: { x?: number, y?: number } }[]} componentBodies
|
|
422
456
|
* @param {{ designator: string, x: number, y: number, layer?: string, pattern?: string, source?: string, modelPath?: string }[]} components
|
|
457
|
+
* @param {({ origin: string, name: string, format: string } | null)[]} resolvedBodyModels
|
|
423
458
|
* @returns {({ designator: string, x: number, y: number, layer?: string, pattern?: string, source?: string, modelPath?: string } | null)[]}
|
|
424
459
|
*/
|
|
425
|
-
static #resolveComponentBodyMatches(
|
|
460
|
+
static #resolveComponentBodyMatches(
|
|
461
|
+
componentBodies,
|
|
462
|
+
components,
|
|
463
|
+
resolvedBodyModels
|
|
464
|
+
) {
|
|
426
465
|
const matches = new Array(componentBodies.length).fill(null)
|
|
427
466
|
const assignedBodyIndexes = new Set()
|
|
428
467
|
const assignedComponentIndexes = new Set()
|
|
429
468
|
const closeCandidates = []
|
|
430
469
|
const matchContext = PcbScene3dBuilder.#buildBodyMatchContext(
|
|
431
470
|
componentBodies,
|
|
432
|
-
components
|
|
471
|
+
components,
|
|
472
|
+
resolvedBodyModels
|
|
433
473
|
)
|
|
434
474
|
|
|
435
475
|
componentBodies.forEach((componentBody, bodyIndex) => {
|
|
476
|
+
if (
|
|
477
|
+
!PcbScene3dBuilder.#isResolvableComponentBody(
|
|
478
|
+
resolvedBodyModels,
|
|
479
|
+
bodyIndex
|
|
480
|
+
)
|
|
481
|
+
) {
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
|
|
436
485
|
components.forEach((component, componentIndex) => {
|
|
437
486
|
const distance =
|
|
438
487
|
PcbScene3dBuilder.#distanceBetweenBodyAndComponent(
|
|
@@ -449,9 +498,31 @@ export class PcbScene3dBuilder {
|
|
|
449
498
|
distance
|
|
450
499
|
)
|
|
451
500
|
) {
|
|
501
|
+
const affinityScore =
|
|
502
|
+
PcbScene3dPlacementSideResolver.scoreBodyComponentAffinity(
|
|
503
|
+
componentBody,
|
|
504
|
+
component
|
|
505
|
+
)
|
|
506
|
+
const sideCompatible =
|
|
507
|
+
PcbScene3dBuilder.#isBodyComponentSideCompatible(
|
|
508
|
+
componentBody,
|
|
509
|
+
component
|
|
510
|
+
)
|
|
511
|
+
const precise =
|
|
512
|
+
PcbScene3dBuilder.#isPreciseBodyComponentDistance(
|
|
513
|
+
distance
|
|
514
|
+
)
|
|
515
|
+
|
|
452
516
|
closeCandidates.push({
|
|
453
517
|
bodyIndex,
|
|
454
518
|
componentIndex,
|
|
519
|
+
affinityScore,
|
|
520
|
+
preciseOwnerScore:
|
|
521
|
+
precise && (sideCompatible || affinityScore > 0)
|
|
522
|
+
? 1
|
|
523
|
+
: 0,
|
|
524
|
+
sideAffinityScore:
|
|
525
|
+
sideCompatible && affinityScore > 0 ? 1 : 0,
|
|
455
526
|
distance
|
|
456
527
|
})
|
|
457
528
|
}
|
|
@@ -459,7 +530,13 @@ export class PcbScene3dBuilder {
|
|
|
459
530
|
})
|
|
460
531
|
|
|
461
532
|
closeCandidates
|
|
462
|
-
.sort(
|
|
533
|
+
.sort(
|
|
534
|
+
(left, right) =>
|
|
535
|
+
right.preciseOwnerScore - left.preciseOwnerScore ||
|
|
536
|
+
right.sideAffinityScore - left.sideAffinityScore ||
|
|
537
|
+
right.affinityScore - left.affinityScore ||
|
|
538
|
+
left.distance - right.distance
|
|
539
|
+
)
|
|
463
540
|
.forEach(({ bodyIndex, componentIndex }) => {
|
|
464
541
|
if (
|
|
465
542
|
assignedBodyIndexes.has(bodyIndex) ||
|
|
@@ -475,6 +552,15 @@ export class PcbScene3dBuilder {
|
|
|
475
552
|
|
|
476
553
|
const groupedBodyIndexes = new Map()
|
|
477
554
|
componentBodies.forEach((componentBody, bodyIndex) => {
|
|
555
|
+
if (
|
|
556
|
+
!PcbScene3dBuilder.#isResolvableComponentBody(
|
|
557
|
+
resolvedBodyModels,
|
|
558
|
+
bodyIndex
|
|
559
|
+
)
|
|
560
|
+
) {
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
|
|
478
564
|
const groupKey =
|
|
479
565
|
PcbScene3dPlacementSideResolver.resolveBodyGroupKey(
|
|
480
566
|
componentBody
|
|
@@ -543,14 +629,28 @@ export class PcbScene3dBuilder {
|
|
|
543
629
|
* Builds reusable identity statistics for body/component matching.
|
|
544
630
|
* @param {{ modelId?: string, name?: string, identifier?: string }[]} componentBodies
|
|
545
631
|
* @param {{ pattern?: string, source?: string, modelPath?: string }[]} components
|
|
632
|
+
* @param {({ origin: string, name: string, format: string } | null)[]} resolvedBodyModels
|
|
546
633
|
* @returns {{ bodyGroupCounts: Map<string, number>, candidateComponentCounts: Map<string, number> }}
|
|
547
634
|
*/
|
|
548
|
-
static #buildBodyMatchContext(
|
|
635
|
+
static #buildBodyMatchContext(
|
|
636
|
+
componentBodies,
|
|
637
|
+
components,
|
|
638
|
+
resolvedBodyModels
|
|
639
|
+
) {
|
|
549
640
|
const bodyGroupCounts = new Map()
|
|
550
641
|
const bodyByGroup = new Map()
|
|
551
642
|
const candidateComponentCounts = new Map()
|
|
552
643
|
|
|
553
|
-
|
|
644
|
+
componentBodies.forEach((componentBody, bodyIndex) => {
|
|
645
|
+
if (
|
|
646
|
+
!PcbScene3dBuilder.#isResolvableComponentBody(
|
|
647
|
+
resolvedBodyModels,
|
|
648
|
+
bodyIndex
|
|
649
|
+
)
|
|
650
|
+
) {
|
|
651
|
+
return
|
|
652
|
+
}
|
|
653
|
+
|
|
554
654
|
const groupKey =
|
|
555
655
|
PcbScene3dPlacementSideResolver.resolveBodyGroupKey(
|
|
556
656
|
componentBody
|
|
@@ -562,7 +662,7 @@ export class PcbScene3dBuilder {
|
|
|
562
662
|
if (!bodyByGroup.has(groupKey)) {
|
|
563
663
|
bodyByGroup.set(groupKey, componentBody)
|
|
564
664
|
}
|
|
565
|
-
}
|
|
665
|
+
})
|
|
566
666
|
|
|
567
667
|
bodyByGroup.forEach((componentBody, groupKey) => {
|
|
568
668
|
candidateComponentCounts.set(
|
|
@@ -580,6 +680,91 @@ export class PcbScene3dBuilder {
|
|
|
580
680
|
return { bodyGroupCounts, candidateComponentCounts }
|
|
581
681
|
}
|
|
582
682
|
|
|
683
|
+
/**
|
|
684
|
+
* Returns true when one body row can produce a renderable external model.
|
|
685
|
+
* @param {unknown[]} resolvedBodyModels Resolved body-model entries.
|
|
686
|
+
* @param {number} bodyIndex Body index.
|
|
687
|
+
* @returns {boolean}
|
|
688
|
+
*/
|
|
689
|
+
static #isResolvableComponentBody(resolvedBodyModels, bodyIndex) {
|
|
690
|
+
return Boolean(
|
|
691
|
+
Array.isArray(resolvedBodyModels)
|
|
692
|
+
? resolvedBodyModels[bodyIndex]
|
|
693
|
+
: true
|
|
694
|
+
)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Returns true when the body/component anchors are close enough to be
|
|
699
|
+
* considered an explicit placement match.
|
|
700
|
+
* @param {number} distanceMil Body/component anchor distance in mil.
|
|
701
|
+
* @returns {boolean}
|
|
702
|
+
*/
|
|
703
|
+
static #isPreciseBodyComponentDistance(distanceMil) {
|
|
704
|
+
return (
|
|
705
|
+
Number(distanceMil) <=
|
|
706
|
+
PcbScene3dBuilder.#PRECISE_BODY_MATCH_TOLERANCE_MIL
|
|
707
|
+
)
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Checks whether the authored mechanical layer agrees with the component
|
|
712
|
+
* layer. Unknown sides remain neutral so generic mechanical bodies can
|
|
713
|
+
* still match from distance and identity.
|
|
714
|
+
* @param {{ layer?: string }} componentBody Component-body record.
|
|
715
|
+
* @param {{ layer?: string }} component Component record.
|
|
716
|
+
* @returns {boolean}
|
|
717
|
+
*/
|
|
718
|
+
static #isBodyComponentSideCompatible(componentBody, component) {
|
|
719
|
+
const bodySide = PcbScene3dBuilder.#resolveMechanicalLayerSide(
|
|
720
|
+
componentBody?.layer
|
|
721
|
+
)
|
|
722
|
+
const componentSide = PcbScene3dBuilder.#resolveComponentLayerSide(
|
|
723
|
+
component?.layer
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
return !bodySide || !componentSide || bodySide === componentSide
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Resolves a component layer to a board side.
|
|
731
|
+
* @param {string | undefined} layer Component layer.
|
|
732
|
+
* @returns {'top' | 'bottom' | null}
|
|
733
|
+
*/
|
|
734
|
+
static #resolveComponentLayerSide(layer) {
|
|
735
|
+
const normalized = String(layer || '')
|
|
736
|
+
.trim()
|
|
737
|
+
.toUpperCase()
|
|
738
|
+
|
|
739
|
+
if (!normalized) {
|
|
740
|
+
return null
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (normalized.includes('BOTTOM') || normalized === 'BOT') {
|
|
744
|
+
return 'bottom'
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (normalized.includes('TOP')) {
|
|
748
|
+
return 'top'
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return null
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Resolves common paired Altium mechanical layer numbers to a board side.
|
|
756
|
+
* @param {string | undefined} layer Mechanical layer.
|
|
757
|
+
* @returns {'top' | 'bottom' | null}
|
|
758
|
+
*/
|
|
759
|
+
static #resolveMechanicalLayerSide(layer) {
|
|
760
|
+
const match = String(layer || '').match(/^MECHANICAL\s*(\d+)$/i)
|
|
761
|
+
if (!match) {
|
|
762
|
+
return null
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return Number(match[1]) % 2 === 0 ? 'bottom' : 'top'
|
|
766
|
+
}
|
|
767
|
+
|
|
583
768
|
/**
|
|
584
769
|
* Returns true when a close body/component pair is identity-compatible and
|
|
585
770
|
* the body group can be matched one-to-one to component anchors.
|
|
@@ -595,10 +780,7 @@ export class PcbScene3dBuilder {
|
|
|
595
780
|
matchContext,
|
|
596
781
|
distanceMil
|
|
597
782
|
) {
|
|
598
|
-
if (
|
|
599
|
-
Number(distanceMil) <=
|
|
600
|
-
PcbScene3dBuilder.#PRECISE_BODY_MATCH_TOLERANCE_MIL
|
|
601
|
-
) {
|
|
783
|
+
if (PcbScene3dBuilder.#isPreciseBodyComponentDistance(distanceMil)) {
|
|
602
784
|
return true
|
|
603
785
|
}
|
|
604
786
|
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* Indexes session companion assets for 3D model lookup.
|
|
7
7
|
*/
|
|
8
8
|
export class PcbScene3dModelRegistry {
|
|
9
|
-
/** @type {{ file?: File | Blob | null, name: string, relativePath: string, format: string, normalizedPath: string, normalizedBaseName: string }[]} */
|
|
9
|
+
/** @type {{ file?: File | Blob | null, name: string, relativePath: string, format: string, source: string, normalizedPath: string, normalizedBaseName: string }[]} */
|
|
10
10
|
#modelFiles
|
|
11
11
|
|
|
12
12
|
/** @type {{ id: string, checksum: number | null, name: string, format: string, payloadText: string, sourceStream: string, normalizedId: string, normalizedBaseName: string }[]} */
|
|
13
13
|
#embeddedModels
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* @param {{ file?: File | Blob | null, name: string, relativePath: string, format: string, normalizedPath: string, normalizedBaseName: string }[]} modelFiles
|
|
16
|
+
* @param {{ file?: File | Blob | null, name: string, relativePath: string, format: string, source: string, normalizedPath: string, normalizedBaseName: string }[]} modelFiles
|
|
17
17
|
* @param {{ id: string, checksum: number | null, name: string, format: string, payloadText: string, sourceStream: string, normalizedId: string, normalizedBaseName: string }[]} embeddedModels
|
|
18
18
|
*/
|
|
19
19
|
constructor(modelFiles, embeddedModels) {
|
|
@@ -23,7 +23,7 @@ export class PcbScene3dModelRegistry {
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Creates one model registry from session files.
|
|
26
|
-
* @param {{ name?: string, relativePath?: string }[]} sessionFiles
|
|
26
|
+
* @param {{ name?: string, relativePath?: string, source?: string }[]} sessionFiles
|
|
27
27
|
* @param {{ id?: string, checksum?: number | null, name?: string, format?: string, payloadText?: string, sourceStream?: string }[]} [embeddedModels]
|
|
28
28
|
* @returns {PcbScene3dModelRegistry}
|
|
29
29
|
*/
|
|
@@ -63,8 +63,8 @@ export class PcbScene3dModelRegistry {
|
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* Normalizes one session file into registry metadata.
|
|
66
|
-
* @param {{ name?: string, relativePath?: string }} file
|
|
67
|
-
* @returns {{ file?: File | Blob | null, name: string, relativePath: string, format: string, normalizedPath: string, normalizedBaseName: string } | null}
|
|
66
|
+
* @param {{ name?: string, relativePath?: string, source?: string }} file
|
|
67
|
+
* @returns {{ file?: File | Blob | null, name: string, relativePath: string, format: string, source: string, normalizedPath: string, normalizedBaseName: string } | null}
|
|
68
68
|
*/
|
|
69
69
|
static #normalizeFile(file) {
|
|
70
70
|
const relativePath = String(file?.relativePath || file?.name || '')
|
|
@@ -80,6 +80,7 @@ export class PcbScene3dModelRegistry {
|
|
|
80
80
|
name,
|
|
81
81
|
relativePath,
|
|
82
82
|
format,
|
|
83
|
+
source: String(file?.source || ''),
|
|
83
84
|
normalizedPath:
|
|
84
85
|
PcbScene3dModelRegistry.#normalizeToken(relativePath),
|
|
85
86
|
normalizedBaseName: PcbScene3dModelRegistry.#normalizeToken(
|
|
@@ -122,13 +123,7 @@ export class PcbScene3dModelRegistry {
|
|
|
122
123
|
(file) => file.normalizedPath === normalizedPath
|
|
123
124
|
)
|
|
124
125
|
if (byPath) {
|
|
125
|
-
return
|
|
126
|
-
origin: 'session',
|
|
127
|
-
file: byPath.file,
|
|
128
|
-
name: byPath.name,
|
|
129
|
-
relativePath: byPath.relativePath,
|
|
130
|
-
format: byPath.format
|
|
131
|
-
}
|
|
126
|
+
return PcbScene3dModelRegistry.#sessionModelFromFile(byPath)
|
|
132
127
|
}
|
|
133
128
|
|
|
134
129
|
const fileName =
|
|
@@ -165,7 +160,7 @@ export class PcbScene3dModelRegistry {
|
|
|
165
160
|
/**
|
|
166
161
|
* Resolves one indexed file by normalized basename and format priority.
|
|
167
162
|
* @param {string} normalizedBaseName
|
|
168
|
-
* @returns {{ file?: File | Blob | null, name: string, relativePath: string, format: string } | null}
|
|
163
|
+
* @returns {{ file?: File | Blob | null, name: string, relativePath: string, format: string, source?: string } | null}
|
|
169
164
|
*/
|
|
170
165
|
#resolveByBaseName(normalizedBaseName) {
|
|
171
166
|
if (!normalizedBaseName) {
|
|
@@ -184,13 +179,28 @@ export class PcbScene3dModelRegistry {
|
|
|
184
179
|
return null
|
|
185
180
|
}
|
|
186
181
|
|
|
187
|
-
return
|
|
182
|
+
return PcbScene3dModelRegistry.#sessionModelFromFile(rankedMatches[0])
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Builds public session model metadata from one indexed file row.
|
|
187
|
+
* @param {{ file?: File | Blob | null, name: string, relativePath: string, format: string, source?: string }} file Indexed model file.
|
|
188
|
+
* @returns {{ origin: 'session', file?: File | Blob | null, name: string, relativePath: string, format: string, source?: string }}
|
|
189
|
+
*/
|
|
190
|
+
static #sessionModelFromFile(file) {
|
|
191
|
+
const model = {
|
|
188
192
|
origin: 'session',
|
|
189
|
-
file:
|
|
190
|
-
name:
|
|
191
|
-
relativePath:
|
|
192
|
-
format:
|
|
193
|
+
file: file.file,
|
|
194
|
+
name: file.name,
|
|
195
|
+
relativePath: file.relativePath,
|
|
196
|
+
format: file.format
|
|
193
197
|
}
|
|
198
|
+
const source = String(file.source || '').trim()
|
|
199
|
+
if (source) {
|
|
200
|
+
model.source = source
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return model
|
|
194
204
|
}
|
|
195
205
|
|
|
196
206
|
/**
|
|
@@ -12,15 +12,17 @@ export class PcbScene3dPlacementSideResolver {
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Resolves which board side one explicit model should mount on.
|
|
15
|
-
* @param {{ layer?: string, standoffHeightMil?: number | null, overallHeightMil?: number | null }} componentBody
|
|
15
|
+
* @param {{ layer?: string, positionMil?: { x?: number, y?: number }, standoffHeightMil?: number | null, overallHeightMil?: number | null }} componentBody
|
|
16
16
|
* @param {{ layer?: string } | null} matchedComponent
|
|
17
17
|
* @param {{ layer?: string, pattern?: string, source?: string, modelPath?: string, x?: number, y?: number }[]} components
|
|
18
|
+
* @param {{ minX?: number, minY?: number, widthMil?: number, heightMil?: number } | null} board
|
|
18
19
|
* @returns {'top' | 'bottom'}
|
|
19
20
|
*/
|
|
20
21
|
static resolvePlacementSide(
|
|
21
22
|
componentBody,
|
|
22
23
|
matchedComponent,
|
|
23
|
-
components = []
|
|
24
|
+
components = [],
|
|
25
|
+
board = null
|
|
24
26
|
) {
|
|
25
27
|
const matchedSide =
|
|
26
28
|
PcbScene3dPlacementSideResolver.#resolveComponentLayerSide(
|
|
@@ -32,7 +34,13 @@ export class PcbScene3dPlacementSideResolver {
|
|
|
32
34
|
|
|
33
35
|
const standoffSide =
|
|
34
36
|
PcbScene3dPlacementSideResolver.#resolveStandoffSide(componentBody)
|
|
35
|
-
if (
|
|
37
|
+
if (
|
|
38
|
+
standoffSide &&
|
|
39
|
+
PcbScene3dPlacementSideResolver.#isBodyAnchorInsideBoard(
|
|
40
|
+
componentBody,
|
|
41
|
+
board
|
|
42
|
+
)
|
|
43
|
+
) {
|
|
36
44
|
return standoffSide
|
|
37
45
|
}
|
|
38
46
|
|
|
@@ -45,11 +53,15 @@ export class PcbScene3dPlacementSideResolver {
|
|
|
45
53
|
return nearbySide
|
|
46
54
|
}
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
const mechanicalSide =
|
|
49
57
|
PcbScene3dPlacementSideResolver.#resolveMechanicalLayerSide(
|
|
50
58
|
componentBody?.layer
|
|
51
|
-
)
|
|
52
|
-
)
|
|
59
|
+
)
|
|
60
|
+
if (mechanicalSide) {
|
|
61
|
+
return mechanicalSide
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return standoffSide || 'top'
|
|
53
65
|
}
|
|
54
66
|
|
|
55
67
|
/**
|
|
@@ -148,6 +160,36 @@ export class PcbScene3dPlacementSideResolver {
|
|
|
148
160
|
return Math.abs(standoff) >= threshold ? 'bottom' : null
|
|
149
161
|
}
|
|
150
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Returns true when one body anchor sits inside the normalized board bounds.
|
|
165
|
+
* @param {{ positionMil?: { x?: number, y?: number } } | null} componentBody
|
|
166
|
+
* @param {{ minX?: number, minY?: number, widthMil?: number, heightMil?: number } | null} board
|
|
167
|
+
* @returns {boolean}
|
|
168
|
+
*/
|
|
169
|
+
static #isBodyAnchorInsideBoard(componentBody, board) {
|
|
170
|
+
const x = Number(componentBody?.positionMil?.x)
|
|
171
|
+
const y = Number(componentBody?.positionMil?.y)
|
|
172
|
+
const minX = Number(board?.minX)
|
|
173
|
+
const minY = Number(board?.minY)
|
|
174
|
+
const width = Number(board?.widthMil)
|
|
175
|
+
const height = Number(board?.heightMil)
|
|
176
|
+
|
|
177
|
+
if (
|
|
178
|
+
!Number.isFinite(x) ||
|
|
179
|
+
!Number.isFinite(y) ||
|
|
180
|
+
!Number.isFinite(minX) ||
|
|
181
|
+
!Number.isFinite(minY) ||
|
|
182
|
+
!Number.isFinite(width) ||
|
|
183
|
+
!Number.isFinite(height) ||
|
|
184
|
+
width <= 0 ||
|
|
185
|
+
height <= 0
|
|
186
|
+
) {
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return x >= minX && x <= minX + width && y >= minY && y <= minY + height
|
|
191
|
+
}
|
|
192
|
+
|
|
151
193
|
/**
|
|
152
194
|
* Resolves side from the nearest footprint-compatible component.
|
|
153
195
|
* @param {{ positionMil?: { x?: number, y?: number } } & { name?: string, identifier?: string }} componentBody
|