altium-toolkit 1.1.33 → 1.1.35
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/model-format.md +2 -3
- package/package.json +1 -1
- package/src/core/altium/AltiumLibraryRecordBuilder.mjs +255 -1
- package/src/core/altium/AltiumPcbLibExporter.mjs +6 -1
- package/src/core/altium/PcbComponentBodyPlacementNormalizer.mjs +120 -15
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +14 -3
- package/src/core/altium/PcbShapeBasedBodyGeometryParser.mjs +85 -11
- package/src/core/altium/SourceComponentBundleNormalizer.mjs +31 -0
- package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
- package/src/ui/AltiumScene3dAuthoredBodyAnchorAdapter.mjs +30 -2
- package/src/ui/AltiumScene3dExternalPlacementAdapter.mjs +6 -2
- package/src/ui/AltiumScene3dPlacementRotationPolicy.mjs +44 -0
- package/src/ui/AltiumScene3dRepeatedModelOwnerRepair.mjs +171 -6
- package/src/ui/AltiumScene3dShapeStackOwnerAdapter.mjs +82 -3
- package/src/ui/AltiumScene3dTwoRowFootprintDetector.mjs +90 -0
- package/src/ui/PcbScene3dBuilder.mjs +470 -20
- package/src/ui/PcbScene3dCopperRegionDetailBuilder.mjs +280 -0
- package/src/ui/PcbScene3dModelRegistry.mjs +16 -0
- package/src/ui/PcbScene3dPackageDimensionResolver.mjs +107 -0
- package/src/ui/PcbScene3dPackages.mjs +15 -3
- package/src/ui/PcbScene3dPadYawResolver.mjs +200 -0
- package/src/ui/PcbScene3dStaticBodyPlacementBuilder.mjs +335 -29
- package/src/ui/PcbScene3dStaticBodyRecovery.mjs +891 -0
- package/src/ui/PcbScene3dStaticBodySelectionKeyBuilder.mjs +358 -0
- package/src/ui/PcbScene3dStaticBodySymmetryRecovery.mjs +876 -0
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
4
|
|
|
5
5
|
import { PcbScene3dPlacementSideResolver } from './PcbScene3dPlacementSideResolver.mjs'
|
|
6
|
+
import { PcbScene3dStaticBodyRecovery } from './PcbScene3dStaticBodyRecovery.mjs'
|
|
7
|
+
import { PcbScene3dStaticBodySelectionKeyBuilder } from './PcbScene3dStaticBodySelectionKeyBuilder.mjs'
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Builds scene placements for static shape-based 3D bodies.
|
|
@@ -12,8 +14,11 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
12
14
|
static #UNMATCHED_BODY_MIN_OVERHANG_MIL = 150
|
|
13
15
|
static #UNMATCHED_BODY_MAX_OVERHANG_MIL = 600
|
|
14
16
|
static #OWNER_AFFINITY_DISTANCE_MIL = 600
|
|
17
|
+
static #OWNER_BOUNDS_TOLERANCE_MIL = 20
|
|
15
18
|
static #OWNER_EXACT_DISTANCE_MIL = 5
|
|
16
19
|
static #OWNER_EXACT_MAX_SPAN_MIL = 500
|
|
20
|
+
static #SOURCE_COORDINATE_MIRROR_TOLERANCE_MIL = 5
|
|
21
|
+
static #SOURCE_COORDINATE_MIRROR_MIN_OFFSET_MIL = 20
|
|
17
22
|
static #GENERIC_MECHANICAL_IDENTITY_TOKENS = new Set([
|
|
18
23
|
'can',
|
|
19
24
|
'cover',
|
|
@@ -34,7 +39,7 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
34
39
|
* @param {{ x: number, y: number, sizeTopX?: number, sizeTopY?: number, sizeMidX?: number, sizeMidY?: number, sizeBottomX?: number, sizeBottomY?: number }[]} pads Pads.
|
|
35
40
|
* @param {{ centerX: number, centerY: number, minX?: number, minY?: number, widthMil?: number, heightMil?: number }} board Board.
|
|
36
41
|
* @param {number} thicknessMil Board thickness.
|
|
37
|
-
* @returns {{ designator: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, bodyPositionMil: { x: number, y: number }, geometry: object }[]}
|
|
42
|
+
* @returns {{ designator: string, selectionKey: string, sourceIdentityKey: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, bodyPositionMil: { x: number, y: number }, geometry: object }[]}
|
|
38
43
|
*/
|
|
39
44
|
static build(
|
|
40
45
|
componentBodies,
|
|
@@ -44,9 +49,14 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
44
49
|
board,
|
|
45
50
|
thicknessMil
|
|
46
51
|
) {
|
|
47
|
-
|
|
52
|
+
const recoveredBodies = PcbScene3dStaticBodyRecovery.recover(
|
|
53
|
+
componentBodies,
|
|
54
|
+
bodyMatches
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const placementRows = recoveredBodies
|
|
48
58
|
.map((componentBody, index) =>
|
|
49
|
-
PcbScene3dStaticBodyPlacementBuilder.#
|
|
59
|
+
PcbScene3dStaticBodyPlacementBuilder.#buildPlacementRow(
|
|
50
60
|
componentBody,
|
|
51
61
|
bodyMatches?.[index] || null,
|
|
52
62
|
components,
|
|
@@ -55,6 +65,8 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
55
65
|
)
|
|
56
66
|
)
|
|
57
67
|
.filter(Boolean)
|
|
68
|
+
|
|
69
|
+
return PcbScene3dStaticBodySelectionKeyBuilder.assign(placementRows)
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
/**
|
|
@@ -66,6 +78,38 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
66
78
|
return Boolean(geometry && geometry.status === 'complete')
|
|
67
79
|
}
|
|
68
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Builds one static body placement row with its matched owner context.
|
|
83
|
+
* @param {{ componentIndex?: number, embedded?: boolean, identifier?: string, name?: string, layer?: string, positionMil?: { x?: number, y?: number }, rotationDeg?: number, standoffHeightMil?: number | null, overallHeightMil?: number | null, bodyOpacity?: number | string, staticGeometry?: object }} componentBody Component body.
|
|
84
|
+
* @param {{ designator: string, x: number, y: number, layer?: string, pattern?: string, rotation?: number, height?: number | null } | null} matchedComponent Matched component.
|
|
85
|
+
* @param {{ designator: string, x: number, y: number, layer?: string, pattern?: string, source?: string, modelPath?: string }[]} components Components.
|
|
86
|
+
* @param {{ centerX: number, centerY: number, minX?: number, minY?: number, widthMil?: number, heightMil?: number }} board Board.
|
|
87
|
+
* @param {number} thicknessMil Board thickness.
|
|
88
|
+
* @returns {{ placement: { designator: string, sourceIdentityKey: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, bodyPositionMil: { x: number, y: number }, geometry: object }, matchedComponent: object | null } | null}
|
|
89
|
+
*/
|
|
90
|
+
static #buildPlacementRow(
|
|
91
|
+
componentBody,
|
|
92
|
+
matchedComponent,
|
|
93
|
+
components,
|
|
94
|
+
board,
|
|
95
|
+
thicknessMil
|
|
96
|
+
) {
|
|
97
|
+
const placement = PcbScene3dStaticBodyPlacementBuilder.#buildPlacement(
|
|
98
|
+
componentBody,
|
|
99
|
+
matchedComponent,
|
|
100
|
+
components,
|
|
101
|
+
board,
|
|
102
|
+
thicknessMil
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return placement
|
|
106
|
+
? {
|
|
107
|
+
placement,
|
|
108
|
+
matchedComponent
|
|
109
|
+
}
|
|
110
|
+
: null
|
|
111
|
+
}
|
|
112
|
+
|
|
69
113
|
/**
|
|
70
114
|
* Builds one static shape-body scene placement.
|
|
71
115
|
* @param {{ componentIndex?: number, embedded?: boolean, identifier?: string, name?: string, layer?: string, positionMil?: { x?: number, y?: number }, rotationDeg?: number, standoffHeightMil?: number | null, overallHeightMil?: number | null, bodyOpacity?: number | string, staticGeometry?: object }} componentBody Component body.
|
|
@@ -73,7 +117,7 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
73
117
|
* @param {{ designator: string, x: number, y: number, layer?: string, pattern?: string, source?: string, modelPath?: string }[]} components Components.
|
|
74
118
|
* @param {{ centerX: number, centerY: number, minX?: number, minY?: number, widthMil?: number, heightMil?: number }} board Board.
|
|
75
119
|
* @param {number} thicknessMil Board thickness.
|
|
76
|
-
* @returns {{ designator: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, bodyPositionMil: { x: number, y: number }, geometry: object } | null}
|
|
120
|
+
* @returns {{ designator: string, sourceIdentityKey: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, bodyPositionMil: { x: number, y: number }, geometry: object } | null}
|
|
77
121
|
*/
|
|
78
122
|
static #buildPlacement(
|
|
79
123
|
componentBody,
|
|
@@ -82,24 +126,30 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
82
126
|
board,
|
|
83
127
|
thicknessMil
|
|
84
128
|
) {
|
|
85
|
-
const
|
|
129
|
+
const staticGeometry =
|
|
86
130
|
PcbScene3dStaticBodyPlacementBuilder.#normalizeStaticGeometry(
|
|
87
131
|
componentBody,
|
|
88
|
-
componentBody?.staticGeometry
|
|
132
|
+
componentBody?.staticGeometry,
|
|
133
|
+
matchedComponent
|
|
89
134
|
)
|
|
135
|
+
const geometry = staticGeometry.geometry
|
|
90
136
|
|
|
91
137
|
if (
|
|
92
138
|
!PcbScene3dStaticBodyPlacementBuilder.#isCompleteGeometry(geometry)
|
|
93
139
|
) {
|
|
94
140
|
return null
|
|
95
141
|
}
|
|
142
|
+
const sourcePosition =
|
|
143
|
+
staticGeometry.placementCenterMil ||
|
|
144
|
+
PcbScene3dStaticBodyPlacementBuilder.#sourcePosition(componentBody)
|
|
96
145
|
|
|
97
146
|
if (
|
|
98
147
|
!matchedComponent &&
|
|
99
148
|
PcbScene3dStaticBodyPlacementBuilder.#shouldSuppressStaticBody(
|
|
100
149
|
componentBody,
|
|
101
150
|
geometry,
|
|
102
|
-
components
|
|
151
|
+
components,
|
|
152
|
+
sourcePosition
|
|
103
153
|
)
|
|
104
154
|
) {
|
|
105
155
|
return null
|
|
@@ -122,8 +172,6 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
122
172
|
components,
|
|
123
173
|
board
|
|
124
174
|
)
|
|
125
|
-
const sourcePosition =
|
|
126
|
-
PcbScene3dStaticBodyPlacementBuilder.#sourcePosition(componentBody)
|
|
127
175
|
const heightMil =
|
|
128
176
|
PcbScene3dStaticBodyPlacementBuilder.#geometryHeight(geometry)
|
|
129
177
|
const standoffMil = Math.abs(
|
|
@@ -142,6 +190,10 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
142
190
|
String(
|
|
143
191
|
componentBody.identifier || componentBody.name || '3D body'
|
|
144
192
|
),
|
|
193
|
+
sourceIdentityKey:
|
|
194
|
+
PcbScene3dStaticBodyPlacementBuilder.#sourceIdentityKey(
|
|
195
|
+
componentBody
|
|
196
|
+
),
|
|
145
197
|
mountSide,
|
|
146
198
|
rotationDeg: PcbScene3dStaticBodyPlacementBuilder.#normalizeAngle(
|
|
147
199
|
Number(componentBody.rotationDeg || 0) +
|
|
@@ -173,9 +225,10 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
173
225
|
* Converts source-space polygon vertices into render-local geometry.
|
|
174
226
|
* @param {{ positionMil?: { x?: number, y?: number } }} componentBody Component body.
|
|
175
227
|
* @param {object | undefined} geometry Static geometry.
|
|
176
|
-
* @
|
|
228
|
+
* @param {{ x?: number, y?: number } | null} matchedComponent Matched owner component.
|
|
229
|
+
* @returns {{ geometry: object | undefined, placementCenterMil?: { x: number, y: number } }}
|
|
177
230
|
*/
|
|
178
|
-
static #normalizeStaticGeometry(componentBody, geometry) {
|
|
231
|
+
static #normalizeStaticGeometry(componentBody, geometry, matchedComponent) {
|
|
179
232
|
if (
|
|
180
233
|
String(geometry?.kind || '').toLowerCase() !== 'extruded-polygon' ||
|
|
181
234
|
!Array.isArray(geometry?.verticesMil) ||
|
|
@@ -185,24 +238,142 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
185
238
|
geometry.verticesMil
|
|
186
239
|
)
|
|
187
240
|
) {
|
|
188
|
-
return geometry
|
|
241
|
+
return { geometry }
|
|
189
242
|
}
|
|
190
243
|
|
|
191
244
|
const center =
|
|
192
245
|
PcbScene3dStaticBodyPlacementBuilder.#polygonBoundsCenter(
|
|
193
246
|
geometry.verticesMil
|
|
194
247
|
)
|
|
248
|
+
const mirror =
|
|
249
|
+
PcbScene3dStaticBodyPlacementBuilder.#sourceCoordinateMirror(
|
|
250
|
+
componentBody,
|
|
251
|
+
center,
|
|
252
|
+
matchedComponent
|
|
253
|
+
)
|
|
254
|
+
const placementCenter = mirror?.placementCenterMil || center
|
|
195
255
|
|
|
196
256
|
return {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
257
|
+
placementCenterMil: placementCenter,
|
|
258
|
+
geometry: {
|
|
259
|
+
...geometry,
|
|
260
|
+
verticesMil: geometry.verticesMil.map((vertex) => {
|
|
261
|
+
const normalizedVertex =
|
|
262
|
+
PcbScene3dStaticBodyPlacementBuilder.#normalizeVertex(
|
|
263
|
+
vertex,
|
|
264
|
+
mirror
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
x: PcbScene3dStaticBodyPlacementBuilder.#roundMil(
|
|
269
|
+
normalizedVertex.x - placementCenter.x
|
|
270
|
+
),
|
|
271
|
+
y: PcbScene3dStaticBodyPlacementBuilder.#roundMil(
|
|
272
|
+
normalizedVertex.y - placementCenter.y
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Resolves owner-axis mirror transforms for source-coordinate polygons
|
|
282
|
+
* whose body anchor is opposite their raw polygon bounds.
|
|
283
|
+
* @param {{ positionMil?: { x?: number, y?: number } }} componentBody Component body.
|
|
284
|
+
* @param {{ x: number, y: number }} boundsCenter Source-coordinate polygon bounds center.
|
|
285
|
+
* @param {{ x?: number, y?: number } | null} matchedComponent Matched owner component.
|
|
286
|
+
* @returns {{ mirrorAxisX?: number, mirrorAxisY?: number, placementCenterMil: { x: number, y: number } } | null}
|
|
287
|
+
*/
|
|
288
|
+
static #sourceCoordinateMirror(
|
|
289
|
+
componentBody,
|
|
290
|
+
boundsCenter,
|
|
291
|
+
matchedComponent
|
|
292
|
+
) {
|
|
293
|
+
const source =
|
|
294
|
+
PcbScene3dStaticBodyPlacementBuilder.#sourcePosition(componentBody)
|
|
295
|
+
const xDecision =
|
|
296
|
+
PcbScene3dStaticBodyPlacementBuilder.#sourceCoordinateMirrorAxis(
|
|
297
|
+
boundsCenter.x,
|
|
298
|
+
source.x,
|
|
299
|
+
Number(matchedComponent?.x)
|
|
300
|
+
)
|
|
301
|
+
const yDecision =
|
|
302
|
+
PcbScene3dStaticBodyPlacementBuilder.#sourceCoordinateMirrorAxis(
|
|
303
|
+
boundsCenter.y,
|
|
304
|
+
source.y,
|
|
305
|
+
Number(matchedComponent?.y)
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
if (
|
|
309
|
+
!xDecision.valid ||
|
|
310
|
+
!yDecision.valid ||
|
|
311
|
+
(!xDecision.mirrored && !yDecision.mirrored)
|
|
312
|
+
) {
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
mirrorAxisX: xDecision.mirrored
|
|
318
|
+
? Number(matchedComponent?.x)
|
|
319
|
+
: undefined,
|
|
320
|
+
mirrorAxisY: yDecision.mirrored
|
|
321
|
+
? Number(matchedComponent?.y)
|
|
322
|
+
: undefined,
|
|
323
|
+
placementCenterMil: {
|
|
324
|
+
x: source.x,
|
|
325
|
+
y: source.y
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Checks whether one source-coordinate axis is aligned or mirrored.
|
|
332
|
+
* @param {number} center Raw polygon center coordinate.
|
|
333
|
+
* @param {number} source Body source coordinate.
|
|
334
|
+
* @param {number} ownerAxis Owner coordinate.
|
|
335
|
+
* @returns {{ valid: boolean, mirrored: boolean }}
|
|
336
|
+
*/
|
|
337
|
+
static #sourceCoordinateMirrorAxis(center, source, ownerAxis) {
|
|
338
|
+
const tolerance =
|
|
339
|
+
PcbScene3dStaticBodyPlacementBuilder
|
|
340
|
+
.#SOURCE_COORDINATE_MIRROR_TOLERANCE_MIL
|
|
341
|
+
const offset = Math.abs(Number(center || 0) - Number(source || 0))
|
|
342
|
+
if (offset <= tolerance) {
|
|
343
|
+
return { valid: true, mirrored: false }
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const mirroredCenter = 2 * Number(ownerAxis) - Number(center || 0)
|
|
347
|
+
const mirrorError = Math.abs(mirroredCenter - Number(source || 0))
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
valid:
|
|
351
|
+
Number.isFinite(ownerAxis) &&
|
|
352
|
+
offset >
|
|
353
|
+
PcbScene3dStaticBodyPlacementBuilder
|
|
354
|
+
.#SOURCE_COORDINATE_MIRROR_MIN_OFFSET_MIL &&
|
|
355
|
+
mirrorError <= tolerance,
|
|
356
|
+
mirrored: true
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Converts one source-space vertex into render source-space coordinates.
|
|
362
|
+
* @param {{ x?: number, y?: number }} vertex Source vertex.
|
|
363
|
+
* @param {{ mirrorAxisX?: number, mirrorAxisY?: number } | null} mirror Optional mirror transform.
|
|
364
|
+
* @returns {{ x: number, y: number }}
|
|
365
|
+
*/
|
|
366
|
+
static #normalizeVertex(vertex, mirror) {
|
|
367
|
+
const sourceX = Number(vertex?.x || 0)
|
|
368
|
+
const sourceY = Number(vertex?.y || 0)
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
x: Number.isFinite(mirror?.mirrorAxisX)
|
|
372
|
+
? 2 * mirror.mirrorAxisX - sourceX
|
|
373
|
+
: sourceX,
|
|
374
|
+
y: Number.isFinite(mirror?.mirrorAxisY)
|
|
375
|
+
? 2 * mirror.mirrorAxisY - sourceY
|
|
376
|
+
: sourceY
|
|
206
377
|
}
|
|
207
378
|
}
|
|
208
379
|
|
|
@@ -264,23 +435,45 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
264
435
|
metadata.bodyColor = componentBody.bodyColor
|
|
265
436
|
}
|
|
266
437
|
|
|
267
|
-
const opacity =
|
|
268
|
-
|
|
438
|
+
const opacity = PcbScene3dStaticBodyRecovery.renderableOpacity(
|
|
439
|
+
componentBody?.bodyOpacity
|
|
440
|
+
)
|
|
441
|
+
if (opacity !== undefined) {
|
|
269
442
|
metadata.bodyOpacity = opacity
|
|
270
443
|
}
|
|
271
444
|
|
|
272
445
|
return metadata
|
|
273
446
|
}
|
|
274
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Resolves the stable source identity shared by sibling static bodies.
|
|
450
|
+
* @param {{ identifier?: string, name?: string, modelId?: string }} componentBody Component body.
|
|
451
|
+
* @returns {string}
|
|
452
|
+
*/
|
|
453
|
+
static #sourceIdentityKey(componentBody) {
|
|
454
|
+
return String(
|
|
455
|
+
componentBody?.identifier ||
|
|
456
|
+
componentBody?.name ||
|
|
457
|
+
componentBody?.modelId ||
|
|
458
|
+
''
|
|
459
|
+
).trim()
|
|
460
|
+
}
|
|
461
|
+
|
|
275
462
|
/**
|
|
276
463
|
* Checks whether an unowned static body is likely a translucent mechanical
|
|
277
464
|
* overlay instead of a component package.
|
|
278
465
|
* @param {{ componentIndex?: number, embedded?: boolean, bodyOpacity?: number | string, identifier?: string, name?: string, positionMil?: { x?: number, y?: number } }} componentBody Component body.
|
|
279
466
|
* @param {object} geometry Static geometry.
|
|
280
467
|
* @param {{ componentIndex?: number, x?: number, y?: number, pattern?: string, source?: string, modelPath?: string, description?: string, parameters?: object, provenance?: object }[]} components Components.
|
|
468
|
+
* @param {{ x: number, y: number }} sourcePosition Source-space placement center.
|
|
281
469
|
* @returns {boolean}
|
|
282
470
|
*/
|
|
283
|
-
static #shouldSuppressStaticBody(
|
|
471
|
+
static #shouldSuppressStaticBody(
|
|
472
|
+
componentBody,
|
|
473
|
+
geometry,
|
|
474
|
+
components,
|
|
475
|
+
sourcePosition
|
|
476
|
+
) {
|
|
284
477
|
return (
|
|
285
478
|
PcbScene3dStaticBodyPlacementBuilder.#isTranslucentBody(
|
|
286
479
|
componentBody
|
|
@@ -288,7 +481,8 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
288
481
|
!PcbScene3dStaticBodyPlacementBuilder.#hasLikelyComponentOwner(
|
|
289
482
|
componentBody,
|
|
290
483
|
geometry,
|
|
291
|
-
components
|
|
484
|
+
components,
|
|
485
|
+
sourcePosition
|
|
292
486
|
)
|
|
293
487
|
)
|
|
294
488
|
}
|
|
@@ -299,9 +493,11 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
299
493
|
* @returns {boolean}
|
|
300
494
|
*/
|
|
301
495
|
static #isTranslucentBody(componentBody) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
496
|
+
return (
|
|
497
|
+
PcbScene3dStaticBodyRecovery.renderableOpacity(
|
|
498
|
+
componentBody?.bodyOpacity
|
|
499
|
+
) !== undefined
|
|
500
|
+
)
|
|
305
501
|
}
|
|
306
502
|
|
|
307
503
|
/**
|
|
@@ -310,9 +506,15 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
310
506
|
* @param {{ componentIndex?: number, embedded?: boolean, identifier?: string, name?: string, positionMil?: { x?: number, y?: number } }} componentBody Component body.
|
|
311
507
|
* @param {object} geometry Static geometry.
|
|
312
508
|
* @param {{ componentIndex?: number, x?: number, y?: number, pattern?: string, source?: string, modelPath?: string, description?: string, parameters?: object, provenance?: object }[]} components Components.
|
|
509
|
+
* @param {{ x: number, y: number }} sourcePosition Source-space placement center.
|
|
313
510
|
* @returns {boolean}
|
|
314
511
|
*/
|
|
315
|
-
static #hasLikelyComponentOwner(
|
|
512
|
+
static #hasLikelyComponentOwner(
|
|
513
|
+
componentBody,
|
|
514
|
+
geometry,
|
|
515
|
+
components,
|
|
516
|
+
sourcePosition
|
|
517
|
+
) {
|
|
316
518
|
if (componentBody?.embedded) {
|
|
317
519
|
return true
|
|
318
520
|
}
|
|
@@ -334,6 +536,11 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
334
536
|
PcbScene3dStaticBodyPlacementBuilder.#hasGenericMechanicalIdentity(
|
|
335
537
|
componentBody
|
|
336
538
|
)
|
|
539
|
+
const sourceBounds =
|
|
540
|
+
PcbScene3dStaticBodyPlacementBuilder.#geometrySourceBounds(
|
|
541
|
+
geometry,
|
|
542
|
+
sourcePosition
|
|
543
|
+
)
|
|
337
544
|
|
|
338
545
|
return (Array.isArray(components) ? components : []).some(
|
|
339
546
|
(component) => {
|
|
@@ -349,6 +556,19 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
349
556
|
)
|
|
350
557
|
|
|
351
558
|
return (
|
|
559
|
+
(genericMechanical &&
|
|
560
|
+
PcbScene3dStaticBodyPlacementBuilder.#isShieldOwner(
|
|
561
|
+
component
|
|
562
|
+
) &&
|
|
563
|
+
(distance <=
|
|
564
|
+
PcbScene3dStaticBodyPlacementBuilder
|
|
565
|
+
.#OWNER_AFFINITY_DISTANCE_MIL ||
|
|
566
|
+
PcbScene3dStaticBodyPlacementBuilder.#boundsContainPoint(
|
|
567
|
+
sourceBounds,
|
|
568
|
+
component,
|
|
569
|
+
PcbScene3dStaticBodyPlacementBuilder
|
|
570
|
+
.#OWNER_BOUNDS_TOLERANCE_MIL
|
|
571
|
+
))) ||
|
|
352
572
|
(!genericMechanical &&
|
|
353
573
|
affinityScore > 0 &&
|
|
354
574
|
distance <=
|
|
@@ -366,6 +586,91 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
366
586
|
)
|
|
367
587
|
}
|
|
368
588
|
|
|
589
|
+
/**
|
|
590
|
+
* Resolves source-space bounds for one static polygon geometry.
|
|
591
|
+
* @param {object} geometry Static geometry.
|
|
592
|
+
* @param {{ x?: number, y?: number }} sourcePosition Source-space placement center.
|
|
593
|
+
* @returns {{ minX: number, minY: number, maxX: number, maxY: number } | null}
|
|
594
|
+
*/
|
|
595
|
+
static #geometrySourceBounds(geometry, sourcePosition) {
|
|
596
|
+
if (
|
|
597
|
+
!Array.isArray(geometry?.verticesMil) ||
|
|
598
|
+
!geometry.verticesMil.length
|
|
599
|
+
) {
|
|
600
|
+
return null
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const points = geometry.verticesMil
|
|
604
|
+
.map((vertex) => ({
|
|
605
|
+
x: Number(vertex?.x || 0) + Number(sourcePosition?.x || 0),
|
|
606
|
+
y: Number(vertex?.y || 0) + Number(sourcePosition?.y || 0)
|
|
607
|
+
}))
|
|
608
|
+
.filter(
|
|
609
|
+
(point) => Number.isFinite(point.x) && Number.isFinite(point.y)
|
|
610
|
+
)
|
|
611
|
+
if (!points.length) {
|
|
612
|
+
return null
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const xs = points.map((point) => point.x)
|
|
616
|
+
const ys = points.map((point) => point.y)
|
|
617
|
+
|
|
618
|
+
return {
|
|
619
|
+
minX: Math.min(...xs),
|
|
620
|
+
minY: Math.min(...ys),
|
|
621
|
+
maxX: Math.max(...xs),
|
|
622
|
+
maxY: Math.max(...ys)
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Checks whether bounds contain a component point with tolerance.
|
|
628
|
+
* @param {{ minX: number, minY: number, maxX: number, maxY: number } | null} bounds Bounds.
|
|
629
|
+
* @param {{ x?: number, y?: number }} point Candidate point.
|
|
630
|
+
* @param {number} toleranceMil Bounds tolerance.
|
|
631
|
+
* @returns {boolean}
|
|
632
|
+
*/
|
|
633
|
+
static #boundsContainPoint(bounds, point, toleranceMil) {
|
|
634
|
+
if (!bounds) {
|
|
635
|
+
return false
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const x = Number(point?.x || 0)
|
|
639
|
+
const y = Number(point?.y || 0)
|
|
640
|
+
const tolerance = Math.max(Number(toleranceMil || 0), 0)
|
|
641
|
+
|
|
642
|
+
return (
|
|
643
|
+
Number.isFinite(x) &&
|
|
644
|
+
Number.isFinite(y) &&
|
|
645
|
+
x >= bounds.minX - tolerance &&
|
|
646
|
+
x <= bounds.maxX + tolerance &&
|
|
647
|
+
y >= bounds.minY - tolerance &&
|
|
648
|
+
y <= bounds.maxY + tolerance
|
|
649
|
+
)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Checks whether a component identity describes a shield owner strongly
|
|
654
|
+
* enough to keep generic translucent shield sub-bodies.
|
|
655
|
+
* @param {{ designator?: string, pattern?: string, source?: string, description?: string, parameters?: object, provenance?: object }} component Component row.
|
|
656
|
+
* @returns {boolean}
|
|
657
|
+
*/
|
|
658
|
+
static #isShieldOwner(component) {
|
|
659
|
+
const identityText = [
|
|
660
|
+
component?.pattern,
|
|
661
|
+
component?.source,
|
|
662
|
+
component?.description,
|
|
663
|
+
...Object.values(component?.parameters || {}),
|
|
664
|
+
...Object.values(component?.provenance || {})
|
|
665
|
+
]
|
|
666
|
+
.join(' ')
|
|
667
|
+
.toLowerCase()
|
|
668
|
+
|
|
669
|
+
return /(?:^|[^a-z0-9])(?:emi|rfi|shield)(?:$|[^a-z0-9])/u.test(
|
|
670
|
+
identityText
|
|
671
|
+
)
|
|
672
|
+
}
|
|
673
|
+
|
|
369
674
|
/**
|
|
370
675
|
* Checks for generic mechanical labels that are too weak to prove package
|
|
371
676
|
* ownership for translucent static bodies.
|
|
@@ -390,6 +695,7 @@ export class PcbScene3dStaticBodyPlacementBuilder {
|
|
|
390
695
|
static #identityTokens(componentBody) {
|
|
391
696
|
return [componentBody?.identifier, componentBody?.name]
|
|
392
697
|
.join(' ')
|
|
698
|
+
.replace(/([a-z])([A-Z])/gu, '$1 $2')
|
|
393
699
|
.toLowerCase()
|
|
394
700
|
.split(/[^a-z0-9]+/g)
|
|
395
701
|
.flatMap((fragment) => fragment.match(/[a-z]+|\d+/g) || [])
|