altium-toolkit 1.1.31 → 1.1.32
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/ui/AltiumScene3dAuthoredBodyAnchorAdapter.mjs +54 -6
- package/src/ui/AltiumScene3dExternalPlacementAdapter.mjs +364 -22
- package/src/ui/AltiumScene3dPlacementRotationPolicy.mjs +568 -4
- package/src/ui/AltiumScene3dRepeatedModelOwnerRepair.mjs +149 -23
- package/src/ui/PcbScene3dBuilder.mjs +297 -5
- package/src/ui/PcbScene3dPlacementSideResolver.mjs +34 -2
package/package.json
CHANGED
|
@@ -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.
|
|
@@ -175,6 +178,19 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
175
178
|
resolvedComponent
|
|
176
179
|
) || placement.mountSide
|
|
177
180
|
: placement.mountSide
|
|
181
|
+
const shouldCenterResolvedModelAnchor =
|
|
182
|
+
AltiumScene3dExternalPlacementAdapter.#shouldCenterResolvedModelAnchor(
|
|
183
|
+
placement,
|
|
184
|
+
resolvedComponent,
|
|
185
|
+
componentBody,
|
|
186
|
+
pads
|
|
187
|
+
)
|
|
188
|
+
const ownerAnchorOffset = shouldCenterResolvedModelAnchor
|
|
189
|
+
? AltiumScene3dExternalPlacementAdapter.#ownerAnchorOffset(
|
|
190
|
+
placement,
|
|
191
|
+
resolvedComponent
|
|
192
|
+
)
|
|
193
|
+
: null
|
|
178
194
|
const nextPlacement =
|
|
179
195
|
exactComponent || metadataComponent
|
|
180
196
|
? {
|
|
@@ -185,11 +201,23 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
185
201
|
mountSide,
|
|
186
202
|
positionMil: {
|
|
187
203
|
...placement.positionMil,
|
|
204
|
+
...(shouldCenterResolvedModelAnchor
|
|
205
|
+
? AltiumScene3dExternalPlacementAdapter.#ownerPositionMil(
|
|
206
|
+
resolvedComponent,
|
|
207
|
+
board
|
|
208
|
+
)
|
|
209
|
+
: {}),
|
|
188
210
|
z: AltiumScene3dExternalPlacementAdapter.#resolveFaceZ(
|
|
189
211
|
mountSide,
|
|
190
212
|
board
|
|
191
213
|
)
|
|
192
|
-
}
|
|
214
|
+
},
|
|
215
|
+
modelTransform: shouldCenterResolvedModelAnchor
|
|
216
|
+
? AltiumScene3dExternalPlacementAdapter.#withRenderableOwnerAnchorOffset(
|
|
217
|
+
placement,
|
|
218
|
+
ownerAnchorOffset
|
|
219
|
+
)
|
|
220
|
+
: placement.modelTransform
|
|
193
221
|
}
|
|
194
222
|
: placement
|
|
195
223
|
const shouldUseComponentYaw =
|
|
@@ -200,6 +228,17 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
200
228
|
currentHasMetadataAffinity &&
|
|
201
229
|
!currentIsMechanicalOwner
|
|
202
230
|
)
|
|
231
|
+
const rotationContext = {
|
|
232
|
+
placement: nextPlacement,
|
|
233
|
+
component: resolvedComponent,
|
|
234
|
+
componentBody,
|
|
235
|
+
pads,
|
|
236
|
+
isExactAnchoredOwner
|
|
237
|
+
}
|
|
238
|
+
const footprintYaw =
|
|
239
|
+
AltiumScene3dPlacementRotationPolicy.resolveFootprintYaw(
|
|
240
|
+
rotationContext
|
|
241
|
+
)
|
|
203
242
|
|
|
204
243
|
const repairedPlacement =
|
|
205
244
|
AltiumScene3dExternalPlacementAdapter.#repairRotation(
|
|
@@ -207,13 +246,10 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
207
246
|
resolvedComponent,
|
|
208
247
|
componentBody,
|
|
209
248
|
shouldUseComponentYaw,
|
|
210
|
-
AltiumScene3dPlacementRotationPolicy.shouldCorrectYaw(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
pads,
|
|
215
|
-
isExactAnchoredOwner
|
|
216
|
-
})
|
|
249
|
+
AltiumScene3dPlacementRotationPolicy.shouldCorrectYaw(
|
|
250
|
+
rotationContext
|
|
251
|
+
),
|
|
252
|
+
footprintYaw
|
|
217
253
|
)
|
|
218
254
|
|
|
219
255
|
return AltiumScene3dExternalPlacementAdapter.#withContactPadHints(
|
|
@@ -419,7 +455,11 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
419
455
|
String(placement?.projection?.source || '') ===
|
|
420
456
|
'model-anchor-fallback'
|
|
421
457
|
) {
|
|
422
|
-
return
|
|
458
|
+
return AltiumScene3dExternalPlacementAdapter.#nearestModelAnchorOwner(
|
|
459
|
+
placement,
|
|
460
|
+
componentBody,
|
|
461
|
+
components
|
|
462
|
+
)
|
|
423
463
|
}
|
|
424
464
|
|
|
425
465
|
return AltiumScene3dExternalPlacementAdapter.#nearestAnchorComponent(
|
|
@@ -429,6 +469,294 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
429
469
|
)
|
|
430
470
|
}
|
|
431
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Finds a nearby compatible owner for a model-anchor fallback body.
|
|
474
|
+
* @param {object} placement External model placement.
|
|
475
|
+
* @param {object | null} componentBody Source component body.
|
|
476
|
+
* @param {object[]} components PCB components.
|
|
477
|
+
* @returns {object | null}
|
|
478
|
+
*/
|
|
479
|
+
static #nearestModelAnchorOwner(placement, componentBody, components) {
|
|
480
|
+
const candidates = components
|
|
481
|
+
.map((component) => ({
|
|
482
|
+
component,
|
|
483
|
+
distance: AltiumScene3dExternalPlacementAdapter.#distanceToBody(
|
|
484
|
+
placement,
|
|
485
|
+
component
|
|
486
|
+
)
|
|
487
|
+
}))
|
|
488
|
+
.filter(
|
|
489
|
+
(candidate) =>
|
|
490
|
+
candidate.distance <=
|
|
491
|
+
AltiumScene3dExternalPlacementAdapter
|
|
492
|
+
.#MODEL_ANCHOR_NEAR_OWNER_TOLERANCE_MIL &&
|
|
493
|
+
AltiumScene3dExternalPlacementAdapter.#hasModelAnchorOwnerAffinity(
|
|
494
|
+
placement,
|
|
495
|
+
componentBody,
|
|
496
|
+
candidate.component
|
|
497
|
+
)
|
|
498
|
+
)
|
|
499
|
+
.sort((left, right) => left.distance - right.distance)
|
|
500
|
+
|
|
501
|
+
return candidates[0]?.component || null
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Checks whether a resolved model-anchor fallback should be centered on
|
|
506
|
+
* its nearby compatible owner.
|
|
507
|
+
* @param {object} placement External model placement.
|
|
508
|
+
* @param {object | null | undefined} component Resolved owner component.
|
|
509
|
+
* @param {object | null} componentBody Source component body.
|
|
510
|
+
* @param {object[]} pads Source PCB pads.
|
|
511
|
+
* @returns {boolean}
|
|
512
|
+
*/
|
|
513
|
+
static #shouldCenterResolvedModelAnchor(
|
|
514
|
+
placement,
|
|
515
|
+
component,
|
|
516
|
+
componentBody,
|
|
517
|
+
pads
|
|
518
|
+
) {
|
|
519
|
+
if (
|
|
520
|
+
!component ||
|
|
521
|
+
String(placement?.projection?.source || '') !==
|
|
522
|
+
'model-anchor-fallback'
|
|
523
|
+
) {
|
|
524
|
+
return false
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (
|
|
528
|
+
AltiumScene3dExternalPlacementAdapter.#distanceToBody(
|
|
529
|
+
placement,
|
|
530
|
+
component
|
|
531
|
+
) <=
|
|
532
|
+
AltiumScene3dExternalPlacementAdapter
|
|
533
|
+
.#MODEL_ANCHOR_NEAR_OWNER_TOLERANCE_MIL &&
|
|
534
|
+
AltiumScene3dExternalPlacementAdapter.#hasModelAnchorOwnerAffinity(
|
|
535
|
+
placement,
|
|
536
|
+
componentBody,
|
|
537
|
+
component
|
|
538
|
+
)
|
|
539
|
+
) {
|
|
540
|
+
return true
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return (
|
|
544
|
+
AltiumScene3dExternalPlacementAdapter.#hasPartCodeAffinity(
|
|
545
|
+
placement,
|
|
546
|
+
componentBody,
|
|
547
|
+
component
|
|
548
|
+
) &&
|
|
549
|
+
AltiumScene3dExternalPlacementAdapter.#hasModelAnchorOwnerComponentAffinity(
|
|
550
|
+
component
|
|
551
|
+
) &&
|
|
552
|
+
AltiumScene3dExternalPlacementAdapter.#hasOwnedPadGeometry(
|
|
553
|
+
component,
|
|
554
|
+
pads
|
|
555
|
+
)
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Checks whether model-anchor and component identity both describe
|
|
561
|
+
* connector/header-like hardware.
|
|
562
|
+
* @param {object} placement External model placement.
|
|
563
|
+
* @param {object | null} componentBody Source component body.
|
|
564
|
+
* @param {object} component PCB component.
|
|
565
|
+
* @returns {boolean}
|
|
566
|
+
*/
|
|
567
|
+
static #hasModelAnchorOwnerAffinity(placement, componentBody, component) {
|
|
568
|
+
return (
|
|
569
|
+
AltiumScene3dExternalPlacementAdapter.#MODEL_ANCHOR_OWNER_PATTERN.test(
|
|
570
|
+
[
|
|
571
|
+
placement?.designator,
|
|
572
|
+
placement?.externalModel?.name,
|
|
573
|
+
placement?.externalModel?.relativePath,
|
|
574
|
+
componentBody?.identifier,
|
|
575
|
+
componentBody?.name
|
|
576
|
+
]
|
|
577
|
+
.map((value) => String(value || ''))
|
|
578
|
+
.join(' ')
|
|
579
|
+
) &&
|
|
580
|
+
AltiumScene3dExternalPlacementAdapter.#MODEL_ANCHOR_OWNER_PATTERN.test(
|
|
581
|
+
[
|
|
582
|
+
component?.designator,
|
|
583
|
+
component?.pattern,
|
|
584
|
+
component?.source,
|
|
585
|
+
component?.description,
|
|
586
|
+
...Object.values(component?.parameters || {})
|
|
587
|
+
]
|
|
588
|
+
.map((value) => String(value || ''))
|
|
589
|
+
.join(' ')
|
|
590
|
+
)
|
|
591
|
+
)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Checks whether component metadata describes connector/header hardware.
|
|
596
|
+
* @param {object} component PCB component.
|
|
597
|
+
* @returns {boolean}
|
|
598
|
+
*/
|
|
599
|
+
static #hasModelAnchorOwnerComponentAffinity(component) {
|
|
600
|
+
return AltiumScene3dExternalPlacementAdapter.#MODEL_ANCHOR_OWNER_PATTERN.test(
|
|
601
|
+
[
|
|
602
|
+
component?.designator,
|
|
603
|
+
component?.pattern,
|
|
604
|
+
component?.source,
|
|
605
|
+
component?.description,
|
|
606
|
+
...Object.values(component?.parameters || {})
|
|
607
|
+
]
|
|
608
|
+
.map((value) => String(value || ''))
|
|
609
|
+
.join(' ')
|
|
610
|
+
)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Checks whether the owner has measurable pad geometry for centering.
|
|
615
|
+
* @param {object} component PCB component.
|
|
616
|
+
* @param {object[]} pads Source PCB pads.
|
|
617
|
+
* @returns {boolean}
|
|
618
|
+
*/
|
|
619
|
+
static #hasOwnedPadGeometry(component, pads) {
|
|
620
|
+
const componentIndex = Number(component?.componentIndex)
|
|
621
|
+
if (!Number.isFinite(componentIndex)) {
|
|
622
|
+
return false
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return (
|
|
626
|
+
(Array.isArray(pads) ? pads : []).filter(
|
|
627
|
+
(pad) =>
|
|
628
|
+
Number(pad?.componentIndex) === componentIndex &&
|
|
629
|
+
AltiumScene3dExternalPlacementAdapter.#isMeasurablePad(pad)
|
|
630
|
+
).length >= 2
|
|
631
|
+
)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Checks whether one pad has finite coordinates and non-zero dimensions.
|
|
636
|
+
* @param {object} pad Source PCB pad.
|
|
637
|
+
* @returns {boolean}
|
|
638
|
+
*/
|
|
639
|
+
static #isMeasurablePad(pad) {
|
|
640
|
+
const width = Math.max(
|
|
641
|
+
Number(pad?.sizeTopX || 0),
|
|
642
|
+
Number(pad?.sizeMidX || 0),
|
|
643
|
+
Number(pad?.sizeBottomX || 0)
|
|
644
|
+
)
|
|
645
|
+
const depth = Math.max(
|
|
646
|
+
Number(pad?.sizeTopY || 0),
|
|
647
|
+
Number(pad?.sizeMidY || 0),
|
|
648
|
+
Number(pad?.sizeBottomY || 0)
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
return (
|
|
652
|
+
Number.isFinite(Number(pad?.x)) &&
|
|
653
|
+
Number.isFinite(Number(pad?.y)) &&
|
|
654
|
+
width > 0 &&
|
|
655
|
+
depth > 0
|
|
656
|
+
)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Resolves a component-centered scene position.
|
|
661
|
+
* @param {object} component PCB component.
|
|
662
|
+
* @param {object | undefined} board Scene board metadata.
|
|
663
|
+
* @returns {{ x: number, y: number }}
|
|
664
|
+
*/
|
|
665
|
+
static #ownerPositionMil(component, board) {
|
|
666
|
+
return {
|
|
667
|
+
x: Number(component?.x || 0) - Number(board?.centerX || 0),
|
|
668
|
+
y: Number(component?.y || 0) - Number(board?.centerY || 0)
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Resolves the source body anchor offset from its component owner.
|
|
674
|
+
* @param {object} placement External model placement.
|
|
675
|
+
* @param {object} component PCB component.
|
|
676
|
+
* @returns {{ x: number, y: number }}
|
|
677
|
+
*/
|
|
678
|
+
static #ownerAnchorOffset(placement, component) {
|
|
679
|
+
return {
|
|
680
|
+
x:
|
|
681
|
+
Number(placement?.bodyPositionMil?.x || 0) -
|
|
682
|
+
Number(component?.x || 0),
|
|
683
|
+
y:
|
|
684
|
+
Number(placement?.bodyPositionMil?.y || 0) -
|
|
685
|
+
Number(component?.y || 0)
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Adds owner anchor provenance and a viewer-applied local model offset.
|
|
691
|
+
* @param {object} placement External model placement.
|
|
692
|
+
* @param {{ x?: number, y?: number } | null} offset Source-origin offset.
|
|
693
|
+
* @returns {object}
|
|
694
|
+
*/
|
|
695
|
+
static #withRenderableOwnerAnchorOffset(placement, offset) {
|
|
696
|
+
const modelTransform = placement?.modelTransform || {}
|
|
697
|
+
const offsetX = Number(offset?.x || 0)
|
|
698
|
+
const offsetY = Number(offset?.y || 0)
|
|
699
|
+
const renderableOffset =
|
|
700
|
+
AltiumScene3dExternalPlacementAdapter.#renderableOwnerAnchorOffset(
|
|
701
|
+
placement,
|
|
702
|
+
{ x: offsetX, y: offsetY }
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
...(modelTransform || {}),
|
|
707
|
+
offsetMil: {
|
|
708
|
+
...(modelTransform?.offsetMil || {}),
|
|
709
|
+
x: renderableOffset.x,
|
|
710
|
+
y: renderableOffset.y
|
|
711
|
+
},
|
|
712
|
+
ownerAnchorOffsetMil: {
|
|
713
|
+
x: offsetX,
|
|
714
|
+
y: offsetY
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Converts a board-space owner anchor offset into mount-rig local XY.
|
|
721
|
+
* @param {{ mountSide?: string, rotationDeg?: number }} placement External placement.
|
|
722
|
+
* @param {{ x: number, y: number }} offset Board-space owner offset.
|
|
723
|
+
* @returns {{ x: number, y: number }}
|
|
724
|
+
*/
|
|
725
|
+
static #renderableOwnerAnchorOffset(placement, offset) {
|
|
726
|
+
const rotationRad =
|
|
727
|
+
(-AltiumScene3dExternalPlacementAdapter.#normalizeAngle(
|
|
728
|
+
Number(placement?.rotationDeg || 0)
|
|
729
|
+
) *
|
|
730
|
+
Math.PI) /
|
|
731
|
+
180
|
|
732
|
+
const cos = Math.cos(rotationRad)
|
|
733
|
+
const sin = Math.sin(rotationRad)
|
|
734
|
+
const x = Number(offset?.x || 0) * cos - Number(offset?.y || 0) * sin
|
|
735
|
+
const y = Number(offset?.x || 0) * sin + Number(offset?.y || 0) * cos
|
|
736
|
+
|
|
737
|
+
return {
|
|
738
|
+
x: Math.abs(x) < Number.EPSILON ? 0 : Number(x.toFixed(10)),
|
|
739
|
+
y: AltiumScene3dExternalPlacementAdapter.#isBottomPlacement(
|
|
740
|
+
placement
|
|
741
|
+
)
|
|
742
|
+
? Math.abs(y) < Number.EPSILON
|
|
743
|
+
? 0
|
|
744
|
+
: Number((-y).toFixed(10))
|
|
745
|
+
: Math.abs(y) < Number.EPSILON
|
|
746
|
+
? 0
|
|
747
|
+
: Number(y.toFixed(10))
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Checks whether one placement mounts on the board bottom face.
|
|
753
|
+
* @param {{ mountSide?: string } | null | undefined} placement External placement.
|
|
754
|
+
* @returns {boolean}
|
|
755
|
+
*/
|
|
756
|
+
static #isBottomPlacement(placement) {
|
|
757
|
+
return String(placement?.mountSide || '').toLowerCase() === 'bottom'
|
|
758
|
+
}
|
|
759
|
+
|
|
432
760
|
/**
|
|
433
761
|
* Repairs orientation fields once a source body and owner are known.
|
|
434
762
|
* @param {object} placement External model placement.
|
|
@@ -436,6 +764,7 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
436
764
|
* @param {object | null} componentBody Source component body.
|
|
437
765
|
* @param {boolean} useComponentYaw Whether component yaw should override body yaw.
|
|
438
766
|
* @param {boolean} correctPinOneYaw Whether a square IC pin-one correction applies.
|
|
767
|
+
* @param {number | null} footprintYaw Footprint-derived yaw when available.
|
|
439
768
|
* @returns {object}
|
|
440
769
|
*/
|
|
441
770
|
static #repairRotation(
|
|
@@ -443,7 +772,8 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
443
772
|
component,
|
|
444
773
|
componentBody,
|
|
445
774
|
useComponentYaw,
|
|
446
|
-
correctPinOneYaw
|
|
775
|
+
correctPinOneYaw,
|
|
776
|
+
footprintYaw
|
|
447
777
|
) {
|
|
448
778
|
const modelTransform =
|
|
449
779
|
AltiumScene3dExternalPlacementAdapter.#repairModelTransform(
|
|
@@ -462,29 +792,41 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
462
792
|
AltiumScene3dExternalPlacementAdapter.#normalizeAngle(
|
|
463
793
|
Number(placement?.rotationDeg || 0)
|
|
464
794
|
)
|
|
795
|
+
const hasFootprintYaw =
|
|
796
|
+
footprintYaw !== null &&
|
|
797
|
+
footprintYaw !== undefined &&
|
|
798
|
+
Number.isFinite(Number(footprintYaw))
|
|
465
799
|
const baseRotation =
|
|
466
|
-
component &&
|
|
467
|
-
?
|
|
468
|
-
|
|
800
|
+
component && hasFootprintYaw
|
|
801
|
+
? AltiumScene3dExternalPlacementAdapter.#normalizeAngle(
|
|
802
|
+
Number(footprintYaw)
|
|
803
|
+
)
|
|
804
|
+
: component && useComponentYaw && !isGenericPassiveBody
|
|
805
|
+
? componentYaw
|
|
806
|
+
: placementYaw
|
|
469
807
|
const rotationDeg =
|
|
470
808
|
correctPinOneYaw && !isGenericPassiveBody
|
|
471
809
|
? AltiumScene3dExternalPlacementAdapter.#normalizeAngle(
|
|
472
810
|
baseRotation + 180
|
|
473
811
|
)
|
|
474
812
|
: baseRotation
|
|
475
|
-
if (!component || !useComponentYaw || isGenericPassiveBody) {
|
|
476
|
-
return {
|
|
477
|
-
...placement,
|
|
478
|
-
rotationDeg,
|
|
479
|
-
modelTransform
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
813
|
|
|
483
|
-
|
|
814
|
+
const repairedPlacement = {
|
|
484
815
|
...placement,
|
|
485
816
|
rotationDeg,
|
|
486
817
|
modelTransform
|
|
487
818
|
}
|
|
819
|
+
|
|
820
|
+
return modelTransform?.ownerAnchorOffsetMil
|
|
821
|
+
? {
|
|
822
|
+
...repairedPlacement,
|
|
823
|
+
modelTransform:
|
|
824
|
+
AltiumScene3dExternalPlacementAdapter.#withRenderableOwnerAnchorOffset(
|
|
825
|
+
repairedPlacement,
|
|
826
|
+
modelTransform.ownerAnchorOffsetMil
|
|
827
|
+
)
|
|
828
|
+
}
|
|
829
|
+
: repairedPlacement
|
|
488
830
|
}
|
|
489
831
|
|
|
490
832
|
/**
|
|
@@ -617,8 +959,8 @@ export class AltiumScene3dExternalPlacementAdapter {
|
|
|
617
959
|
componentBody
|
|
618
960
|
).some(
|
|
619
961
|
(token) =>
|
|
620
|
-
/[a-z]/u.test(token) &&
|
|
621
962
|
/\d/u.test(token) &&
|
|
963
|
+
(/[a-z]/u.test(token) || token.length >= 6) &&
|
|
622
964
|
haystack.includes(token)
|
|
623
965
|
)
|
|
624
966
|
}
|