altium-toolkit 1.1.36 → 1.1.38
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/AGENTS.md +15 -0
- package/package.json +1 -1
- package/src/ui/AltiumScene3dAuthoredBodyAnchorAdapter.mjs +245 -12
- package/src/ui/AltiumScene3dAuthoredConnectorYawPolicy.mjs +176 -0
- package/src/ui/AltiumScene3dBottomSourceHalfTurnPolicy.mjs +93 -0
- package/src/ui/AltiumScene3dDisplayModuleYawPolicy.mjs +284 -0
- package/src/ui/AltiumScene3dExternalPlacementAdapter.mjs +521 -23
- package/src/ui/AltiumScene3dPlacementRotationPolicy.mjs +762 -19
- package/src/ui/AltiumScene3dQfnFootprintDetector.mjs +307 -0
- package/src/ui/AltiumScene3dRepeatedFullFootprintBodyCollapse.mjs +518 -0
- package/src/ui/AltiumScene3dRepeatedModelOwnerRepair.mjs +121 -17
- package/src/ui/AltiumScene3dShapeStackOwnerAdapter.mjs +180 -158
- package/src/ui/AltiumScene3dShapeStackOwnerConflictPolicy.mjs +299 -0
- package/src/ui/PcbScene3dBoardOutlineRefiner.mjs +22 -0
- package/src/ui/PcbScene3dBuilder.mjs +613 -117
- package/src/ui/PcbScene3dModelRegistry.mjs +222 -10
- package/src/ui/PcbScene3dPlacementSideResolver.mjs +262 -22
- package/src/ui/PcbScene3dStaticBodyOwnerPromotion.mjs +87 -2
- package/src/ui/PcbScene3dStaticBodyPadOwnerPromotion.mjs +378 -0
- package/src/ui/PcbScene3dStaticBodyPlacementBuilder.mjs +32 -16
- package/src/ui/PcbScene3dStaticBodySymmetricOwnerPromotion.mjs +1 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PcbScene3dPackageDimensionResolver } from './PcbScene3dPackageDimensionResolver.mjs'
|
|
2
|
+
import { AltiumScene3dRepeatedFullFootprintBodyCollapse } from './AltiumScene3dRepeatedFullFootprintBodyCollapse.mjs'
|
|
2
3
|
|
|
3
4
|
const CONNECTOR_TOKENS = new Set([
|
|
4
5
|
'antenna',
|
|
@@ -12,6 +13,8 @@ const PASSIVE_BODY_PATTERN =
|
|
|
12
13
|
/(?:^|[^a-z0-9])(?:cap|capacitor|res|resistor|ind|inductor|ferrite|bead|lqw|lqg)(?:$|[^a-z0-9])/i
|
|
13
14
|
const COMPACT_IC_BODY_PATTERN =
|
|
14
15
|
/(?:^|[^a-z0-9])(?:u?qfn(?:[-_ ]?\d+)?|dfn(?:[-_ ]?\d+)?|qfp(?:[-_ ]?\d+)?|bga(?:[-_ ]?\d+)?|lga(?:[-_ ]?\d+)?)(?:$|[^a-z0-9])/i
|
|
16
|
+
const MODEL_BOUNDS_CORNER_ORIGIN_PACKAGE_PATTERN =
|
|
17
|
+
/(?:^|[^a-z0-9])(?:[a-z0-9]*qfn[a-z0-9]*|[a-z0-9]*dfn[a-z0-9]*|qfp|lqfp|tqfp|bga|lga)(?:[-_ ]?\d+)?(?:$|[^a-z0-9])/i
|
|
15
18
|
const TIMING_PACKAGE_PATTERN =
|
|
16
19
|
/(?:^|[^a-z0-9])(?:clock|crystal|osc|oscillator|resonator|tcxo|txco|xtal)(?:$|[^a-z0-9])/i
|
|
17
20
|
const TIMING_DESIGNATOR_PATTERN = /^(?:y|xo)\d+[a-z]?$/i
|
|
@@ -52,6 +55,9 @@ export class AltiumScene3dRepeatedModelOwnerRepair {
|
|
|
52
55
|
const components = Array.isArray(documentModel?.pcb?.components)
|
|
53
56
|
? documentModel.pcb.components
|
|
54
57
|
: []
|
|
58
|
+
const pads = Array.isArray(documentModel?.pcb?.pads)
|
|
59
|
+
? documentModel.pcb.pads
|
|
60
|
+
: []
|
|
55
61
|
if (!components.length) {
|
|
56
62
|
return sceneDescription
|
|
57
63
|
}
|
|
@@ -62,15 +68,18 @@ export class AltiumScene3dRepeatedModelOwnerRepair {
|
|
|
62
68
|
component
|
|
63
69
|
])
|
|
64
70
|
)
|
|
65
|
-
const
|
|
66
|
-
(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
const collapsedPlacements =
|
|
72
|
+
AltiumScene3dRepeatedFullFootprintBodyCollapse.apply(
|
|
73
|
+
sceneDescription.externalPlacements,
|
|
74
|
+
componentByDesignator,
|
|
75
|
+
pads
|
|
76
|
+
)
|
|
77
|
+
const placements = collapsedPlacements.map((placement) =>
|
|
78
|
+
AltiumScene3dRepeatedModelOwnerRepair.#withSingleOwnerCenter(
|
|
79
|
+
placement,
|
|
80
|
+
componentByDesignator.get(String(placement?.designator || '')),
|
|
81
|
+
sceneDescription.board
|
|
82
|
+
)
|
|
74
83
|
)
|
|
75
84
|
|
|
76
85
|
for (const group of AltiumScene3dRepeatedModelOwnerRepair.#groups(
|
|
@@ -178,9 +187,13 @@ export class AltiumScene3dRepeatedModelOwnerRepair {
|
|
|
178
187
|
* @returns {object}
|
|
179
188
|
*/
|
|
180
189
|
static #withSingleOwnerCenter(placement, component, board) {
|
|
190
|
+
const projectionSource = String(
|
|
191
|
+
placement?.projection?.source || ''
|
|
192
|
+
).toLowerCase()
|
|
181
193
|
if (
|
|
182
194
|
!component ||
|
|
183
|
-
|
|
195
|
+
(projectionSource !== 'pad-fallback' &&
|
|
196
|
+
projectionSource !== 'model-bounds') ||
|
|
184
197
|
AltiumScene3dRepeatedModelOwnerRepair.#isAuthoredAnchorPlacement(
|
|
185
198
|
placement,
|
|
186
199
|
component
|
|
@@ -204,13 +217,58 @@ export class AltiumScene3dRepeatedModelOwnerRepair {
|
|
|
204
217
|
) {
|
|
205
218
|
return placement
|
|
206
219
|
}
|
|
220
|
+
if (
|
|
221
|
+
projectionSource === 'model-bounds' &&
|
|
222
|
+
!AltiumScene3dRepeatedModelOwnerRepair.#hasModelBoundsCornerSourceOriginOffset(
|
|
223
|
+
placement,
|
|
224
|
+
component,
|
|
225
|
+
offset
|
|
226
|
+
)
|
|
227
|
+
) {
|
|
228
|
+
return placement
|
|
229
|
+
}
|
|
207
230
|
|
|
208
231
|
return AltiumScene3dRepeatedModelOwnerRepair.#withOwner(
|
|
209
232
|
placement,
|
|
210
233
|
component,
|
|
211
234
|
board,
|
|
212
235
|
offset,
|
|
213
|
-
{
|
|
236
|
+
{
|
|
237
|
+
preserveRotation: true,
|
|
238
|
+
carryRenderableSourceOffset: projectionSource !== 'model-bounds'
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Checks whether a model-bounds IC body uses a package-corner source
|
|
245
|
+
* origin rather than an authored mechanical anchor.
|
|
246
|
+
* @param {object} placement Scene placement.
|
|
247
|
+
* @param {object} component PCB component.
|
|
248
|
+
* @param {{ x: number, y: number }} offset Body anchor offset from owner.
|
|
249
|
+
* @returns {boolean}
|
|
250
|
+
*/
|
|
251
|
+
static #hasModelBoundsCornerSourceOriginOffset(
|
|
252
|
+
placement,
|
|
253
|
+
component,
|
|
254
|
+
offset
|
|
255
|
+
) {
|
|
256
|
+
const identityText =
|
|
257
|
+
AltiumScene3dRepeatedModelOwnerRepair.#identityTextForPlacement(
|
|
258
|
+
placement,
|
|
259
|
+
component
|
|
260
|
+
)
|
|
261
|
+
if (!MODEL_BOUNDS_CORNER_ORIGIN_PACKAGE_PATTERN.test(identityText)) {
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const offsetWidth = Math.abs(Number(offset?.x || 0)) * 2
|
|
266
|
+
const offsetDepth = Math.abs(Number(offset?.y || 0)) * 2
|
|
267
|
+
|
|
268
|
+
return AltiumScene3dRepeatedModelOwnerRepair.#matchesCornerOriginBounds(
|
|
269
|
+
offsetWidth,
|
|
270
|
+
offsetDepth,
|
|
271
|
+
placement?.projection?.boundsMil
|
|
214
272
|
)
|
|
215
273
|
}
|
|
216
274
|
|
|
@@ -387,6 +445,35 @@ export class AltiumScene3dRepeatedModelOwnerRepair {
|
|
|
387
445
|
)
|
|
388
446
|
}
|
|
389
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Checks whether doubled owner offsets match any planar pair from measured
|
|
450
|
+
* model bounds. Tilted STEP bodies often expose package height on `depth`
|
|
451
|
+
* and package width/depth on `width` plus `height`.
|
|
452
|
+
* @param {number} offsetWidth Doubled X offset.
|
|
453
|
+
* @param {number} offsetDepth Doubled Y offset.
|
|
454
|
+
* @param {{ width?: number, depth?: number, height?: number } | null | undefined} bounds Model bounds.
|
|
455
|
+
* @returns {boolean}
|
|
456
|
+
*/
|
|
457
|
+
static #matchesCornerOriginBounds(offsetWidth, offsetDepth, bounds) {
|
|
458
|
+
const dimensions = [
|
|
459
|
+
Number(bounds?.width),
|
|
460
|
+
Number(bounds?.depth),
|
|
461
|
+
Number(bounds?.height)
|
|
462
|
+
].filter((dimension) => Number.isFinite(dimension) && dimension > 0)
|
|
463
|
+
|
|
464
|
+
return dimensions.some((width, widthIndex) =>
|
|
465
|
+
dimensions.some(
|
|
466
|
+
(depth, depthIndex) =>
|
|
467
|
+
widthIndex !== depthIndex &&
|
|
468
|
+
AltiumScene3dRepeatedModelOwnerRepair.#matchesCornerOriginSize(
|
|
469
|
+
offsetWidth,
|
|
470
|
+
offsetDepth,
|
|
471
|
+
{ width, depth }
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
|
|
390
477
|
/**
|
|
391
478
|
* Checks whether two dimensions are close enough for source-origin repair.
|
|
392
479
|
* @param {number} actual Actual dimension.
|
|
@@ -656,7 +743,7 @@ export class AltiumScene3dRepeatedModelOwnerRepair {
|
|
|
656
743
|
* @param {object} component Resolved owner.
|
|
657
744
|
* @param {object} board Scene board metadata.
|
|
658
745
|
* @param {{ x: number, y: number }} offset Source-origin offset.
|
|
659
|
-
* @param {{ preserveRotation?: boolean }} [options] Repair options.
|
|
746
|
+
* @param {{ preserveRotation?: boolean, carryRenderableSourceOffset?: boolean }} [options] Repair options.
|
|
660
747
|
* @returns {object}
|
|
661
748
|
*/
|
|
662
749
|
static #withOwner(placement, component, board, offset, options = {}) {
|
|
@@ -671,11 +758,15 @@ export class AltiumScene3dRepeatedModelOwnerRepair {
|
|
|
671
758
|
component?.rotation
|
|
672
759
|
)
|
|
673
760
|
const renderableOffset =
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
761
|
+
options?.carryRenderableSourceOffset === false
|
|
762
|
+
? AltiumScene3dRepeatedModelOwnerRepair.#renderableZOffset(
|
|
763
|
+
placement?.modelTransform
|
|
764
|
+
)
|
|
765
|
+
: AltiumScene3dRepeatedModelOwnerRepair.#renderableOwnerAnchorOffset(
|
|
766
|
+
{ mountSide, rotationDeg },
|
|
767
|
+
offset,
|
|
768
|
+
placement?.modelTransform
|
|
769
|
+
)
|
|
679
770
|
|
|
680
771
|
return {
|
|
681
772
|
...placement,
|
|
@@ -702,6 +793,19 @@ export class AltiumScene3dRepeatedModelOwnerRepair {
|
|
|
702
793
|
}
|
|
703
794
|
}
|
|
704
795
|
|
|
796
|
+
/**
|
|
797
|
+
* Preserves authored model Z offset while suppressing lateral render shift.
|
|
798
|
+
* @param {object | undefined} modelTransform Existing model transform.
|
|
799
|
+
* @returns {{ x: number, y: number, z: number }}
|
|
800
|
+
*/
|
|
801
|
+
static #renderableZOffset(modelTransform) {
|
|
802
|
+
const z = Number(
|
|
803
|
+
modelTransform?.offsetMil?.z ?? modelTransform?.dzMil ?? 0
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
return { x: 0, y: 0, z: Number.isFinite(z) ? z : 0 }
|
|
807
|
+
}
|
|
808
|
+
|
|
705
809
|
/**
|
|
706
810
|
* Converts a board-space source-origin offset into mount-rig local XY.
|
|
707
811
|
* @param {{ mountSide?: string, rotationDeg?: number }} placement Resolved placement fields.
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
/(?:^|[^a-z0-9])(?:clock|crystal|osc|oscillator|resonator|tcxo|txco|xtal)(?:$|[^a-z0-9])/i
|
|
3
|
-
const TIMING_DESIGNATOR_PATTERN = /^(?:y|xo)\d+[a-z]?$/i
|
|
1
|
+
import { AltiumScene3dShapeStackOwnerConflictPolicy } from './AltiumScene3dShapeStackOwnerConflictPolicy.mjs'
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* Keeps authored shape-based sub-bodies grouped under their carrier owner.
|
|
@@ -10,9 +8,8 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
10
8
|
static #EXACT_OWNER_TOLERANCE_MIL = 1
|
|
11
9
|
static #STACK_BODY_RADIUS_MIL = 220
|
|
12
10
|
static #HEIGHT_TOLERANCE_MIL = 0.1
|
|
13
|
-
static #
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
static #OPPOSITE_SIDE_OWNER_DISTANCE_MARGIN_MIL = 10
|
|
12
|
+
static #STACK_BODY_OWNER_CONFLICT_MARGIN_MIL = 5
|
|
16
13
|
/**
|
|
17
14
|
* Applies authored stack ownership to static and external placements.
|
|
18
15
|
* @param {object} sceneDescription Built scene description.
|
|
@@ -55,23 +52,25 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
55
52
|
])
|
|
56
53
|
)
|
|
57
54
|
|
|
55
|
+
const repairedExternalPlacements =
|
|
56
|
+
AltiumScene3dShapeStackOwnerAdapter.#repairExternalPlacements(
|
|
57
|
+
sceneDescription.externalPlacements,
|
|
58
|
+
componentBodies,
|
|
59
|
+
assignments,
|
|
60
|
+
sceneDescription.board,
|
|
61
|
+
components
|
|
62
|
+
)
|
|
63
|
+
|
|
58
64
|
return {
|
|
59
65
|
...sceneDescription,
|
|
60
66
|
components: AltiumScene3dShapeStackOwnerAdapter.#markComponents(
|
|
61
67
|
sceneDescription.components,
|
|
62
68
|
ownerGroupKeys
|
|
63
69
|
),
|
|
64
|
-
externalPlacements:
|
|
65
|
-
AltiumScene3dShapeStackOwnerAdapter.#repairExternalPlacements(
|
|
66
|
-
sceneDescription.externalPlacements,
|
|
67
|
-
componentBodies,
|
|
68
|
-
assignments,
|
|
69
|
-
sceneDescription.board
|
|
70
|
-
),
|
|
70
|
+
externalPlacements: repairedExternalPlacements,
|
|
71
71
|
staticBodyPlacements:
|
|
72
72
|
AltiumScene3dShapeStackOwnerAdapter.#repairStaticPlacements(
|
|
73
73
|
sceneDescription.staticBodyPlacements,
|
|
74
|
-
componentBodies,
|
|
75
74
|
assignments,
|
|
76
75
|
sceneDescription.board
|
|
77
76
|
)
|
|
@@ -104,17 +103,19 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
104
103
|
* @param {object[]} componentBodies Source component-body rows.
|
|
105
104
|
* @param {object[]} assignments Carrier assignments.
|
|
106
105
|
* @param {object | undefined} board Scene board metadata.
|
|
106
|
+
* @param {object[]} components Source components.
|
|
107
107
|
* @returns {object[]}
|
|
108
108
|
*/
|
|
109
109
|
static #repairExternalPlacements(
|
|
110
110
|
placements,
|
|
111
111
|
componentBodies,
|
|
112
112
|
assignments,
|
|
113
|
-
board
|
|
113
|
+
board,
|
|
114
|
+
components
|
|
114
115
|
) {
|
|
115
116
|
const usedComponentBodies = new Set()
|
|
116
117
|
|
|
117
|
-
return (Array.isArray(placements) ? placements : []).
|
|
118
|
+
return (Array.isArray(placements) ? placements : []).flatMap(
|
|
118
119
|
(placement) => {
|
|
119
120
|
const componentBody =
|
|
120
121
|
AltiumScene3dShapeStackOwnerAdapter.#resolveComponentBody(
|
|
@@ -129,18 +130,27 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
129
130
|
const assignment =
|
|
130
131
|
AltiumScene3dShapeStackOwnerAdapter.#assignmentForBody(
|
|
131
132
|
componentBody,
|
|
132
|
-
assignments
|
|
133
|
+
assignments,
|
|
134
|
+
components
|
|
133
135
|
)
|
|
134
136
|
if (!assignment) {
|
|
135
|
-
return
|
|
137
|
+
return AltiumScene3dShapeStackOwnerAdapter.#shouldDropConflictingStackBody(
|
|
138
|
+
componentBody,
|
|
139
|
+
assignments,
|
|
140
|
+
components
|
|
141
|
+
)
|
|
142
|
+
? []
|
|
143
|
+
: [placement]
|
|
136
144
|
}
|
|
137
145
|
|
|
138
|
-
return
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
146
|
+
return [
|
|
147
|
+
AltiumScene3dShapeStackOwnerAdapter.#withOwner(
|
|
148
|
+
placement,
|
|
149
|
+
componentBody,
|
|
150
|
+
assignment,
|
|
151
|
+
board
|
|
152
|
+
)
|
|
153
|
+
]
|
|
144
154
|
}
|
|
145
155
|
)
|
|
146
156
|
}
|
|
@@ -148,17 +158,11 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
148
158
|
/**
|
|
149
159
|
* Repairs carrier static body ownership.
|
|
150
160
|
* @param {object[] | undefined} placements Scene static body placements.
|
|
151
|
-
* @param {object[]} componentBodies Source component-body rows.
|
|
152
161
|
* @param {object[]} assignments Carrier assignments.
|
|
153
162
|
* @param {object | undefined} board Scene board metadata.
|
|
154
163
|
* @returns {object[]}
|
|
155
164
|
*/
|
|
156
|
-
static #repairStaticPlacements(
|
|
157
|
-
placements,
|
|
158
|
-
componentBodies,
|
|
159
|
-
assignments,
|
|
160
|
-
board
|
|
161
|
-
) {
|
|
165
|
+
static #repairStaticPlacements(placements, assignments, board) {
|
|
162
166
|
return (Array.isArray(placements) ? placements : []).map(
|
|
163
167
|
(placement) => {
|
|
164
168
|
const assignment = assignments.find((candidate) =>
|
|
@@ -298,7 +302,9 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
298
302
|
AltiumScene3dShapeStackOwnerAdapter.#isCarrierBase(body)
|
|
299
303
|
)
|
|
300
304
|
const timingComponents = components.filter((component) =>
|
|
301
|
-
|
|
305
|
+
AltiumScene3dShapeStackOwnerConflictPolicy.isTimingComponent(
|
|
306
|
+
component
|
|
307
|
+
)
|
|
302
308
|
)
|
|
303
309
|
const groups =
|
|
304
310
|
AltiumScene3dShapeStackOwnerAdapter.#groupCarrierBases(bases)
|
|
@@ -313,20 +319,26 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
313
319
|
return []
|
|
314
320
|
}
|
|
315
321
|
|
|
316
|
-
const owners =
|
|
317
|
-
.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
322
|
+
const owners =
|
|
323
|
+
AltiumScene3dShapeStackOwnerConflictPolicy.preferredSideOwners(
|
|
324
|
+
group.anchor,
|
|
325
|
+
timingComponents.filter(
|
|
326
|
+
(component) =>
|
|
327
|
+
AltiumScene3dShapeStackOwnerAdapter.#distance(
|
|
328
|
+
group.anchor,
|
|
329
|
+
component
|
|
330
|
+
) <=
|
|
331
|
+
AltiumScene3dShapeStackOwnerAdapter
|
|
332
|
+
.#BASE_OWNER_TOLERANCE_MIL
|
|
333
|
+
),
|
|
334
|
+
AltiumScene3dShapeStackOwnerAdapter
|
|
335
|
+
.#OPPOSITE_SIDE_OWNER_DISTANCE_MARGIN_MIL
|
|
336
|
+
).sort(
|
|
327
337
|
(left, right) =>
|
|
328
|
-
|
|
329
|
-
|
|
338
|
+
AltiumScene3dShapeStackOwnerConflictPolicy.ownerRank(
|
|
339
|
+
right
|
|
340
|
+
) -
|
|
341
|
+
AltiumScene3dShapeStackOwnerConflictPolicy.ownerRank(
|
|
330
342
|
left
|
|
331
343
|
) ||
|
|
332
344
|
String(left?.designator || '').localeCompare(
|
|
@@ -389,12 +401,12 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
389
401
|
return (
|
|
390
402
|
exactOwners.some(
|
|
391
403
|
(component) =>
|
|
392
|
-
!
|
|
404
|
+
!AltiumScene3dShapeStackOwnerConflictPolicy.isTimingComponent(
|
|
393
405
|
component
|
|
394
406
|
)
|
|
395
407
|
) &&
|
|
396
408
|
!exactOwners.some((component) =>
|
|
397
|
-
|
|
409
|
+
AltiumScene3dShapeStackOwnerConflictPolicy.isTimingComponent(
|
|
398
410
|
component
|
|
399
411
|
)
|
|
400
412
|
)
|
|
@@ -452,9 +464,109 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
452
464
|
* Finds a carrier assignment for one positive-height sub-body.
|
|
453
465
|
* @param {object | null} componentBody Source component-body row.
|
|
454
466
|
* @param {object[]} assignments Carrier assignments.
|
|
467
|
+
* @param {object[]} components Source components.
|
|
455
468
|
* @returns {object | null}
|
|
456
469
|
*/
|
|
457
|
-
static #assignmentForBody(componentBody, assignments) {
|
|
470
|
+
static #assignmentForBody(componentBody, assignments, components) {
|
|
471
|
+
return (
|
|
472
|
+
AltiumScene3dShapeStackOwnerAdapter.#assignmentCandidatesForBody(
|
|
473
|
+
componentBody,
|
|
474
|
+
assignments
|
|
475
|
+
)
|
|
476
|
+
.filter((candidate) =>
|
|
477
|
+
AltiumScene3dShapeStackOwnerAdapter.#canAssignStackBodyCandidate(
|
|
478
|
+
componentBody,
|
|
479
|
+
candidate,
|
|
480
|
+
components
|
|
481
|
+
)
|
|
482
|
+
)
|
|
483
|
+
.sort(
|
|
484
|
+
(left, right) =>
|
|
485
|
+
left.heightError - right.heightError ||
|
|
486
|
+
left.distance - right.distance
|
|
487
|
+
)[0]?.assignment || null
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Checks whether a candidate stack owner can claim a raised body.
|
|
493
|
+
* @param {object | null} componentBody Source component-body row.
|
|
494
|
+
* @param {{ assignment: { owner: object } }} candidate Candidate assignment row.
|
|
495
|
+
* @param {object[]} components Source components.
|
|
496
|
+
* @returns {boolean}
|
|
497
|
+
*/
|
|
498
|
+
static #canAssignStackBodyCandidate(componentBody, candidate, components) {
|
|
499
|
+
const hasConflictingOwner =
|
|
500
|
+
AltiumScene3dShapeStackOwnerConflictPolicy.hasConflictingComponentOwner(
|
|
501
|
+
componentBody,
|
|
502
|
+
candidate.assignment.owner,
|
|
503
|
+
components,
|
|
504
|
+
AltiumScene3dShapeStackOwnerAdapter
|
|
505
|
+
.#STACK_BODY_OWNER_CONFLICT_MARGIN_MIL
|
|
506
|
+
)
|
|
507
|
+
if (!hasConflictingOwner) {
|
|
508
|
+
return true
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return (
|
|
512
|
+
AltiumScene3dShapeStackOwnerConflictPolicy.hasStackDetailBodyIdentity(
|
|
513
|
+
componentBody
|
|
514
|
+
) &&
|
|
515
|
+
!AltiumScene3dShapeStackOwnerConflictPolicy.hasExactOppositeSideOwner(
|
|
516
|
+
componentBody,
|
|
517
|
+
candidate.assignment.owner,
|
|
518
|
+
components,
|
|
519
|
+
AltiumScene3dShapeStackOwnerAdapter.#EXACT_OWNER_TOLERANCE_MIL
|
|
520
|
+
)
|
|
521
|
+
)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Checks whether an unassigned positive-height body should be suppressed
|
|
526
|
+
* because it conflicts with an opposite-side owner but has no exact owner
|
|
527
|
+
* of its own.
|
|
528
|
+
* @param {object | null} componentBody Source component-body row.
|
|
529
|
+
* @param {object[]} assignments Carrier assignments.
|
|
530
|
+
* @param {object[]} components Source components.
|
|
531
|
+
* @returns {boolean}
|
|
532
|
+
*/
|
|
533
|
+
static #shouldDropConflictingStackBody(
|
|
534
|
+
componentBody,
|
|
535
|
+
assignments,
|
|
536
|
+
components
|
|
537
|
+
) {
|
|
538
|
+
const conflict =
|
|
539
|
+
AltiumScene3dShapeStackOwnerAdapter.#assignmentCandidatesForBody(
|
|
540
|
+
componentBody,
|
|
541
|
+
assignments
|
|
542
|
+
).find((candidate) =>
|
|
543
|
+
AltiumScene3dShapeStackOwnerConflictPolicy.hasConflictingComponentOwner(
|
|
544
|
+
componentBody,
|
|
545
|
+
candidate.assignment.owner,
|
|
546
|
+
components,
|
|
547
|
+
AltiumScene3dShapeStackOwnerAdapter
|
|
548
|
+
.#STACK_BODY_OWNER_CONFLICT_MARGIN_MIL
|
|
549
|
+
)
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
return Boolean(
|
|
553
|
+
conflict &&
|
|
554
|
+
!AltiumScene3dShapeStackOwnerConflictPolicy.hasExactOppositeSideOwner(
|
|
555
|
+
componentBody,
|
|
556
|
+
conflict.assignment.owner,
|
|
557
|
+
components,
|
|
558
|
+
AltiumScene3dShapeStackOwnerAdapter.#EXACT_OWNER_TOLERANCE_MIL
|
|
559
|
+
)
|
|
560
|
+
)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Builds stack assignment candidates for one positive-height body.
|
|
565
|
+
* @param {object | null} componentBody Source component-body row.
|
|
566
|
+
* @param {object[]} assignments Carrier assignments.
|
|
567
|
+
* @returns {{ assignment: object, heightError: number, distance: number }[]}
|
|
568
|
+
*/
|
|
569
|
+
static #assignmentCandidatesForBody(componentBody, assignments) {
|
|
458
570
|
if (
|
|
459
571
|
!componentBody ||
|
|
460
572
|
!AltiumScene3dShapeStackOwnerAdapter.#isShapeBasedBody(
|
|
@@ -462,7 +574,7 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
462
574
|
) ||
|
|
463
575
|
Number(componentBody?.standoffHeightMil || 0) <= 0
|
|
464
576
|
) {
|
|
465
|
-
return
|
|
577
|
+
return []
|
|
466
578
|
}
|
|
467
579
|
|
|
468
580
|
const standoff = Number(componentBody.standoffHeightMil)
|
|
@@ -470,33 +582,26 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
470
582
|
componentBody.positionMil
|
|
471
583
|
)
|
|
472
584
|
|
|
473
|
-
return (
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
assignment.owner
|
|
483
|
-
)
|
|
484
|
-
}))
|
|
485
|
-
.filter(
|
|
486
|
-
(candidate) =>
|
|
487
|
-
candidate.heightError <=
|
|
488
|
-
AltiumScene3dShapeStackOwnerAdapter
|
|
489
|
-
.#HEIGHT_TOLERANCE_MIL &&
|
|
490
|
-
candidate.distance <=
|
|
491
|
-
AltiumScene3dShapeStackOwnerAdapter
|
|
492
|
-
.#STACK_BODY_RADIUS_MIL
|
|
585
|
+
return (Array.isArray(assignments) ? assignments : [])
|
|
586
|
+
.map((assignment) => ({
|
|
587
|
+
assignment,
|
|
588
|
+
heightError: Math.abs(
|
|
589
|
+
Number(assignment.heightMil || 0) - standoff
|
|
590
|
+
),
|
|
591
|
+
distance: AltiumScene3dShapeStackOwnerAdapter.#distance(
|
|
592
|
+
bodyPosition,
|
|
593
|
+
assignment.owner
|
|
493
594
|
)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
595
|
+
}))
|
|
596
|
+
.filter(
|
|
597
|
+
(candidate) =>
|
|
598
|
+
candidate.heightError <=
|
|
599
|
+
AltiumScene3dShapeStackOwnerAdapter
|
|
600
|
+
.#HEIGHT_TOLERANCE_MIL &&
|
|
601
|
+
candidate.distance <=
|
|
602
|
+
AltiumScene3dShapeStackOwnerAdapter
|
|
603
|
+
.#STACK_BODY_RADIUS_MIL
|
|
604
|
+
)
|
|
500
605
|
}
|
|
501
606
|
|
|
502
607
|
/**
|
|
@@ -703,51 +808,6 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
703
808
|
: 0
|
|
704
809
|
}
|
|
705
810
|
|
|
706
|
-
/**
|
|
707
|
-
* Checks whether a component is a timing-package owner.
|
|
708
|
-
* @param {object} component Source component.
|
|
709
|
-
* @returns {boolean}
|
|
710
|
-
*/
|
|
711
|
-
static #isTimingComponent(component) {
|
|
712
|
-
const designator = String(component?.designator || '').trim()
|
|
713
|
-
|
|
714
|
-
return (
|
|
715
|
-
TIMING_DESIGNATOR_PATTERN.test(designator) ||
|
|
716
|
-
TIMING_PACKAGE_PATTERN.test(
|
|
717
|
-
AltiumScene3dShapeStackOwnerAdapter.#timingIdentityText(
|
|
718
|
-
component
|
|
719
|
-
)
|
|
720
|
-
)
|
|
721
|
-
)
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* Scores how strongly a component looks like the fitted stack owner.
|
|
726
|
-
* @param {object} component Source component.
|
|
727
|
-
* @returns {number}
|
|
728
|
-
*/
|
|
729
|
-
static #ownerRank(component) {
|
|
730
|
-
const parameters = Object.values(component?.parameters || {}).filter(
|
|
731
|
-
(value) => String(value || '').trim()
|
|
732
|
-
)
|
|
733
|
-
return (
|
|
734
|
-
parameters.length * 10 +
|
|
735
|
-
(TIMING_DESIGNATOR_PATTERN.test(component?.designator) ? 5 : 0) +
|
|
736
|
-
(TIMING_PACKAGE_PATTERN.test(
|
|
737
|
-
[
|
|
738
|
-
component?.pattern,
|
|
739
|
-
component?.source,
|
|
740
|
-
component?.description,
|
|
741
|
-
component?.provenance?.footprintDescription
|
|
742
|
-
]
|
|
743
|
-
.map((value) => String(value || ''))
|
|
744
|
-
.join(' ')
|
|
745
|
-
)
|
|
746
|
-
? 3
|
|
747
|
-
: 0)
|
|
748
|
-
)
|
|
749
|
-
}
|
|
750
|
-
|
|
751
811
|
/**
|
|
752
812
|
* Checks whether one body row came from shape-based component metadata.
|
|
753
813
|
* @param {object | null | undefined} componentBody Source body row.
|
|
@@ -861,44 +921,6 @@ export class AltiumScene3dShapeStackOwnerAdapter {
|
|
|
861
921
|
.join(' ')
|
|
862
922
|
}
|
|
863
923
|
|
|
864
|
-
/**
|
|
865
|
-
* Builds timing-relevant identity text without material metadata.
|
|
866
|
-
* @param {object} component Source component.
|
|
867
|
-
* @returns {string}
|
|
868
|
-
*/
|
|
869
|
-
static #timingIdentityText(component) {
|
|
870
|
-
return [
|
|
871
|
-
component?.pattern,
|
|
872
|
-
component?.source,
|
|
873
|
-
component?.description,
|
|
874
|
-
component?.provenance?.footprintDescription,
|
|
875
|
-
...AltiumScene3dShapeStackOwnerAdapter.#timingParameterValues(
|
|
876
|
-
component?.parameters
|
|
877
|
-
)
|
|
878
|
-
]
|
|
879
|
-
.map((value) => String(value || ''))
|
|
880
|
-
.join(' ')
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* Returns parameter values whose names describe component identity.
|
|
885
|
-
* @param {object | undefined} parameters Source component parameters.
|
|
886
|
-
* @returns {unknown[]}
|
|
887
|
-
*/
|
|
888
|
-
static #timingParameterValues(parameters) {
|
|
889
|
-
if (!parameters || typeof parameters !== 'object') {
|
|
890
|
-
return []
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
return Object.entries(parameters)
|
|
894
|
-
.filter(([key]) =>
|
|
895
|
-
AltiumScene3dShapeStackOwnerAdapter.#TIMING_PARAMETER_NAME_PATTERN.test(
|
|
896
|
-
String(key || '')
|
|
897
|
-
)
|
|
898
|
-
)
|
|
899
|
-
.map(([, value]) => value)
|
|
900
|
-
}
|
|
901
|
-
|
|
902
924
|
/**
|
|
903
925
|
* Rounds one mil value for stable scene output.
|
|
904
926
|
* @param {number} value Candidate value.
|