pcb-scene3d-viewer 1.1.21 → 1.1.31
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/README.md +3 -2
- package/docs/api.md +27 -1
- package/docs/circuitjson.md +63 -18
- package/docs/model-format.md +13 -2
- package/package.json +1 -1
- package/spec/library-scope.md +2 -2
- package/src/PcbAssemblyBoardSubstrateBuilder.mjs +25 -5
- package/src/PcbAssemblyComponentMeshBuilder.mjs +762 -0
- package/src/PcbAssemblyFillGeometryResolver.mjs +579 -0
- package/src/PcbAssemblyFillRingNormalizer.mjs +209 -0
- package/src/PcbAssemblyGeometryBuilder.mjs +41 -301
- package/src/PcbAssemblyGltfModelMeshParser.mjs +912 -0
- package/src/PcbAssemblyGltfValidator.mjs +460 -0
- package/src/PcbAssemblyGltfWriter.mjs +801 -0
- package/src/PcbAssemblyMeshUtils.mjs +117 -0
- package/src/PcbAssemblyModelMeshLoader.mjs +18 -0
- package/src/PcbAssemblyPadMeshBuilder.mjs +394 -0
- package/src/PcbAssemblyStepWriter.mjs +24 -2
- package/src/PcbAssemblyTextModelMeshParser.mjs +602 -0
- package/src/PcbModelArchiveExporter.mjs +521 -7
- package/src/PcbScene3dCircuitJsonAdapter.mjs +409 -7
- package/src/PcbScene3dCircuitJsonModelTransform.mjs +232 -0
- package/src/PcbScene3dCompanionBasePlacementAdjuster.mjs +242 -0
- package/src/PcbScene3dComponentVisibility.mjs +100 -5
- package/src/PcbScene3dController.mjs +25 -55
- package/src/PcbScene3dCopperDetailFilter.mjs +86 -9
- package/src/PcbScene3dCopperDetailGroupBuilder.mjs +186 -15
- package/src/PcbScene3dCopperDrillCutoutBuilder.mjs +258 -0
- package/src/PcbScene3dCopperFactory.mjs +99 -85
- package/src/PcbScene3dCopperFillMeshBuilder.mjs +393 -0
- package/src/PcbScene3dCopperLayerFilter.mjs +32 -3
- package/src/PcbScene3dCutoutGeometryFilter.mjs +17 -14
- package/src/PcbScene3dExternalCompanionFallback.mjs +202 -0
- package/src/PcbScene3dExternalModelCenteringPolicy.mjs +21 -0
- package/src/PcbScene3dExternalModelOpacity.mjs +51 -0
- package/src/PcbScene3dExternalModelPlacementRepair.mjs +5 -2
- package/src/PcbScene3dExternalModelSourceOriginPolicy.mjs +41 -0
- package/src/PcbScene3dExternalModels.mjs +16 -7
- package/src/PcbScene3dFallbackBodyFactory.mjs +105 -0
- package/src/PcbScene3dFallbackVisibility.mjs +58 -1
- package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +68 -0
- package/src/PcbScene3dPadFactory.mjs +35 -2
- package/src/PcbScene3dRenderGroupVisibility.mjs +8 -1
- package/src/PcbScene3dRuntime.mjs +94 -100
- package/src/PcbScene3dRuntimeHelpers.mjs +39 -0
- package/src/PcbScene3dSelectionIndexBuilder.mjs +87 -0
- package/src/PcbScene3dSelectionInspectorRenderer.mjs +50 -11
- package/src/PcbScene3dSelectionMarkerFactory.mjs +333 -0
- package/src/PcbScene3dSelectionMarkerOverlay.mjs +70 -0
- package/src/PcbScene3dSelectionStyler.mjs +52 -2
- package/src/PcbScene3dStaticBodyFactory.mjs +263 -0
- package/src/PcbScene3dText.mjs +1 -0
- package/src/PcbScene3dTransparentMeshSplitter.mjs +623 -0
- package/src/PcbScene3dViaFactory.mjs +27 -7
- package/src/scene3d.mjs +3 -0
|
@@ -314,12 +314,12 @@ export class PcbScene3dPadFactory {
|
|
|
314
314
|
|
|
315
315
|
/**
|
|
316
316
|
* Returns true when one pad has visible copper on the requested face.
|
|
317
|
-
* @param {{ sizeTopX?: number, sizeTopY?: number, sizeMidX?: number, sizeMidY?: number, sizeBottomX?: number, sizeBottomY?: number, holeDiameter?: number, hasTopSolderMaskOpening?: boolean, hasBottomSolderMaskOpening?: boolean }} pad
|
|
317
|
+
* @param {{ sizeTopX?: number, sizeTopY?: number, sizeMidX?: number, sizeMidY?: number, sizeBottomX?: number, sizeBottomY?: number, holeDiameter?: number, hasTopSolderMaskOpening?: boolean, hasBottomSolderMaskOpening?: boolean, hasTopPasteMaskOpening?: boolean, hasBottomPasteMaskOpening?: boolean }} pad
|
|
318
318
|
* @param {'top' | 'bottom'} side
|
|
319
319
|
* @returns {boolean}
|
|
320
320
|
*/
|
|
321
321
|
static #hasVisibleSurface(pad, side) {
|
|
322
|
-
const maskOpening = PcbScene3dPadFactory.#
|
|
322
|
+
const maskOpening = PcbScene3dPadFactory.#resolveSurfaceOpening(
|
|
323
323
|
pad,
|
|
324
324
|
side
|
|
325
325
|
)
|
|
@@ -350,6 +350,39 @@ export class PcbScene3dPadFactory {
|
|
|
350
350
|
)
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
/**
|
|
354
|
+
* Resolves an explicit side-specific surface opening flag.
|
|
355
|
+
* @param {{ hasTopSolderMaskOpening?: boolean, hasBottomSolderMaskOpening?: boolean, hasTopPasteMaskOpening?: boolean, hasBottomPasteMaskOpening?: boolean }} pad
|
|
356
|
+
* @param {'top' | 'bottom'} side
|
|
357
|
+
* @returns {boolean | null}
|
|
358
|
+
*/
|
|
359
|
+
static #resolveSurfaceOpening(pad, side) {
|
|
360
|
+
const solderMaskOpening =
|
|
361
|
+
PcbScene3dPadFactory.#resolveSolderMaskOpening(pad, side)
|
|
362
|
+
if (solderMaskOpening !== null) {
|
|
363
|
+
return solderMaskOpening
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const pasteFieldName =
|
|
367
|
+
side === 'bottom'
|
|
368
|
+
? 'hasBottomPasteMaskOpening'
|
|
369
|
+
: 'hasTopPasteMaskOpening'
|
|
370
|
+
const alternatePasteFieldName =
|
|
371
|
+
side === 'bottom'
|
|
372
|
+
? 'hasTopPasteMaskOpening'
|
|
373
|
+
: 'hasBottomPasteMaskOpening'
|
|
374
|
+
|
|
375
|
+
if (typeof pad?.[pasteFieldName] === 'boolean') {
|
|
376
|
+
if (pad[pasteFieldName]) {
|
|
377
|
+
return true
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return pad?.[alternatePasteFieldName] === true ? false : null
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return null
|
|
384
|
+
}
|
|
385
|
+
|
|
353
386
|
/**
|
|
354
387
|
* Resolves an explicit side-specific solder-mask opening when available.
|
|
355
388
|
* @param {{ hasTopSolderMaskOpening?: boolean, hasBottomSolderMaskOpening?: boolean }} pad
|
|
@@ -37,9 +37,16 @@ export class PcbScene3dRenderGroupVisibility {
|
|
|
37
37
|
state?.groups?.get('copper'),
|
|
38
38
|
boardAssemblyActive
|
|
39
39
|
)
|
|
40
|
+
const showFallbackBodies =
|
|
41
|
+
!boardAssemblyActive &&
|
|
42
|
+
(Boolean(state?.toggles?.['fallback-bodies']) ||
|
|
43
|
+
PcbScene3dFallbackVisibility.hasVisibleExternalCompanion(
|
|
44
|
+
state?.fallbackBodyRoots,
|
|
45
|
+
state?.toggles
|
|
46
|
+
))
|
|
40
47
|
PcbScene3dRenderGroupVisibility.#setVisible(
|
|
41
48
|
state?.groups?.get('fallback-bodies'),
|
|
42
|
-
|
|
49
|
+
showFallbackBodies
|
|
43
50
|
)
|
|
44
51
|
|
|
45
52
|
PcbScene3dFallbackVisibility.applyVisibility(
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
import { PcbScene3dBoardSolderMaskFactory } from './PcbScene3dBoardSolderMaskFactory.mjs'
|
|
2
|
-
import { PcbScene3dBodyColor } from './PcbScene3dBodyColor.mjs'
|
|
3
2
|
import { PcbScene3dCameraRig } from './PcbScene3dCameraRig.mjs'
|
|
4
3
|
import { PcbScene3dCircuitJsonAdapter } from './PcbScene3dCircuitJsonAdapter.mjs'
|
|
5
4
|
import { PcbScene3dComponentVisibility } from './PcbScene3dComponentVisibility.mjs'
|
|
6
5
|
import { PcbScene3dComponentAdjustment } from './PcbScene3dComponentAdjustment.mjs'
|
|
7
6
|
import { PcbScene3dComponentAdjustmentRegistry } from './PcbScene3dComponentAdjustmentRegistry.mjs'
|
|
7
|
+
import { PcbScene3dCompanionBasePlacementAdjuster } from './PcbScene3dCompanionBasePlacementAdjuster.mjs'
|
|
8
8
|
import { PcbScene3dCopperDetailGroupBuilder } from './PcbScene3dCopperDetailGroupBuilder.mjs'
|
|
9
9
|
import { PcbScene3dCopperFactory } from './PcbScene3dCopperFactory.mjs'
|
|
10
10
|
import { PcbScene3dDetailCoordinateNormalizer } from './PcbScene3dDetailCoordinateNormalizer.mjs'
|
|
11
11
|
import { PcbScene3dDrillVoidFactory } from './PcbScene3dDrillVoidFactory.mjs'
|
|
12
|
+
import { PcbScene3dExternalCompanionFallback } from './PcbScene3dExternalCompanionFallback.mjs'
|
|
12
13
|
import { PcbScene3dExternalModels } from './PcbScene3dExternalModels.mjs'
|
|
14
|
+
import { PcbScene3dFallbackBodyFactory } from './PcbScene3dFallbackBodyFactory.mjs'
|
|
13
15
|
import { PcbScene3dFallbackVisibility } from './PcbScene3dFallbackVisibility.mjs'
|
|
14
16
|
import { PcbScene3dInteractionHints } from './PcbScene3dInteractionHints.mjs'
|
|
15
|
-
import { PcbScene3dMountRig } from './PcbScene3dMountRig.mjs'
|
|
16
17
|
import { PcbScene3dModelSearchPlacement } from './PcbScene3dModelSearchPlacement.mjs'
|
|
17
18
|
import { PcbScene3dPresetState } from './PcbScene3dPresetState.mjs'
|
|
18
19
|
import { PcbScene3dRenderGroupVisibility } from './PcbScene3dRenderGroupVisibility.mjs'
|
|
19
20
|
import { PcbScene3dRenderScheduler } from './PcbScene3dRenderScheduler.mjs'
|
|
20
21
|
import { PcbScene3dRuntimeBoardMeshes } from './PcbScene3dRuntimeBoardMeshes.mjs'
|
|
22
|
+
import { PcbScene3dRuntimeHelpers } from './PcbScene3dRuntimeHelpers.mjs'
|
|
21
23
|
import { PcbScene3dSilkscreenChunkedFactory } from './PcbScene3dSilkscreenChunkedFactory.mjs'
|
|
22
24
|
import { PcbScene3dTrueTypeTextFactory } from './PcbScene3dTrueTypeTextFactory.mjs'
|
|
23
25
|
import { PcbScene3dSelectionResolver } from './PcbScene3dSelectionResolver.mjs'
|
|
26
|
+
import { PcbScene3dSelectionMarkerOverlay } from './PcbScene3dSelectionMarkerOverlay.mjs'
|
|
24
27
|
import { PcbScene3dSelectionStyler } from './PcbScene3dSelectionStyler.mjs'
|
|
28
|
+
import { PcbScene3dStaticBodyFactory } from './PcbScene3dStaticBodyFactory.mjs'
|
|
25
29
|
import { PcbScene3dViewportResize } from './PcbScene3dViewportResize.mjs'
|
|
26
30
|
import { PcbScene3dViewScale } from './PcbScene3dViewScale.mjs'
|
|
27
31
|
const SILKSCREEN_COPPER_CLEARANCE_MIL = 0.12
|
|
@@ -37,6 +41,7 @@ const Z_MIL = {
|
|
|
37
41
|
export class PcbScene3dRuntime {
|
|
38
42
|
#viewportNode
|
|
39
43
|
#sceneDescription
|
|
44
|
+
#placementSceneDescription
|
|
40
45
|
#hooks
|
|
41
46
|
#toggles
|
|
42
47
|
#groups
|
|
@@ -59,6 +64,7 @@ export class PcbScene3dRuntime {
|
|
|
59
64
|
#fallbackBodyRoots
|
|
60
65
|
#loadedExternalModelDesignators
|
|
61
66
|
#modelSearchExternalModelRoots
|
|
67
|
+
#selectionMarkerOverlay
|
|
62
68
|
#hasLoadedBoardAssemblyModel
|
|
63
69
|
#selectedDesignator
|
|
64
70
|
#initialRadius
|
|
@@ -78,6 +84,8 @@ export class PcbScene3dRuntime {
|
|
|
78
84
|
PcbScene3dRuntime.#normalizeSceneDescription(sceneDescription)
|
|
79
85
|
this.#viewportNode = viewportNode
|
|
80
86
|
this.#sceneDescription = renderModel
|
|
87
|
+
this.#placementSceneDescription =
|
|
88
|
+
PcbScene3dCompanionBasePlacementAdjuster.adjust(renderModel)
|
|
81
89
|
this.#hooks = hooks
|
|
82
90
|
this.#toggles = {
|
|
83
91
|
'external-models': true,
|
|
@@ -106,6 +114,7 @@ export class PcbScene3dRuntime {
|
|
|
106
114
|
this.#fallbackBodyRoots = new Map()
|
|
107
115
|
this.#loadedExternalModelDesignators = new Set()
|
|
108
116
|
this.#modelSearchExternalModelRoots = new Set()
|
|
117
|
+
this.#selectionMarkerOverlay = null
|
|
109
118
|
this.#hasLoadedBoardAssemblyModel = false
|
|
110
119
|
this.#selectedDesignator = ''
|
|
111
120
|
this.#initialRadius =
|
|
@@ -175,6 +184,7 @@ export class PcbScene3dRuntime {
|
|
|
175
184
|
return
|
|
176
185
|
}
|
|
177
186
|
this.#applyToggleVisibility()
|
|
187
|
+
this.#updateSelectionMarker()
|
|
178
188
|
this.#render()
|
|
179
189
|
}
|
|
180
190
|
/** @param {string} designator @returns {boolean} */
|
|
@@ -236,6 +246,7 @@ export class PcbScene3dRuntime {
|
|
|
236
246
|
this.#fallbackBodyRoots.clear()
|
|
237
247
|
this.#loadedExternalModelDesignators.clear()
|
|
238
248
|
this.#modelSearchExternalModelRoots.clear()
|
|
249
|
+
this.#selectionMarkerOverlay = null
|
|
239
250
|
this.#hasLoadedBoardAssemblyModel = false
|
|
240
251
|
this.#selectedDesignator = ''
|
|
241
252
|
this.#groups.clear()
|
|
@@ -385,20 +396,69 @@ export class PcbScene3dRuntime {
|
|
|
385
396
|
this.#groups.set('copper', copperGroup)
|
|
386
397
|
this.#rootGroup.add(copperGroup)
|
|
387
398
|
const fallbackBodiesGroup = new THREE.Group()
|
|
399
|
+
const staticBodiesGroup = new THREE.Group()
|
|
388
400
|
const externalModelsGroup = new THREE.Group()
|
|
401
|
+
const selectionMarkerGroup = new THREE.Group()
|
|
389
402
|
this.#sceneDescription.components.forEach((component) => {
|
|
390
|
-
|
|
391
|
-
|
|
403
|
+
if (component?.renderFallbackBody === false) {
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const isCompanionBase =
|
|
408
|
+
PcbScene3dExternalCompanionFallback.shouldKeepFallback(
|
|
409
|
+
this.#sceneDescription,
|
|
410
|
+
component
|
|
411
|
+
)
|
|
412
|
+
const fallbackBody = PcbScene3dFallbackBodyFactory.build(
|
|
413
|
+
THREE,
|
|
414
|
+
component,
|
|
415
|
+
{ companionBase: isCompanionBase }
|
|
416
|
+
)
|
|
417
|
+
if (isCompanionBase) {
|
|
418
|
+
fallbackBody.rootGroup.userData.scene3dFallbackExternalCompanion = true
|
|
419
|
+
}
|
|
420
|
+
fallbackBodiesGroup.add(fallbackBody.rootGroup)
|
|
392
421
|
PcbScene3dFallbackVisibility.registerFallbackRoot(
|
|
393
422
|
this.#fallbackBodyRoots,
|
|
394
423
|
component?.designator,
|
|
395
|
-
fallbackBody
|
|
424
|
+
fallbackBody.rootGroup
|
|
425
|
+
)
|
|
426
|
+
this.#registerSelectionRoot(
|
|
427
|
+
component?.designator,
|
|
428
|
+
fallbackBody.rootGroup
|
|
429
|
+
)
|
|
430
|
+
this.#componentAdjustmentRegistry.register(
|
|
431
|
+
component?.designator,
|
|
432
|
+
fallbackBody.adjustmentGroup
|
|
433
|
+
)
|
|
434
|
+
})
|
|
435
|
+
PcbScene3dStaticBodyFactory.buildMany(
|
|
436
|
+
THREE,
|
|
437
|
+
this.#placementSceneDescription.staticBodyPlacements
|
|
438
|
+
).forEach((staticBody) => {
|
|
439
|
+
const selectionKey =
|
|
440
|
+
PcbScene3dStaticBodyFactory.resolveSelectionKey(
|
|
441
|
+
staticBody.placement
|
|
442
|
+
)
|
|
443
|
+
staticBodiesGroup.add(staticBody.rootGroup)
|
|
444
|
+
this.#registerSelectionRoot(selectionKey, staticBody.rootGroup)
|
|
445
|
+
this.#componentAdjustmentRegistry.register(
|
|
446
|
+
selectionKey,
|
|
447
|
+
staticBody.adjustmentGroup
|
|
396
448
|
)
|
|
397
449
|
})
|
|
398
450
|
this.#groups.set('fallback-bodies', fallbackBodiesGroup)
|
|
451
|
+
this.#groups.set('static-bodies', staticBodiesGroup)
|
|
399
452
|
this.#groups.set('external-models', externalModelsGroup)
|
|
453
|
+
this.#groups.set('selection-marker', selectionMarkerGroup)
|
|
454
|
+
this.#selectionMarkerOverlay = new PcbScene3dSelectionMarkerOverlay(
|
|
455
|
+
selectionMarkerGroup
|
|
456
|
+
)
|
|
400
457
|
this.#rootGroup.add(fallbackBodiesGroup)
|
|
458
|
+
this.#rootGroup.add(staticBodiesGroup)
|
|
401
459
|
this.#rootGroup.add(externalModelsGroup)
|
|
460
|
+
this.#rootGroup.add(selectionMarkerGroup)
|
|
461
|
+
this.#updateSelectionMarker()
|
|
402
462
|
const boardSpan = Math.max(board.widthMil, board.heightMil, 1)
|
|
403
463
|
this.#scene.fog = new THREE.Fog(
|
|
404
464
|
0xf4f0ea,
|
|
@@ -442,7 +502,7 @@ export class PcbScene3dRuntime {
|
|
|
442
502
|
*/
|
|
443
503
|
async #loadDeferredDetail() {
|
|
444
504
|
try {
|
|
445
|
-
await
|
|
505
|
+
await PcbScene3dRuntimeHelpers.yieldToNextFrame(globalThis)
|
|
446
506
|
if (this.#isDisposed) {
|
|
447
507
|
return
|
|
448
508
|
}
|
|
@@ -450,7 +510,7 @@ export class PcbScene3dRuntime {
|
|
|
450
510
|
await this.#loadDeferredSilkscreen()
|
|
451
511
|
this.#render()
|
|
452
512
|
|
|
453
|
-
await
|
|
513
|
+
await PcbScene3dRuntimeHelpers.yieldToNextFrame(globalThis)
|
|
454
514
|
if (this.#isDisposed) {
|
|
455
515
|
return
|
|
456
516
|
}
|
|
@@ -459,7 +519,7 @@ export class PcbScene3dRuntime {
|
|
|
459
519
|
this.#applyToggleVisibility()
|
|
460
520
|
this.#render()
|
|
461
521
|
this.#settleReady()
|
|
462
|
-
await
|
|
522
|
+
await PcbScene3dRuntimeHelpers.yieldToNextFrame(globalThis)
|
|
463
523
|
if (this.#isDisposed) {
|
|
464
524
|
return
|
|
465
525
|
}
|
|
@@ -503,7 +563,8 @@ export class PcbScene3dRuntime {
|
|
|
503
563
|
(x, y) => this.#normalizeDetailPoint(x, y),
|
|
504
564
|
{
|
|
505
565
|
shouldContinue: () => !this.#isDisposed,
|
|
506
|
-
yieldToMain: () =>
|
|
566
|
+
yieldToMain: () =>
|
|
567
|
+
PcbScene3dRuntimeHelpers.yieldToNextFrame(globalThis)
|
|
507
568
|
}
|
|
508
569
|
)
|
|
509
570
|
if (this.#isDisposed) return
|
|
@@ -536,63 +597,6 @@ export class PcbScene3dRuntime {
|
|
|
536
597
|
}
|
|
537
598
|
}
|
|
538
599
|
|
|
539
|
-
/**
|
|
540
|
-
* Builds one procedural fallback body mesh.
|
|
541
|
-
* @param {{ positionMil: { x: number, y: number, z: number }, rotationDeg: number, mountSide: string, body: { family: string, sizeMil: { width: number, depth: number, height: number } } }} component
|
|
542
|
-
* @returns {any}
|
|
543
|
-
*/
|
|
544
|
-
#buildFallbackBody(component) {
|
|
545
|
-
const THREE = this.#three
|
|
546
|
-
const family = component.body.family
|
|
547
|
-
const size = component.body.sizeMil
|
|
548
|
-
const material = new THREE.MeshStandardMaterial({
|
|
549
|
-
color: PcbScene3dBodyColor.resolve(family),
|
|
550
|
-
roughness: 0.72,
|
|
551
|
-
metalness: family === 'chip' ? 0.12 : 0.08
|
|
552
|
-
})
|
|
553
|
-
|
|
554
|
-
let mesh
|
|
555
|
-
if (family === 'radial-capacitor' || family === 'test-point') {
|
|
556
|
-
mesh = new THREE.Mesh(
|
|
557
|
-
new THREE.CylinderGeometry(
|
|
558
|
-
size.width / 2,
|
|
559
|
-
size.width / 2,
|
|
560
|
-
size.height,
|
|
561
|
-
28
|
|
562
|
-
),
|
|
563
|
-
material
|
|
564
|
-
)
|
|
565
|
-
} else {
|
|
566
|
-
mesh = new THREE.Mesh(
|
|
567
|
-
new THREE.BoxGeometry(size.width, size.depth, size.height),
|
|
568
|
-
material
|
|
569
|
-
)
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
const mountRig = PcbScene3dMountRig.create(THREE, component)
|
|
573
|
-
const rootGroup = mountRig.rootGroup
|
|
574
|
-
const adjustmentGroup = new THREE.Group()
|
|
575
|
-
|
|
576
|
-
rootGroup.userData.scene3dSelection = {
|
|
577
|
-
designator: String(component?.designator || 'component'),
|
|
578
|
-
sourceType: 'component'
|
|
579
|
-
}
|
|
580
|
-
adjustmentGroup.userData.scene3dAdjustmentTarget = true
|
|
581
|
-
adjustmentGroup.userData.scene3dAdjustmentDesignator = String(
|
|
582
|
-
component?.designator || 'component'
|
|
583
|
-
)
|
|
584
|
-
adjustmentGroup.userData.scene3dAdjustmentBaseline =
|
|
585
|
-
PcbScene3dComponentAdjustment.neutral()
|
|
586
|
-
this.#registerSelectionRoot(component?.designator, rootGroup)
|
|
587
|
-
this.#componentAdjustmentRegistry.register(
|
|
588
|
-
component?.designator,
|
|
589
|
-
adjustmentGroup
|
|
590
|
-
)
|
|
591
|
-
adjustmentGroup.add(mesh)
|
|
592
|
-
mountRig.faceGroup.add(adjustmentGroup)
|
|
593
|
-
return rootGroup
|
|
594
|
-
}
|
|
595
|
-
|
|
596
600
|
/**
|
|
597
601
|
* Attempts to load any resolved external 3D models.
|
|
598
602
|
* @returns {Promise<void>}
|
|
@@ -605,7 +609,7 @@ export class PcbScene3dRuntime {
|
|
|
605
609
|
|
|
606
610
|
const diagnostics = await PcbScene3dExternalModels.loadIntoScene({
|
|
607
611
|
three: this.#three,
|
|
608
|
-
sceneDescription: this.#
|
|
612
|
+
sceneDescription: this.#placementSceneDescription,
|
|
609
613
|
externalModelsGroup,
|
|
610
614
|
modelViewScale: PcbScene3dRuntime.resolveViewScale(
|
|
611
615
|
this.#presetState.get(),
|
|
@@ -726,7 +730,7 @@ export class PcbScene3dRuntime {
|
|
|
726
730
|
|
|
727
731
|
if (
|
|
728
732
|
!this.#pointerDownPosition ||
|
|
729
|
-
|
|
733
|
+
PcbScene3dRuntimeHelpers.pointerTravel(
|
|
730
734
|
this.#pointerDownPosition,
|
|
731
735
|
{
|
|
732
736
|
x: Number(event?.clientX || 0),
|
|
@@ -770,6 +774,7 @@ export class PcbScene3dRuntime {
|
|
|
770
774
|
})
|
|
771
775
|
PcbScene3dComponentVisibility.apply({
|
|
772
776
|
selectionRoots: this.#selectionRoots,
|
|
777
|
+
selectedDesignator: this.#selectedDesignator,
|
|
773
778
|
hiddenDesignators: this.#hiddenComponentDesignators,
|
|
774
779
|
fallbackBodyRoots: this.#fallbackBodyRoots,
|
|
775
780
|
loadedExternalModelDesignators:
|
|
@@ -780,6 +785,24 @@ export class PcbScene3dRuntime {
|
|
|
780
785
|
})
|
|
781
786
|
}
|
|
782
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Rebuilds the flat selected-component marker overlay.
|
|
790
|
+
* @returns {void}
|
|
791
|
+
*/
|
|
792
|
+
#updateSelectionMarker() {
|
|
793
|
+
this.#selectionMarkerOverlay?.update(
|
|
794
|
+
this.#three,
|
|
795
|
+
this.#sceneDescription,
|
|
796
|
+
this.#selectedDesignator,
|
|
797
|
+
PcbScene3dComponentVisibility.isHidden(
|
|
798
|
+
this.#hiddenComponentDesignators,
|
|
799
|
+
this.#selectedDesignator
|
|
800
|
+
),
|
|
801
|
+
(x, y) => this.#normalizeDetailPoint(x, y),
|
|
802
|
+
{ color: PcbScene3dRuntime.#resolveSelectionHighlightColor() }
|
|
803
|
+
)
|
|
804
|
+
}
|
|
805
|
+
|
|
783
806
|
/** @returns {void} */
|
|
784
807
|
#render() {
|
|
785
808
|
if (!this.#renderer || !this.#scene || !this.#camera) {
|
|
@@ -802,26 +825,6 @@ export class PcbScene3dRuntime {
|
|
|
802
825
|
this.#resolveReadyPromise = null
|
|
803
826
|
}
|
|
804
827
|
|
|
805
|
-
/**
|
|
806
|
-
* Yields one turn so the browser can present the initial rendered frame
|
|
807
|
-
* before deferred detail work continues.
|
|
808
|
-
* @returns {Promise<void>}
|
|
809
|
-
*/
|
|
810
|
-
static async #yieldToNextFrame() {
|
|
811
|
-
if (
|
|
812
|
-
typeof window !== 'undefined' &&
|
|
813
|
-
typeof window.requestAnimationFrame === 'function'
|
|
814
|
-
) {
|
|
815
|
-
await new Promise((resolve) => {
|
|
816
|
-
window.requestAnimationFrame(() => resolve())
|
|
817
|
-
})
|
|
818
|
-
return
|
|
819
|
-
}
|
|
820
|
-
await new Promise((resolve) => {
|
|
821
|
-
globalThis.setTimeout(resolve, 0)
|
|
822
|
-
})
|
|
823
|
-
}
|
|
824
|
-
|
|
825
828
|
/**
|
|
826
829
|
* Normalizes one board coordinate into the scene's centered board plane.
|
|
827
830
|
* @param {number} x
|
|
@@ -891,6 +894,7 @@ export class PcbScene3dRuntime {
|
|
|
891
894
|
|
|
892
895
|
const selectableRoots = [
|
|
893
896
|
this.#groups.get('external-models'),
|
|
897
|
+
this.#groups.get('static-bodies'),
|
|
894
898
|
this.#groups.get('fallback-bodies')
|
|
895
899
|
].filter((group) => group && group.visible !== false)
|
|
896
900
|
const intersections = this.#raycaster.intersectObjects(
|
|
@@ -935,6 +939,8 @@ export class PcbScene3dRuntime {
|
|
|
935
939
|
PcbScene3dRuntime.#resolveSelectionHighlightColor()
|
|
936
940
|
)
|
|
937
941
|
this.#selectedDesignator = normalizedDesignator
|
|
942
|
+
this.#applyToggleVisibility()
|
|
943
|
+
this.#updateSelectionMarker()
|
|
938
944
|
this.#render()
|
|
939
945
|
}
|
|
940
946
|
|
|
@@ -982,16 +988,4 @@ export class PcbScene3dRuntime {
|
|
|
982
988
|
static #resolveSelectionHighlightColor() {
|
|
983
989
|
return 0x14c5e6
|
|
984
990
|
}
|
|
985
|
-
|
|
986
|
-
/**
|
|
987
|
-
* Resolves the drag travel between two pointer positions.
|
|
988
|
-
* @param {{ x: number, y: number }} start
|
|
989
|
-
* @param {{ x: number, y: number }} end
|
|
990
|
-
* @returns {number}
|
|
991
|
-
*/
|
|
992
|
-
static #resolvePointerTravel(start, end) {
|
|
993
|
-
const dx = Number(end?.x || 0) - Number(start?.x || 0)
|
|
994
|
-
const dy = Number(end?.y || 0) - Number(start?.y || 0)
|
|
995
|
-
return Math.hypot(dx, dy)
|
|
996
|
-
}
|
|
997
991
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small runtime utilities that do not need access to private runtime state.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dRuntimeHelpers {
|
|
5
|
+
/**
|
|
6
|
+
* Yields one turn so the browser can present a rendered frame.
|
|
7
|
+
* @param {typeof globalThis} globalScope Runtime global object.
|
|
8
|
+
* @returns {Promise<void>}
|
|
9
|
+
*/
|
|
10
|
+
static async yieldToNextFrame(globalScope) {
|
|
11
|
+
const frameScheduler =
|
|
12
|
+
globalScope?.window?.requestAnimationFrame ||
|
|
13
|
+
globalScope?.requestAnimationFrame
|
|
14
|
+
if (typeof frameScheduler === 'function') {
|
|
15
|
+
await new Promise((resolve) => {
|
|
16
|
+
frameScheduler.call(globalScope?.window || globalScope, () =>
|
|
17
|
+
resolve()
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await new Promise((resolve) => {
|
|
24
|
+
globalScope?.setTimeout(resolve, 0)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolves the drag travel between two pointer positions.
|
|
30
|
+
* @param {{ x: number, y: number }} start Pointer-down position.
|
|
31
|
+
* @param {{ x: number, y: number }} end Pointer-up position.
|
|
32
|
+
* @returns {number}
|
|
33
|
+
*/
|
|
34
|
+
static pointerTravel(start, end) {
|
|
35
|
+
const dx = Number(end?.x || 0) - Number(start?.x || 0)
|
|
36
|
+
const dy = Number(end?.y || 0) - Number(start?.y || 0)
|
|
37
|
+
return Math.hypot(dx, dy)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds lookup indexes for 3D scene selections.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dSelectionIndexBuilder {
|
|
5
|
+
/**
|
|
6
|
+
* Builds one selected-key inspector lookup from the scene description.
|
|
7
|
+
* @param {{ components?: any[], externalPlacements?: any[], staticBodyPlacements?: any[] }} sceneDescription Scene data.
|
|
8
|
+
* @returns {Map<string, { component: any | null, externalPlacement: any | null, staticBodyPlacement: any | null }>}
|
|
9
|
+
*/
|
|
10
|
+
static build(sceneDescription) {
|
|
11
|
+
const index = new Map()
|
|
12
|
+
const components = Array.isArray(sceneDescription?.components)
|
|
13
|
+
? sceneDescription.components
|
|
14
|
+
: []
|
|
15
|
+
const externalPlacements = Array.isArray(
|
|
16
|
+
sceneDescription?.externalPlacements
|
|
17
|
+
)
|
|
18
|
+
? sceneDescription.externalPlacements
|
|
19
|
+
: []
|
|
20
|
+
const staticBodyPlacements = Array.isArray(
|
|
21
|
+
sceneDescription?.staticBodyPlacements
|
|
22
|
+
)
|
|
23
|
+
? sceneDescription.staticBodyPlacements
|
|
24
|
+
: []
|
|
25
|
+
|
|
26
|
+
components.forEach((component) => {
|
|
27
|
+
const designator = String(component?.designator || '').trim()
|
|
28
|
+
if (!designator) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
index.set(designator, {
|
|
33
|
+
component,
|
|
34
|
+
externalPlacement:
|
|
35
|
+
index.get(designator)?.externalPlacement || null,
|
|
36
|
+
staticBodyPlacement:
|
|
37
|
+
index.get(designator)?.staticBodyPlacement || null
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
externalPlacements.forEach((externalPlacement) => {
|
|
42
|
+
const designator = String(
|
|
43
|
+
externalPlacement?.designator || ''
|
|
44
|
+
).trim()
|
|
45
|
+
if (!designator) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
index.set(designator, {
|
|
50
|
+
component: index.get(designator)?.component || null,
|
|
51
|
+
externalPlacement,
|
|
52
|
+
staticBodyPlacement:
|
|
53
|
+
index.get(designator)?.staticBodyPlacement || null
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
staticBodyPlacements.forEach((staticBodyPlacement) => {
|
|
58
|
+
const selectionKey =
|
|
59
|
+
PcbScene3dSelectionIndexBuilder.#resolveStaticBodySelectionKey(
|
|
60
|
+
staticBodyPlacement
|
|
61
|
+
)
|
|
62
|
+
if (!selectionKey) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
index.set(selectionKey, {
|
|
67
|
+
component: index.get(selectionKey)?.component || null,
|
|
68
|
+
externalPlacement:
|
|
69
|
+
index.get(selectionKey)?.externalPlacement || null,
|
|
70
|
+
staticBodyPlacement
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return index
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolves the selectable key for one static body placement.
|
|
79
|
+
* @param {{ designator?: string, selectionKey?: string }} placement Static body placement.
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
static #resolveStaticBodySelectionKey(placement) {
|
|
83
|
+
return String(
|
|
84
|
+
placement?.selectionKey || placement?.designator || ''
|
|
85
|
+
).trim()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -46,37 +46,49 @@ export class PcbScene3dSelectionInspectorRenderer {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Renders selected component details and optional editable transform controls.
|
|
49
|
-
* @param {{ designator: string, hidden?: boolean, selection?: { sourceType?: string } | null, selectionEntry: { component: any | null, externalPlacement: any | null }, adjustment: { scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }, includeControls?: boolean, translate: (key: string) => string }} options Render options.
|
|
49
|
+
* @param {{ designator: string, hidden?: boolean, selection?: { sourceType?: string } | null, selectionEntry: { component: any | null, externalPlacement: any | null, staticBodyPlacement?: any | null }, adjustment: { scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }, includeControls?: boolean, translate: (key: string) => string }} options Render options.
|
|
50
50
|
* @returns {string}
|
|
51
51
|
*/
|
|
52
52
|
static renderSelected(options) {
|
|
53
53
|
const component = options.selectionEntry.component
|
|
54
54
|
const externalPlacement = options.selectionEntry.externalPlacement
|
|
55
|
+
const staticBodyPlacement =
|
|
56
|
+
options.selectionEntry.staticBodyPlacement || null
|
|
57
|
+
const displayDesignator =
|
|
58
|
+
staticBodyPlacement?.designator || options.designator
|
|
55
59
|
const hidden = options.hidden === true
|
|
56
60
|
const fields = [
|
|
57
|
-
[options.translate('scene3d.designator'),
|
|
61
|
+
[options.translate('scene3d.designator'), displayDesignator],
|
|
58
62
|
[
|
|
59
63
|
options.translate('scene3d.picked'),
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
PcbScene3dSelectionInspectorRenderer.#formatSourceType(
|
|
65
|
+
options.selection,
|
|
66
|
+
options.selectionEntry,
|
|
67
|
+
options.translate
|
|
68
|
+
)
|
|
63
69
|
],
|
|
64
70
|
[
|
|
65
71
|
options.translate('scene3d.mountSide'),
|
|
66
|
-
externalPlacement?.mountSide ||
|
|
72
|
+
externalPlacement?.mountSide ||
|
|
73
|
+
component?.mountSide ||
|
|
74
|
+
staticBodyPlacement?.mountSide ||
|
|
75
|
+
''
|
|
67
76
|
],
|
|
68
77
|
[
|
|
69
78
|
options.translate('scene3d.rotation'),
|
|
70
79
|
PcbScene3dSelectionInspectorRenderer.#formatValue(
|
|
71
|
-
component?.rotationDeg ??
|
|
80
|
+
component?.rotationDeg ??
|
|
81
|
+
externalPlacement?.rotationDeg ??
|
|
82
|
+
staticBodyPlacement?.rotationDeg,
|
|
72
83
|
'deg'
|
|
73
84
|
)
|
|
74
85
|
],
|
|
75
86
|
[
|
|
76
87
|
options.translate('scene3d.boardPosition'),
|
|
77
|
-
component?.boardPositionMil
|
|
88
|
+
component?.boardPositionMil || staticBodyPlacement?.positionMil
|
|
78
89
|
? PcbScene3dSelectionInspectorRenderer.#formatPoint(
|
|
79
|
-
component
|
|
90
|
+
component?.boardPositionMil ||
|
|
91
|
+
staticBodyPlacement?.positionMil,
|
|
80
92
|
true
|
|
81
93
|
)
|
|
82
94
|
: ''
|
|
@@ -98,9 +110,11 @@ export class PcbScene3dSelectionInspectorRenderer {
|
|
|
98
110
|
],
|
|
99
111
|
[
|
|
100
112
|
options.translate('scene3d.bodyPosition'),
|
|
101
|
-
externalPlacement?.bodyPositionMil
|
|
113
|
+
externalPlacement?.bodyPositionMil ||
|
|
114
|
+
staticBodyPlacement?.bodyPositionMil
|
|
102
115
|
? PcbScene3dSelectionInspectorRenderer.#formatPoint(
|
|
103
|
-
externalPlacement
|
|
116
|
+
externalPlacement?.bodyPositionMil ||
|
|
117
|
+
staticBodyPlacement?.bodyPositionMil,
|
|
104
118
|
false
|
|
105
119
|
)
|
|
106
120
|
: ''
|
|
@@ -536,6 +550,31 @@ export class PcbScene3dSelectionInspectorRenderer {
|
|
|
536
550
|
: ''
|
|
537
551
|
}
|
|
538
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Formats one selection source type label.
|
|
555
|
+
* @param {{ sourceType?: string } | null | undefined} selection Selection payload.
|
|
556
|
+
* @param {{ externalPlacement?: any | null, staticBodyPlacement?: any | null }} selectionEntry Selection metadata.
|
|
557
|
+
* @param {(key: string) => string} translate Translation lookup.
|
|
558
|
+
* @returns {string}
|
|
559
|
+
*/
|
|
560
|
+
static #formatSourceType(selection, selectionEntry, translate) {
|
|
561
|
+
if (
|
|
562
|
+
selection?.sourceType === 'external-model' ||
|
|
563
|
+
selectionEntry?.externalPlacement
|
|
564
|
+
) {
|
|
565
|
+
return translate('scene3d.externalModel')
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (
|
|
569
|
+
selection?.sourceType === 'static-body' ||
|
|
570
|
+
selectionEntry?.staticBodyPlacement
|
|
571
|
+
) {
|
|
572
|
+
return translate('scene3d.staticBody')
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return translate('scene3d.fallbackBody')
|
|
576
|
+
}
|
|
577
|
+
|
|
539
578
|
/**
|
|
540
579
|
* Formats one rotation triplet.
|
|
541
580
|
* @param {{ x?: number, y?: number, z?: number } | null | undefined} rotationDeg Rotation.
|