altium-toolkit 1.1.31 → 1.1.33
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/AltiumGeneratedLibraryRecordBuilder.mjs +551 -0
- package/src/core/altium/AltiumLibraryRecordBuilder.mjs +31 -1
- package/src/core/altium/AltiumPcbLibExporter.mjs +3 -1
- package/src/core/altium/AltiumSchLibExporter.mjs +1 -1
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +38 -1
- package/src/ui/AltiumScene3dAuthoredBodyAnchorAdapter.mjs +54 -6
- package/src/ui/AltiumScene3dExternalPlacementAdapter.mjs +385 -22
- package/src/ui/AltiumScene3dPlacementRotationPolicy.mjs +568 -4
- package/src/ui/AltiumScene3dRepeatedModelOwnerRepair.mjs +149 -23
- package/src/ui/AltiumScene3dShapeStackOwnerAdapter.mjs +703 -0
- package/src/ui/PcbScene3dBuilder.mjs +431 -22
- package/src/ui/PcbScene3dPlacementSideResolver.mjs +90 -2
- package/src/ui/PcbScene3dStaticBodyPlacementBuilder.mjs +326 -10
|
@@ -6,6 +6,8 @@ export class AltiumScene3dAuthoredBodyAnchorAdapter {
|
|
|
6
6
|
static #MIN_OWNER_OFFSET_MIL = 25
|
|
7
7
|
static #BODY_ANCHOR_TOLERANCE_MIL = 5
|
|
8
8
|
static #AUTHORED_SOURCE = 'authored-body-anchor'
|
|
9
|
+
static #AUTHORED_ANCHOR_IDENTITY_PATTERN =
|
|
10
|
+
/(?:^|[^a-z0-9])(?:antenna|coax|connector|edge|header|jack|mechanical|module|mount|shield|sma|socket|usb)(?:$|[^a-z0-9])/i
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Marks off-anchor explicit Altium body placements so the runtime does not
|
|
@@ -111,25 +113,71 @@ export class AltiumScene3dAuthoredBodyAnchorAdapter {
|
|
|
111
113
|
component,
|
|
112
114
|
board
|
|
113
115
|
)
|
|
116
|
+
const ownerOffset = AltiumScene3dAuthoredBodyAnchorAdapter.#distance(
|
|
117
|
+
bodyPosition,
|
|
118
|
+
ownerPosition
|
|
119
|
+
)
|
|
114
120
|
|
|
115
121
|
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
ownerPosition
|
|
119
|
-
) < AltiumScene3dAuthoredBodyAnchorAdapter.#MIN_OWNER_OFFSET_MIL
|
|
122
|
+
ownerOffset <
|
|
123
|
+
AltiumScene3dAuthoredBodyAnchorAdapter.#MIN_OWNER_OFFSET_MIL
|
|
120
124
|
) {
|
|
121
125
|
return false
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
|
|
128
|
+
if (
|
|
125
129
|
AltiumScene3dAuthoredBodyAnchorAdapter.#distance(
|
|
126
130
|
placementPosition,
|
|
127
131
|
bodyPosition
|
|
128
|
-
)
|
|
132
|
+
) >
|
|
129
133
|
AltiumScene3dAuthoredBodyAnchorAdapter.#BODY_ANCHOR_TOLERANCE_MIL
|
|
134
|
+
) {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return AltiumScene3dAuthoredBodyAnchorAdapter.#hasAuthoredAnchorIdentity(
|
|
139
|
+
placement,
|
|
140
|
+
component
|
|
130
141
|
)
|
|
131
142
|
}
|
|
132
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Checks whether package metadata suggests the offset is an authored
|
|
146
|
+
* connector/mechanical anchor instead of a package source-origin bias.
|
|
147
|
+
* @param {object} placement External placement.
|
|
148
|
+
* @param {object} component Matched scene component.
|
|
149
|
+
* @returns {boolean}
|
|
150
|
+
*/
|
|
151
|
+
static #hasAuthoredAnchorIdentity(placement, component) {
|
|
152
|
+
return AltiumScene3dAuthoredBodyAnchorAdapter.#AUTHORED_ANCHOR_IDENTITY_PATTERN.test(
|
|
153
|
+
AltiumScene3dAuthoredBodyAnchorAdapter.#identityText(
|
|
154
|
+
placement,
|
|
155
|
+
component
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Builds searchable package metadata for anchor-preservation checks.
|
|
162
|
+
* @param {object} placement External placement.
|
|
163
|
+
* @param {object} component Matched scene component.
|
|
164
|
+
* @returns {string}
|
|
165
|
+
*/
|
|
166
|
+
static #identityText(placement, component) {
|
|
167
|
+
return [
|
|
168
|
+
placement?.designator,
|
|
169
|
+
placement?.externalModel?.name,
|
|
170
|
+
placement?.externalModel?.relativePath,
|
|
171
|
+
component?.pattern,
|
|
172
|
+
component?.source,
|
|
173
|
+
component?.description,
|
|
174
|
+
component?.body?.family,
|
|
175
|
+
...Object.values(component?.parameters || {})
|
|
176
|
+
]
|
|
177
|
+
.map((value) => String(value || ''))
|
|
178
|
+
.join(' ')
|
|
179
|
+
}
|
|
180
|
+
|
|
133
181
|
/**
|
|
134
182
|
* Marks one placement as authored-anchor based.
|
|
135
183
|
* @param {object} placement External placement.
|
|
@@ -9,11 +9,14 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
9
9
|
static #EXACT_ANCHOR_TOLERANCE_MIL = 5
|
|
10
10
|
static #NEAR_ANCHOR_TOLERANCE_MIL = 20
|
|
11
11
|
static #FAR_OWNER_DISTANCE_MIL = 100
|
|
12
|
+
static #MODEL_ANCHOR_NEAR_OWNER_TOLERANCE_MIL = 35
|
|
12
13
|
static #DEFAULT_BOARD_THICKNESS_MIL = 63
|
|
13
14
|
static #PASSIVE_BODY_PATTERN =
|
|
14
15
|
/(?:^|[^a-z0-9])(?:cap|capacitor|res|resistor|ind|inductor|ferrite|bead|crystal|xtal|lqw|lqg)(?:$|[^a-z0-9])/i
|
|
15
16
|
static #MECHANICAL_OWNER_PATTERN =
|
|
16
17
|
/(?:^|[^a-z0-9])(?:mech|mechanical|shield|frame|cover|hardware)(?:$|[^a-z0-9])/i
|
|
18
|
+
static #MODEL_ANCHOR_OWNER_PATTERN =
|
|
19
|
+
/(?:pin\s*header|pinheader|header|connector|socket|fpc|flex|jtag)/i
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* Applies exact-anchor repairs to Altium external 3D placements.
|
|
@@ -86,6 +89,14 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
86
89
|
if (!placement?.bodyPositionMil || !placement?.positionMil) {
|
|
87
90
|
return placement
|
|
88
91
|
}
|
|
92
|
+
if (
|
|
93
|
+
AltiumScene3dExternalPlacementAdapter.#isAuthoredShapeStackPlacement(
|
|
94
|
+
placement
|
|
95
|
+
)
|
|
96
|
+
) {
|
|
97
|
+
return placement
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
const componentBody =
|
|
90
101
|
AltiumScene3dExternalPlacementAdapter.#resolveComponentBody(
|
|
91
102
|
placement,
|
|
@@ -175,6 +186,19 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
175
186
|
resolvedComponent
|
|
176
187
|
) || placement.mountSide
|
|
177
188
|
: placement.mountSide
|
|
189
|
+
const shouldCenterResolvedModelAnchor =
|
|
190
|
+
AltiumScene3dExternalPlacementAdapter.#shouldCenterResolvedModelAnchor(
|
|
191
|
+
placement,
|
|
192
|
+
resolvedComponent,
|
|
193
|
+
componentBody,
|
|
194
|
+
pads
|
|
195
|
+
)
|
|
196
|
+
const ownerAnchorOffset = shouldCenterResolvedModelAnchor
|
|
197
|
+
? AltiumScene3dExternalPlacementAdapter.#ownerAnchorOffset(
|
|
198
|
+
placement,
|
|
199
|
+
resolvedComponent
|
|
200
|
+
)
|
|
201
|
+
: null
|
|
178
202
|
const nextPlacement =
|
|
179
203
|
exactComponent || metadataComponent
|
|
180
204
|
? {
|
|
@@ -185,11 +209,23 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
185
209
|
mountSide,
|
|
186
210
|
positionMil: {
|
|
187
211
|
...placement.positionMil,
|
|
212
|
+
...(shouldCenterResolvedModelAnchor
|
|
213
|
+
? AltiumScene3dExternalPlacementAdapter.#ownerPositionMil(
|
|
214
|
+
resolvedComponent,
|
|
215
|
+
board
|
|
216
|
+
)
|
|
217
|
+
: {}),
|
|
188
218
|
z: AltiumScene3dExternalPlacementAdapter.#resolveFaceZ(
|
|
189
219
|
mountSide,
|
|
190
220
|
board
|
|
191
221
|
)
|
|
192
|
-
}
|
|
222
|
+
},
|
|
223
|
+
modelTransform: shouldCenterResolvedModelAnchor
|
|
224
|
+
? AltiumScene3dExternalPlacementAdapter.#withRenderableOwnerAnchorOffset(
|
|
225
|
+
placement,
|
|
226
|
+
ownerAnchorOffset
|
|
227
|
+
)
|
|
228
|
+
: placement.modelTransform
|
|
193
229
|
}
|
|
194
230
|
: placement
|
|
195
231
|
const shouldUseComponentYaw =
|
|
@@ -200,6 +236,17 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
200
236
|
currentHasMetadataAffinity &&
|
|
201
237
|
!currentIsMechanicalOwner
|
|
202
238
|
)
|
|
239
|
+
const rotationContext = {
|
|
240
|
+
placement: nextPlacement,
|
|
241
|
+
component: resolvedComponent,
|
|
242
|
+
componentBody,
|
|
243
|
+
pads,
|
|
244
|
+
isExactAnchoredOwner
|
|
245
|
+
}
|
|
246
|
+
const footprintYaw =
|
|
247
|
+
AltiumScene3dPlacementRotationPolicy.resolveFootprintYaw(
|
|
248
|
+
rotationContext
|
|
249
|
+
)
|
|
203
250
|
|
|
204
251
|
const repairedPlacement =
|
|
205
252
|
AltiumScene3dExternalPlacementAdapter.#repairRotation(
|
|
@@ -207,13 +254,10 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
207
254
|
resolvedComponent,
|
|
208
255
|
componentBody,
|
|
209
256
|
shouldUseComponentYaw,
|
|
210
|
-
AltiumScene3dPlacementRotationPolicy.shouldCorrectYaw(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
pads,
|
|
215
|
-
isExactAnchoredOwner
|
|
216
|
-
})
|
|
257
|
+
AltiumScene3dPlacementRotationPolicy.shouldCorrectYaw(
|
|
258
|
+
rotationContext
|
|
259
|
+
),
|
|
260
|
+
footprintYaw
|
|
217
261
|
)
|
|
218
262
|
|
|
219
263
|
return AltiumScene3dExternalPlacementAdapter.#withContactPadHints(
|
|
@@ -246,6 +290,19 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
246
290
|
}
|
|
247
291
|
}
|
|
248
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Checks whether an earlier adapter already resolved an authored carrier
|
|
295
|
+
* stack placement.
|
|
296
|
+
* @param {object} placement External model placement.
|
|
297
|
+
* @returns {boolean}
|
|
298
|
+
*/
|
|
299
|
+
static #isAuthoredShapeStackPlacement(placement) {
|
|
300
|
+
return (
|
|
301
|
+
String(placement?.projection?.source || '').toLowerCase() ===
|
|
302
|
+
'authored-shape-stack'
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
|
|
249
306
|
/**
|
|
250
307
|
* Resolves board-local SMT pad centers for mixed connector footprints.
|
|
251
308
|
* @param {object} placement External model placement.
|
|
@@ -419,7 +476,11 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
419
476
|
String(placement?.projection?.source || '') ===
|
|
420
477
|
'model-anchor-fallback'
|
|
421
478
|
) {
|
|
422
|
-
return
|
|
479
|
+
return AltiumScene3dExternalPlacementAdapter.#nearestModelAnchorOwner(
|
|
480
|
+
placement,
|
|
481
|
+
componentBody,
|
|
482
|
+
components
|
|
483
|
+
)
|
|
423
484
|
}
|
|
424
485
|
|
|
425
486
|
return AltiumScene3dExternalPlacementAdapter.#nearestAnchorComponent(
|
|
@@ -429,6 +490,294 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
429
490
|
)
|
|
430
491
|
}
|
|
431
492
|
|
|
493
|
+
/**
|
|
494
|
+
* Finds a nearby compatible owner for a model-anchor fallback body.
|
|
495
|
+
* @param {object} placement External model placement.
|
|
496
|
+
* @param {object | null} componentBody Source component body.
|
|
497
|
+
* @param {object[]} components PCB components.
|
|
498
|
+
* @returns {object | null}
|
|
499
|
+
*/
|
|
500
|
+
static #nearestModelAnchorOwner(placement, componentBody, components) {
|
|
501
|
+
const candidates = components
|
|
502
|
+
.map((component) => ({
|
|
503
|
+
component,
|
|
504
|
+
distance: AltiumScene3dExternalPlacementAdapter.#distanceToBody(
|
|
505
|
+
placement,
|
|
506
|
+
component
|
|
507
|
+
)
|
|
508
|
+
}))
|
|
509
|
+
.filter(
|
|
510
|
+
(candidate) =>
|
|
511
|
+
candidate.distance <=
|
|
512
|
+
AltiumScene3dExternalPlacementAdapter
|
|
513
|
+
.#MODEL_ANCHOR_NEAR_OWNER_TOLERANCE_MIL &&
|
|
514
|
+
AltiumScene3dExternalPlacementAdapter.#hasModelAnchorOwnerAffinity(
|
|
515
|
+
placement,
|
|
516
|
+
componentBody,
|
|
517
|
+
candidate.component
|
|
518
|
+
)
|
|
519
|
+
)
|
|
520
|
+
.sort((left, right) => left.distance - right.distance)
|
|
521
|
+
|
|
522
|
+
return candidates[0]?.component || null
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Checks whether a resolved model-anchor fallback should be centered on
|
|
527
|
+
* its nearby compatible owner.
|
|
528
|
+
* @param {object} placement External model placement.
|
|
529
|
+
* @param {object | null | undefined} component Resolved owner component.
|
|
530
|
+
* @param {object | null} componentBody Source component body.
|
|
531
|
+
* @param {object[]} pads Source PCB pads.
|
|
532
|
+
* @returns {boolean}
|
|
533
|
+
*/
|
|
534
|
+
static #shouldCenterResolvedModelAnchor(
|
|
535
|
+
placement,
|
|
536
|
+
component,
|
|
537
|
+
componentBody,
|
|
538
|
+
pads
|
|
539
|
+
) {
|
|
540
|
+
if (
|
|
541
|
+
!component ||
|
|
542
|
+
String(placement?.projection?.source || '') !==
|
|
543
|
+
'model-anchor-fallback'
|
|
544
|
+
) {
|
|
545
|
+
return false
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (
|
|
549
|
+
AltiumScene3dExternalPlacementAdapter.#distanceToBody(
|
|
550
|
+
placement,
|
|
551
|
+
component
|
|
552
|
+
) <=
|
|
553
|
+
AltiumScene3dExternalPlacementAdapter
|
|
554
|
+
.#MODEL_ANCHOR_NEAR_OWNER_TOLERANCE_MIL &&
|
|
555
|
+
AltiumScene3dExternalPlacementAdapter.#hasModelAnchorOwnerAffinity(
|
|
556
|
+
placement,
|
|
557
|
+
componentBody,
|
|
558
|
+
component
|
|
559
|
+
)
|
|
560
|
+
) {
|
|
561
|
+
return true
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return (
|
|
565
|
+
AltiumScene3dExternalPlacementAdapter.#hasPartCodeAffinity(
|
|
566
|
+
placement,
|
|
567
|
+
componentBody,
|
|
568
|
+
component
|
|
569
|
+
) &&
|
|
570
|
+
AltiumScene3dExternalPlacementAdapter.#hasModelAnchorOwnerComponentAffinity(
|
|
571
|
+
component
|
|
572
|
+
) &&
|
|
573
|
+
AltiumScene3dExternalPlacementAdapter.#hasOwnedPadGeometry(
|
|
574
|
+
component,
|
|
575
|
+
pads
|
|
576
|
+
)
|
|
577
|
+
)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Checks whether model-anchor and component identity both describe
|
|
582
|
+
* connector/header-like hardware.
|
|
583
|
+
* @param {object} placement External model placement.
|
|
584
|
+
* @param {object | null} componentBody Source component body.
|
|
585
|
+
* @param {object} component PCB component.
|
|
586
|
+
* @returns {boolean}
|
|
587
|
+
*/
|
|
588
|
+
static #hasModelAnchorOwnerAffinity(placement, componentBody, component) {
|
|
589
|
+
return (
|
|
590
|
+
AltiumScene3dExternalPlacementAdapter.#MODEL_ANCHOR_OWNER_PATTERN.test(
|
|
591
|
+
[
|
|
592
|
+
placement?.designator,
|
|
593
|
+
placement?.externalModel?.name,
|
|
594
|
+
placement?.externalModel?.relativePath,
|
|
595
|
+
componentBody?.identifier,
|
|
596
|
+
componentBody?.name
|
|
597
|
+
]
|
|
598
|
+
.map((value) => String(value || ''))
|
|
599
|
+
.join(' ')
|
|
600
|
+
) &&
|
|
601
|
+
AltiumScene3dExternalPlacementAdapter.#MODEL_ANCHOR_OWNER_PATTERN.test(
|
|
602
|
+
[
|
|
603
|
+
component?.designator,
|
|
604
|
+
component?.pattern,
|
|
605
|
+
component?.source,
|
|
606
|
+
component?.description,
|
|
607
|
+
...Object.values(component?.parameters || {})
|
|
608
|
+
]
|
|
609
|
+
.map((value) => String(value || ''))
|
|
610
|
+
.join(' ')
|
|
611
|
+
)
|
|
612
|
+
)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Checks whether component metadata describes connector/header hardware.
|
|
617
|
+
* @param {object} component PCB component.
|
|
618
|
+
* @returns {boolean}
|
|
619
|
+
*/
|
|
620
|
+
static #hasModelAnchorOwnerComponentAffinity(component) {
|
|
621
|
+
return AltiumScene3dExternalPlacementAdapter.#MODEL_ANCHOR_OWNER_PATTERN.test(
|
|
622
|
+
[
|
|
623
|
+
component?.designator,
|
|
624
|
+
component?.pattern,
|
|
625
|
+
component?.source,
|
|
626
|
+
component?.description,
|
|
627
|
+
...Object.values(component?.parameters || {})
|
|
628
|
+
]
|
|
629
|
+
.map((value) => String(value || ''))
|
|
630
|
+
.join(' ')
|
|
631
|
+
)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Checks whether the owner has measurable pad geometry for centering.
|
|
636
|
+
* @param {object} component PCB component.
|
|
637
|
+
* @param {object[]} pads Source PCB pads.
|
|
638
|
+
* @returns {boolean}
|
|
639
|
+
*/
|
|
640
|
+
static #hasOwnedPadGeometry(component, pads) {
|
|
641
|
+
const componentIndex = Number(component?.componentIndex)
|
|
642
|
+
if (!Number.isFinite(componentIndex)) {
|
|
643
|
+
return false
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return (
|
|
647
|
+
(Array.isArray(pads) ? pads : []).filter(
|
|
648
|
+
(pad) =>
|
|
649
|
+
Number(pad?.componentIndex) === componentIndex &&
|
|
650
|
+
AltiumScene3dExternalPlacementAdapter.#isMeasurablePad(pad)
|
|
651
|
+
).length >= 2
|
|
652
|
+
)
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Checks whether one pad has finite coordinates and non-zero dimensions.
|
|
657
|
+
* @param {object} pad Source PCB pad.
|
|
658
|
+
* @returns {boolean}
|
|
659
|
+
*/
|
|
660
|
+
static #isMeasurablePad(pad) {
|
|
661
|
+
const width = Math.max(
|
|
662
|
+
Number(pad?.sizeTopX || 0),
|
|
663
|
+
Number(pad?.sizeMidX || 0),
|
|
664
|
+
Number(pad?.sizeBottomX || 0)
|
|
665
|
+
)
|
|
666
|
+
const depth = Math.max(
|
|
667
|
+
Number(pad?.sizeTopY || 0),
|
|
668
|
+
Number(pad?.sizeMidY || 0),
|
|
669
|
+
Number(pad?.sizeBottomY || 0)
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
return (
|
|
673
|
+
Number.isFinite(Number(pad?.x)) &&
|
|
674
|
+
Number.isFinite(Number(pad?.y)) &&
|
|
675
|
+
width > 0 &&
|
|
676
|
+
depth > 0
|
|
677
|
+
)
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Resolves a component-centered scene position.
|
|
682
|
+
* @param {object} component PCB component.
|
|
683
|
+
* @param {object | undefined} board Scene board metadata.
|
|
684
|
+
* @returns {{ x: number, y: number }}
|
|
685
|
+
*/
|
|
686
|
+
static #ownerPositionMil(component, board) {
|
|
687
|
+
return {
|
|
688
|
+
x: Number(component?.x || 0) - Number(board?.centerX || 0),
|
|
689
|
+
y: Number(component?.y || 0) - Number(board?.centerY || 0)
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Resolves the source body anchor offset from its component owner.
|
|
695
|
+
* @param {object} placement External model placement.
|
|
696
|
+
* @param {object} component PCB component.
|
|
697
|
+
* @returns {{ x: number, y: number }}
|
|
698
|
+
*/
|
|
699
|
+
static #ownerAnchorOffset(placement, component) {
|
|
700
|
+
return {
|
|
701
|
+
x:
|
|
702
|
+
Number(placement?.bodyPositionMil?.x || 0) -
|
|
703
|
+
Number(component?.x || 0),
|
|
704
|
+
y:
|
|
705
|
+
Number(placement?.bodyPositionMil?.y || 0) -
|
|
706
|
+
Number(component?.y || 0)
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Adds owner anchor provenance and a viewer-applied local model offset.
|
|
712
|
+
* @param {object} placement External model placement.
|
|
713
|
+
* @param {{ x?: number, y?: number } | null} offset Source-origin offset.
|
|
714
|
+
* @returns {object}
|
|
715
|
+
*/
|
|
716
|
+
static #withRenderableOwnerAnchorOffset(placement, offset) {
|
|
717
|
+
const modelTransform = placement?.modelTransform || {}
|
|
718
|
+
const offsetX = Number(offset?.x || 0)
|
|
719
|
+
const offsetY = Number(offset?.y || 0)
|
|
720
|
+
const renderableOffset =
|
|
721
|
+
AltiumScene3dExternalPlacementAdapter.#renderableOwnerAnchorOffset(
|
|
722
|
+
placement,
|
|
723
|
+
{ x: offsetX, y: offsetY }
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
return {
|
|
727
|
+
...(modelTransform || {}),
|
|
728
|
+
offsetMil: {
|
|
729
|
+
...(modelTransform?.offsetMil || {}),
|
|
730
|
+
x: renderableOffset.x,
|
|
731
|
+
y: renderableOffset.y
|
|
732
|
+
},
|
|
733
|
+
ownerAnchorOffsetMil: {
|
|
734
|
+
x: offsetX,
|
|
735
|
+
y: offsetY
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Converts a board-space owner anchor offset into mount-rig local XY.
|
|
742
|
+
* @param {{ mountSide?: string, rotationDeg?: number }} placement External placement.
|
|
743
|
+
* @param {{ x: number, y: number }} offset Board-space owner offset.
|
|
744
|
+
* @returns {{ x: number, y: number }}
|
|
745
|
+
*/
|
|
746
|
+
static #renderableOwnerAnchorOffset(placement, offset) {
|
|
747
|
+
const rotationRad =
|
|
748
|
+
(-AltiumScene3dExternalPlacementAdapter.#normalizeAngle(
|
|
749
|
+
Number(placement?.rotationDeg || 0)
|
|
750
|
+
) *
|
|
751
|
+
Math.PI) /
|
|
752
|
+
180
|
|
753
|
+
const cos = Math.cos(rotationRad)
|
|
754
|
+
const sin = Math.sin(rotationRad)
|
|
755
|
+
const x = Number(offset?.x || 0) * cos - Number(offset?.y || 0) * sin
|
|
756
|
+
const y = Number(offset?.x || 0) * sin + Number(offset?.y || 0) * cos
|
|
757
|
+
|
|
758
|
+
return {
|
|
759
|
+
x: Math.abs(x) < Number.EPSILON ? 0 : Number(x.toFixed(10)),
|
|
760
|
+
y: AltiumScene3dExternalPlacementAdapter.#isBottomPlacement(
|
|
761
|
+
placement
|
|
762
|
+
)
|
|
763
|
+
? Math.abs(y) < Number.EPSILON
|
|
764
|
+
? 0
|
|
765
|
+
: Number((-y).toFixed(10))
|
|
766
|
+
: Math.abs(y) < Number.EPSILON
|
|
767
|
+
? 0
|
|
768
|
+
: Number(y.toFixed(10))
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Checks whether one placement mounts on the board bottom face.
|
|
774
|
+
* @param {{ mountSide?: string } | null | undefined} placement External placement.
|
|
775
|
+
* @returns {boolean}
|
|
776
|
+
*/
|
|
777
|
+
static #isBottomPlacement(placement) {
|
|
778
|
+
return String(placement?.mountSide || '').toLowerCase() === 'bottom'
|
|
779
|
+
}
|
|
780
|
+
|
|
432
781
|
/**
|
|
433
782
|
* Repairs orientation fields once a source body and owner are known.
|
|
434
783
|
* @param {object} placement External model placement.
|
|
@@ -436,6 +785,7 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
436
785
|
* @param {object | null} componentBody Source component body.
|
|
437
786
|
* @param {boolean} useComponentYaw Whether component yaw should override body yaw.
|
|
438
787
|
* @param {boolean} correctPinOneYaw Whether a square IC pin-one correction applies.
|
|
788
|
+
* @param {number | null} footprintYaw Footprint-derived yaw when available.
|
|
439
789
|
* @returns {object}
|
|
440
790
|
*/
|
|
441
791
|
static #repairRotation(
|
|
@@ -443,7 +793,8 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
443
793
|
component,
|
|
444
794
|
componentBody,
|
|
445
795
|
useComponentYaw,
|
|
446
|
-
correctPinOneYaw
|
|
796
|
+
correctPinOneYaw,
|
|
797
|
+
footprintYaw
|
|
447
798
|
) {
|
|
448
799
|
const modelTransform =
|
|
449
800
|
AltiumScene3dExternalPlacementAdapter.#repairModelTransform(
|
|
@@ -462,29 +813,41 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
462
813
|
AltiumScene3dExternalPlacementAdapter.#normalizeAngle(
|
|
463
814
|
Number(placement?.rotationDeg || 0)
|
|
464
815
|
)
|
|
816
|
+
const hasFootprintYaw =
|
|
817
|
+
footprintYaw !== null &&
|
|
818
|
+
footprintYaw !== undefined &&
|
|
819
|
+
Number.isFinite(Number(footprintYaw))
|
|
465
820
|
const baseRotation =
|
|
466
|
-
component &&
|
|
467
|
-
?
|
|
468
|
-
|
|
821
|
+
component && hasFootprintYaw
|
|
822
|
+
? AltiumScene3dExternalPlacementAdapter.#normalizeAngle(
|
|
823
|
+
Number(footprintYaw)
|
|
824
|
+
)
|
|
825
|
+
: component && useComponentYaw && !isGenericPassiveBody
|
|
826
|
+
? componentYaw
|
|
827
|
+
: placementYaw
|
|
469
828
|
const rotationDeg =
|
|
470
829
|
correctPinOneYaw && !isGenericPassiveBody
|
|
471
830
|
? AltiumScene3dExternalPlacementAdapter.#normalizeAngle(
|
|
472
831
|
baseRotation + 180
|
|
473
832
|
)
|
|
474
833
|
: baseRotation
|
|
475
|
-
if (!component || !useComponentYaw || isGenericPassiveBody) {
|
|
476
|
-
return {
|
|
477
|
-
...placement,
|
|
478
|
-
rotationDeg,
|
|
479
|
-
modelTransform
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
834
|
|
|
483
|
-
|
|
835
|
+
const repairedPlacement = {
|
|
484
836
|
...placement,
|
|
485
837
|
rotationDeg,
|
|
486
838
|
modelTransform
|
|
487
839
|
}
|
|
840
|
+
|
|
841
|
+
return modelTransform?.ownerAnchorOffsetMil
|
|
842
|
+
? {
|
|
843
|
+
...repairedPlacement,
|
|
844
|
+
modelTransform:
|
|
845
|
+
AltiumScene3dExternalPlacementAdapter.#withRenderableOwnerAnchorOffset(
|
|
846
|
+
repairedPlacement,
|
|
847
|
+
modelTransform.ownerAnchorOffsetMil
|
|
848
|
+
)
|
|
849
|
+
}
|
|
850
|
+
: repairedPlacement
|
|
488
851
|
}
|
|
489
852
|
|
|
490
853
|
/**
|
|
@@ -617,8 +980,8 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
617
980
|
componentBody
|
|
618
981
|
).some(
|
|
619
982
|
(token) =>
|
|
620
|
-
/[a-z]/u.test(token) &&
|
|
621
983
|
/\d/u.test(token) &&
|
|
984
|
+
(/[a-z]/u.test(token) || token.length >= 6) &&
|
|
622
985
|
haystack.includes(token)
|
|
623
986
|
)
|
|
624
987
|
}
|