pcb-scene3d-viewer 1.1.1 → 1.1.2
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/api.md +10 -0
- package/package.json +2 -2
- package/src/PcbScene3dAdjustmentControlBinder.mjs +667 -0
- package/src/PcbScene3dComponentAdjustment.mjs +174 -0
- package/src/PcbScene3dComponentAdjustmentRegistry.mjs +95 -0
- package/src/PcbScene3dController.mjs +214 -197
- package/src/PcbScene3dCopperFactory.mjs +4 -1
- package/src/PcbScene3dCutoutGeometryFilter.mjs +9 -3
- package/src/PcbScene3dExternalModels.mjs +56 -71
- package/src/PcbScene3dGeometryBoundsResolver.mjs +78 -0
- package/src/PcbScene3dModelBounds.mjs +267 -0
- package/src/PcbScene3dModelSearchPlacement.mjs +40 -0
- package/src/PcbScene3dPadFactory.mjs +221 -20
- package/src/PcbScene3dRenderGroupVisibility.mjs +23 -1
- package/src/PcbScene3dRuntime.mjs +62 -51
- package/src/PcbScene3dSelectionInspectorRenderer.mjs +581 -0
- package/src/PcbScene3dSelectionResolver.mjs +42 -0
- package/src/PcbScene3dSilkscreenFactory.mjs +4 -1
- package/src/PcbScene3dText.mjs +3 -0
- package/src/styles/scene3d.css +142 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { PcbModelArchiveExporter } from './PcbModelArchiveExporter.mjs'
|
|
2
|
+
import { PcbScene3dAdjustmentControlBinder } from './PcbScene3dAdjustmentControlBinder.mjs'
|
|
2
3
|
import { PcbScene3dCircuitJsonAdapter } from './PcbScene3dCircuitJsonAdapter.mjs'
|
|
3
4
|
import { PcbScene3dInteractionHints } from './PcbScene3dInteractionHints.mjs'
|
|
4
5
|
import { PcbScene3dRuntime } from './PcbScene3dRuntime.mjs'
|
|
6
|
+
import { PcbScene3dSelectionInspectorRenderer } from './PcbScene3dSelectionInspectorRenderer.mjs'
|
|
5
7
|
import { PcbScene3dText } from './PcbScene3dText.mjs'
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -23,16 +25,25 @@ export class PcbScene3dController {
|
|
|
23
25
|
/** @type {HTMLElement | null} */
|
|
24
26
|
#selectionNode
|
|
25
27
|
|
|
28
|
+
/** @type {HTMLElement | null} */
|
|
29
|
+
#adjustmentHostNode
|
|
30
|
+
|
|
31
|
+
/** @type {PcbScene3dAdjustmentControlBinder | null} */
|
|
32
|
+
#adjustmentControls
|
|
33
|
+
|
|
26
34
|
/** @type {Array<{ node: EventTarget, type: string, listener: (event: any) => void }>} */
|
|
27
35
|
#listeners
|
|
28
36
|
|
|
29
37
|
/** @type {Map<string, { component: any | null, externalPlacement: any | null }>} */
|
|
30
38
|
#selectionIndex
|
|
31
39
|
|
|
40
|
+
/** @type {Map<string, { scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }>} */
|
|
41
|
+
#componentAdjustments
|
|
42
|
+
|
|
32
43
|
/** @type {string} */
|
|
33
44
|
#selectedComponentKey
|
|
34
45
|
|
|
35
|
-
/** @type {{ setPreset?: (preset: string) => void, setToggle?: (toggleName: string, enabled: boolean) => void, dispose?: () => void } | null} */
|
|
46
|
+
/** @type {{ setPreset?: (preset: string) => void, setToggle?: (toggleName: string, enabled: boolean) => void, setSelectedDesignator?: (designator: string) => void, setComponentAdjustment?: (designator: string, adjustment: { scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }) => void, dispose?: () => void } | null} */
|
|
36
47
|
#runtime
|
|
37
48
|
|
|
38
49
|
/** @type {any | null} */
|
|
@@ -53,6 +64,12 @@ export class PcbScene3dController {
|
|
|
53
64
|
/** @type {(key: string) => string} */
|
|
54
65
|
#translate
|
|
55
66
|
|
|
67
|
+
/** @type {boolean} */
|
|
68
|
+
#renderAdjustmentControlsInSelection
|
|
69
|
+
|
|
70
|
+
/** @type {boolean} */
|
|
71
|
+
#autoSearchMissingModels
|
|
72
|
+
|
|
56
73
|
/** @type {string} */
|
|
57
74
|
#documentId
|
|
58
75
|
|
|
@@ -65,7 +82,7 @@ export class PcbScene3dController {
|
|
|
65
82
|
/**
|
|
66
83
|
* @param {HTMLElement} viewportNode
|
|
67
84
|
* @param {any} documentModel
|
|
68
|
-
* @param {{ rootNode?: HTMLElement | null, documentId?: string, onComponentSelectionChange?: ((change: { documentId: string, componentKey: string, source?: string }) => void) | null, sessionAssets?: any[], circuitJson?: object[], sceneDescription?: any, buildScene?: (documentModel: any, options: { modelRegistry: any }) => any, createModelRegistry?: (documentModel: any, sessionAssets: any[]) => any, createRuntime?: (viewportNode: HTMLElement, sceneDescription: any, hooks: { setDiagnostics: (messages: string[]) => void, setSelection: (selection: any | null) => void, translate?: ((key: string) => string) | null }) => { setPreset?: (preset: string) => void, setToggle?: (toggleName: string, enabled: boolean) => void, dispose?: () => void, whenReady?: () => Promise<void> | void }, scenePrepClient?: { prepareScene?: (documentModel: any, sessionAssets?: any[]) => Promise<any>, dispose?: () => void } | null, exportArchive?: (options: { archiveBaseName?: string, sceneDescription?: any }) => Promise<{ archiveName: string, archiveBytes: Uint8Array, exportedEntries: any[], skippedEntries: any[] }>, downloadArchive?: (archiveName: string, archiveBytes: Uint8Array) => Promise<void> | void, setLoadingVisible?: (visible: boolean) => void, translate?: ((key: string) => string) | null }} [options]
|
|
85
|
+
* @param {{ rootNode?: HTMLElement | null, documentId?: string, onComponentSelectionChange?: ((change: { documentId: string, componentKey: string, source?: string }) => void) | null, sessionAssets?: any[], autoSearchMissingModels?: boolean, circuitJson?: object[], sceneDescription?: any, buildScene?: (documentModel: any, options: { modelRegistry: any }) => any, createModelRegistry?: (documentModel: any, sessionAssets: any[]) => any, createRuntime?: (viewportNode: HTMLElement, sceneDescription: any, hooks: { setDiagnostics: (messages: string[]) => void, setSelection: (selection: any | null) => void, translate?: ((key: string) => string) | null }) => { setPreset?: (preset: string) => void, setToggle?: (toggleName: string, enabled: boolean) => void, dispose?: () => void, whenReady?: () => Promise<void> | void }, scenePrepClient?: { prepareScene?: (documentModel: any, sessionAssets?: any[]) => Promise<any>, dispose?: () => void } | null, exportArchive?: (options: { archiveBaseName?: string, sceneDescription?: any }) => Promise<{ archiveName: string, archiveBytes: Uint8Array, exportedEntries: any[], skippedEntries: any[] }>, downloadArchive?: (archiveName: string, archiveBytes: Uint8Array) => Promise<void> | void, renderAdjustmentControlsInSelection?: boolean, setLoadingVisible?: (visible: boolean) => void, translate?: ((key: string) => string) | null }} [options]
|
|
69
86
|
*/
|
|
70
87
|
constructor(viewportNode, documentModel, options = {}) {
|
|
71
88
|
this.#viewportNode = viewportNode
|
|
@@ -81,6 +98,7 @@ export class PcbScene3dController {
|
|
|
81
98
|
this.#selectionNode = this.#rootNode?.querySelector(
|
|
82
99
|
'.scene-3d__selection'
|
|
83
100
|
)
|
|
101
|
+
this.#adjustmentHostNode = null
|
|
84
102
|
this.#listeners = []
|
|
85
103
|
this.#scenePrepClient = options.scenePrepClient || null
|
|
86
104
|
this.#sceneDescription = null
|
|
@@ -99,6 +117,10 @@ export class PcbScene3dController {
|
|
|
99
117
|
this.#translate = PcbScene3dText.createTranslator(
|
|
100
118
|
options.translate || null
|
|
101
119
|
)
|
|
120
|
+
this.#renderAdjustmentControlsInSelection =
|
|
121
|
+
options.renderAdjustmentControlsInSelection !== false
|
|
122
|
+
this.#autoSearchMissingModels =
|
|
123
|
+
options.autoSearchMissingModels !== false
|
|
102
124
|
this.#documentId = String(options.documentId || '')
|
|
103
125
|
this.#onComponentSelectionChange =
|
|
104
126
|
typeof options.onComponentSelectionChange === 'function'
|
|
@@ -107,12 +129,41 @@ export class PcbScene3dController {
|
|
|
107
129
|
this.#isDisposed = false
|
|
108
130
|
this.#runtime = null
|
|
109
131
|
this.#selectionIndex = new Map()
|
|
132
|
+
this.#componentAdjustments = new Map()
|
|
110
133
|
this.#selectedComponentKey = ''
|
|
134
|
+
this.#adjustmentControls = new PcbScene3dAdjustmentControlBinder({
|
|
135
|
+
resolveDesignator: () => this.#selectedComponentKey,
|
|
136
|
+
resolveCurrentAdjustment: (designator) =>
|
|
137
|
+
this.#resolveCurrentAdjustment(designator),
|
|
138
|
+
resolveBaselineAdjustment: (designator) =>
|
|
139
|
+
this.#resolveBaselineAdjustment(designator),
|
|
140
|
+
setAdjustment: (designator, adjustment) => {
|
|
141
|
+
this.#componentAdjustments.set(designator, adjustment)
|
|
142
|
+
},
|
|
143
|
+
deleteAdjustment: (designator) => {
|
|
144
|
+
this.#componentAdjustments.delete(designator)
|
|
145
|
+
},
|
|
146
|
+
applyRuntimeAdjustment: (designator, adjustment) => {
|
|
147
|
+
this.#runtime?.setComponentAdjustment?.(designator, adjustment)
|
|
148
|
+
},
|
|
149
|
+
setSelectionHighlightSuppressed: (suppressed) => {
|
|
150
|
+
this.#runtime?.setSelectedDesignator?.(
|
|
151
|
+
suppressed ? '' : this.#selectedComponentKey
|
|
152
|
+
)
|
|
153
|
+
},
|
|
154
|
+
refreshSelection: (designator) => {
|
|
155
|
+
this.#setSelection({
|
|
156
|
+
designator,
|
|
157
|
+
sourceType: this.#resolveSelectionSourceType(designator)
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
})
|
|
111
161
|
|
|
112
162
|
this.#bindPresets()
|
|
113
163
|
this.#setActivePresetButton('isometric')
|
|
114
164
|
this.#bindToggles()
|
|
115
165
|
this.#bindExportAction()
|
|
166
|
+
this.#adjustmentControls.bindSelectionNode(this.#selectionNode)
|
|
116
167
|
this.#setSelection(null)
|
|
117
168
|
this.#setLoadingVisible(true)
|
|
118
169
|
const circuitJsonModel = PcbScene3dController.#resolveCircuitJsonModel(
|
|
@@ -182,6 +233,46 @@ export class PcbScene3dController {
|
|
|
182
233
|
this.#applySelectedComponent()
|
|
183
234
|
}
|
|
184
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Updates app-discovered model visibility without remounting the scene.
|
|
238
|
+
* @param {boolean} enabled Whether app-discovered models should be shown.
|
|
239
|
+
* @returns {void}
|
|
240
|
+
*/
|
|
241
|
+
setAutoSearchMissingModels(enabled) {
|
|
242
|
+
this.#autoSearchMissingModels = enabled === true
|
|
243
|
+
this.#runtime?.setToggle?.(
|
|
244
|
+
'model-search-models',
|
|
245
|
+
this.#autoSearchMissingModels
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Sets an external host for selected-component transform controls.
|
|
251
|
+
* @param {HTMLElement | null} hostNode Sidebar or app-owned controls host.
|
|
252
|
+
* @returns {void}
|
|
253
|
+
*/
|
|
254
|
+
setAdjustmentHost(hostNode) {
|
|
255
|
+
const nextHost =
|
|
256
|
+
hostNode && typeof hostNode === 'object' ? hostNode : null
|
|
257
|
+
if (this.#adjustmentHostNode === nextHost) {
|
|
258
|
+
this.#renderAdjustmentHost()
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
this.#adjustmentHostNode = nextHost
|
|
263
|
+
this.#adjustmentControls?.setHost(this.#adjustmentHostNode)
|
|
264
|
+
this.#setSelection(
|
|
265
|
+
this.#selectedComponentKey
|
|
266
|
+
? {
|
|
267
|
+
designator: this.#selectedComponentKey,
|
|
268
|
+
sourceType: this.#resolveSelectionSourceType(
|
|
269
|
+
this.#selectedComponentKey
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
: null
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
185
276
|
/**
|
|
186
277
|
* Releases event listeners and runtime resources.
|
|
187
278
|
* @returns {void}
|
|
@@ -194,6 +285,8 @@ export class PcbScene3dController {
|
|
|
194
285
|
this.#listeners = []
|
|
195
286
|
this.#scenePrepClient?.dispose?.()
|
|
196
287
|
this.#scenePrepClient = null
|
|
288
|
+
this.#adjustmentControls?.dispose()
|
|
289
|
+
this.#adjustmentControls = null
|
|
197
290
|
this.#runtime?.dispose?.()
|
|
198
291
|
this.#runtime = null
|
|
199
292
|
this.#sceneDescription = null
|
|
@@ -202,6 +295,7 @@ export class PcbScene3dController {
|
|
|
202
295
|
this.#rootNode = null
|
|
203
296
|
this.#diagnosticsNode = null
|
|
204
297
|
this.#selectionNode = null
|
|
298
|
+
this.#adjustmentHostNode = null
|
|
205
299
|
this.#selectedComponentKey = ''
|
|
206
300
|
this.#exportArchive = async () => ({
|
|
207
301
|
archiveName: '',
|
|
@@ -211,6 +305,7 @@ export class PcbScene3dController {
|
|
|
211
305
|
})
|
|
212
306
|
this.#downloadArchive = async () => {}
|
|
213
307
|
this.#selectionIndex = new Map()
|
|
308
|
+
this.#componentAdjustments = new Map()
|
|
214
309
|
}
|
|
215
310
|
|
|
216
311
|
/**
|
|
@@ -350,6 +445,9 @@ export class PcbScene3dController {
|
|
|
350
445
|
this.#handleRuntimeSelection(selection),
|
|
351
446
|
translate: this.#translate
|
|
352
447
|
})
|
|
448
|
+
if (!this.#autoSearchMissingModels) {
|
|
449
|
+
this.#runtime?.setToggle?.('model-search-models', false)
|
|
450
|
+
}
|
|
353
451
|
if (this.#selectedComponentKey) {
|
|
354
452
|
this.#applySelectedComponent()
|
|
355
453
|
}
|
|
@@ -608,153 +706,133 @@ export class PcbScene3dController {
|
|
|
608
706
|
* @returns {void}
|
|
609
707
|
*/
|
|
610
708
|
#setSelection(selection) {
|
|
611
|
-
if (!this.#selectionNode) {
|
|
612
|
-
return
|
|
613
|
-
}
|
|
614
|
-
|
|
615
709
|
const designator = String(selection?.designator || '').trim()
|
|
616
710
|
if (!designator) {
|
|
617
|
-
this.#selectionNode
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
this.#translate('scene3d.inspectPrompt')
|
|
625
|
-
) +
|
|
626
|
-
'</p>'
|
|
711
|
+
if (this.#selectionNode) {
|
|
712
|
+
this.#selectionNode.innerHTML =
|
|
713
|
+
PcbScene3dSelectionInspectorRenderer.renderEmpty(
|
|
714
|
+
this.#translate
|
|
715
|
+
)
|
|
716
|
+
}
|
|
717
|
+
this.#renderAdjustmentHost(null)
|
|
627
718
|
return
|
|
628
719
|
}
|
|
629
720
|
|
|
630
721
|
const selectionEntry = this.#selectionIndex.get(designator)
|
|
631
722
|
if (!selectionEntry) {
|
|
723
|
+
if (this.#selectionNode) {
|
|
724
|
+
this.#selectionNode.innerHTML =
|
|
725
|
+
PcbScene3dSelectionInspectorRenderer.renderMissing(
|
|
726
|
+
designator,
|
|
727
|
+
this.#translate
|
|
728
|
+
)
|
|
729
|
+
}
|
|
730
|
+
this.#renderAdjustmentHost({
|
|
731
|
+
designator,
|
|
732
|
+
selectionEntry: null
|
|
733
|
+
})
|
|
734
|
+
return
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const adjustment = this.#resolveCurrentAdjustment(designator)
|
|
738
|
+
if (this.#selectionNode) {
|
|
632
739
|
this.#selectionNode.innerHTML =
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
740
|
+
PcbScene3dSelectionInspectorRenderer.renderSelected({
|
|
741
|
+
designator,
|
|
742
|
+
selection,
|
|
743
|
+
selectionEntry,
|
|
744
|
+
adjustment,
|
|
745
|
+
includeControls:
|
|
746
|
+
this.#renderAdjustmentControlsInSelection &&
|
|
747
|
+
!this.#adjustmentHostNode,
|
|
748
|
+
translate: this.#translate
|
|
749
|
+
})
|
|
750
|
+
}
|
|
751
|
+
this.#renderAdjustmentHost({
|
|
752
|
+
designator,
|
|
753
|
+
selection,
|
|
754
|
+
selectionEntry,
|
|
755
|
+
adjustment
|
|
756
|
+
})
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Renders selected component transform controls into the external host.
|
|
761
|
+
* @param {{ designator?: string, selection?: { sourceType?: string } | null, selectionEntry?: { component: any | null, externalPlacement: any | null } | null, adjustment?: { scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } } } | null} state Selection state.
|
|
762
|
+
* @returns {void}
|
|
763
|
+
*/
|
|
764
|
+
#renderAdjustmentHost(state = null) {
|
|
765
|
+
if (!this.#adjustmentHostNode) {
|
|
644
766
|
return
|
|
645
767
|
}
|
|
646
768
|
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
? this.#translate('scene3d.externalModel')
|
|
655
|
-
: this.#translate('scene3d.fallbackBody')
|
|
656
|
-
],
|
|
657
|
-
[
|
|
658
|
-
this.#translate('scene3d.mountSide'),
|
|
659
|
-
externalPlacement?.mountSide || component?.mountSide || ''
|
|
660
|
-
],
|
|
661
|
-
[
|
|
662
|
-
this.#translate('scene3d.rotation'),
|
|
663
|
-
PcbScene3dController.#formatMilValue(
|
|
664
|
-
component?.rotationDeg ?? externalPlacement?.rotationDeg,
|
|
665
|
-
'deg'
|
|
666
|
-
)
|
|
667
|
-
],
|
|
668
|
-
[
|
|
669
|
-
this.#translate('scene3d.boardPosition'),
|
|
670
|
-
component?.boardPositionMil
|
|
671
|
-
? PcbScene3dController.#formatPoint(
|
|
672
|
-
component.boardPositionMil,
|
|
673
|
-
true
|
|
674
|
-
)
|
|
675
|
-
: ''
|
|
676
|
-
],
|
|
677
|
-
[
|
|
678
|
-
this.#translate('scene3d.pattern'),
|
|
679
|
-
String(component?.pattern || '')
|
|
680
|
-
],
|
|
681
|
-
[
|
|
682
|
-
this.#translate('scene3d.source'),
|
|
683
|
-
String(component?.source || '')
|
|
684
|
-
],
|
|
685
|
-
[
|
|
686
|
-
this.#translate('scene3d.model'),
|
|
687
|
-
externalPlacement?.externalModel
|
|
688
|
-
? String(externalPlacement.externalModel.name || '') +
|
|
689
|
-
' (' +
|
|
690
|
-
String(externalPlacement.externalModel.format || '') +
|
|
691
|
-
')'
|
|
692
|
-
: component?.externalModel
|
|
693
|
-
? String(component.externalModel.name || '') +
|
|
694
|
-
' (' +
|
|
695
|
-
String(component.externalModel.format || '') +
|
|
696
|
-
')'
|
|
697
|
-
: ''
|
|
698
|
-
],
|
|
699
|
-
[
|
|
700
|
-
this.#translate('scene3d.bodyPosition'),
|
|
701
|
-
externalPlacement?.bodyPositionMil
|
|
702
|
-
? PcbScene3dController.#formatPoint(
|
|
703
|
-
externalPlacement.bodyPositionMil,
|
|
704
|
-
false
|
|
705
|
-
)
|
|
706
|
-
: ''
|
|
707
|
-
],
|
|
708
|
-
[
|
|
709
|
-
this.#translate('scene3d.bodyRotation'),
|
|
710
|
-
PcbScene3dController.#formatMilValue(
|
|
711
|
-
externalPlacement?.bodyRotationDeg,
|
|
712
|
-
'deg'
|
|
713
|
-
)
|
|
714
|
-
],
|
|
715
|
-
[
|
|
716
|
-
this.#translate('scene3d.modelRotation'),
|
|
717
|
-
externalPlacement?.modelTransform?.rotationDeg
|
|
718
|
-
? 'X ' +
|
|
719
|
-
PcbScene3dController.#formatNumber(
|
|
720
|
-
externalPlacement.modelTransform.rotationDeg.x
|
|
721
|
-
) +
|
|
722
|
-
', Y ' +
|
|
723
|
-
PcbScene3dController.#formatNumber(
|
|
724
|
-
externalPlacement.modelTransform.rotationDeg.y
|
|
725
|
-
) +
|
|
726
|
-
', Z ' +
|
|
727
|
-
PcbScene3dController.#formatNumber(
|
|
728
|
-
externalPlacement.modelTransform.rotationDeg.z
|
|
729
|
-
)
|
|
730
|
-
: ''
|
|
731
|
-
],
|
|
732
|
-
[
|
|
733
|
-
'dz',
|
|
734
|
-
PcbScene3dController.#formatMilValue(
|
|
735
|
-
externalPlacement?.modelTransform?.dzMil,
|
|
736
|
-
'mil'
|
|
769
|
+
const designator = String(
|
|
770
|
+
state?.designator || this.#selectedComponentKey || ''
|
|
771
|
+
).trim()
|
|
772
|
+
if (!designator) {
|
|
773
|
+
this.#adjustmentHostNode.innerHTML =
|
|
774
|
+
PcbScene3dSelectionInspectorRenderer.renderControlsEmpty(
|
|
775
|
+
this.#translate
|
|
737
776
|
)
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
'<div class="scene-3d__selection-field"><dt>' +
|
|
751
|
-
PcbScene3dController.#escapeHtml(label) +
|
|
752
|
-
'</dt><dd>' +
|
|
753
|
-
PcbScene3dController.#escapeHtml(String(value)) +
|
|
754
|
-
'</dd></div>'
|
|
777
|
+
return
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const selectionEntry =
|
|
781
|
+
state?.selectionEntry === undefined
|
|
782
|
+
? this.#selectionIndex.get(designator)
|
|
783
|
+
: state.selectionEntry
|
|
784
|
+
if (!selectionEntry) {
|
|
785
|
+
this.#adjustmentHostNode.innerHTML =
|
|
786
|
+
PcbScene3dSelectionInspectorRenderer.renderControlsMissing(
|
|
787
|
+
designator,
|
|
788
|
+
this.#translate
|
|
755
789
|
)
|
|
756
|
-
|
|
757
|
-
|
|
790
|
+
return
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
this.#adjustmentHostNode.innerHTML =
|
|
794
|
+
PcbScene3dSelectionInspectorRenderer.renderControlsPanel({
|
|
795
|
+
designator,
|
|
796
|
+
adjustment:
|
|
797
|
+
state?.adjustment ||
|
|
798
|
+
this.#resolveCurrentAdjustment(designator),
|
|
799
|
+
translate: this.#translate
|
|
800
|
+
})
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Resolves the current in-memory adjustment for one component.
|
|
805
|
+
* @param {string} designator Component designator.
|
|
806
|
+
* @returns {{ scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }}
|
|
807
|
+
*/
|
|
808
|
+
#resolveCurrentAdjustment(designator) {
|
|
809
|
+
return (
|
|
810
|
+
this.#componentAdjustments.get(designator) ||
|
|
811
|
+
this.#resolveBaselineAdjustment(designator)
|
|
812
|
+
)
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Resolves the original transform shown before any live edits.
|
|
817
|
+
* @param {string} designator Component designator.
|
|
818
|
+
* @returns {{ scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }}
|
|
819
|
+
*/
|
|
820
|
+
#resolveBaselineAdjustment(designator) {
|
|
821
|
+
return PcbScene3dSelectionInspectorRenderer.resolveBaseline(
|
|
822
|
+
this.#selectionIndex,
|
|
823
|
+
designator
|
|
824
|
+
)
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Resolves the current source type label for one selected component.
|
|
829
|
+
* @param {string} designator Component designator.
|
|
830
|
+
* @returns {string}
|
|
831
|
+
*/
|
|
832
|
+
#resolveSelectionSourceType(designator) {
|
|
833
|
+
return this.#selectionIndex.get(designator)?.externalPlacement
|
|
834
|
+
? 'external-model'
|
|
835
|
+
: 'component'
|
|
758
836
|
}
|
|
759
837
|
|
|
760
838
|
/**
|
|
@@ -837,67 +915,6 @@ export class PcbScene3dController {
|
|
|
837
915
|
return index
|
|
838
916
|
}
|
|
839
917
|
|
|
840
|
-
/**
|
|
841
|
-
* Formats one point for the inspector.
|
|
842
|
-
* @param {{ x?: number, y?: number, z?: number }} point
|
|
843
|
-
* @param {boolean} includeZ
|
|
844
|
-
* @returns {string}
|
|
845
|
-
*/
|
|
846
|
-
static #formatPoint(point, includeZ) {
|
|
847
|
-
const values = [
|
|
848
|
-
'X ' + PcbScene3dController.#formatNumber(point?.x),
|
|
849
|
-
'Y ' + PcbScene3dController.#formatNumber(point?.y)
|
|
850
|
-
]
|
|
851
|
-
|
|
852
|
-
if (includeZ) {
|
|
853
|
-
values.push('Z ' + PcbScene3dController.#formatNumber(point?.z))
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
return values.join(', ') + ' mil'
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* Formats one numeric inspector value with an optional unit.
|
|
861
|
-
* @param {number | undefined} value
|
|
862
|
-
* @param {string} unit
|
|
863
|
-
* @returns {string}
|
|
864
|
-
*/
|
|
865
|
-
static #formatMilValue(value, unit) {
|
|
866
|
-
if (!Number.isFinite(Number(value))) {
|
|
867
|
-
return ''
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
return PcbScene3dController.#formatNumber(value) + ' ' + unit
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
/**
|
|
874
|
-
* Formats one number for compact UI display.
|
|
875
|
-
* @param {number | undefined} value
|
|
876
|
-
* @returns {string}
|
|
877
|
-
*/
|
|
878
|
-
static #formatNumber(value) {
|
|
879
|
-
const numericValue = Number(value)
|
|
880
|
-
if (!Number.isFinite(numericValue)) {
|
|
881
|
-
return ''
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
return numericValue.toFixed(2).replace(/\.00$/, '')
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
/**
|
|
888
|
-
* Escapes user-facing HTML values.
|
|
889
|
-
* @param {string} value
|
|
890
|
-
* @returns {string}
|
|
891
|
-
*/
|
|
892
|
-
static #escapeHtml(value) {
|
|
893
|
-
return String(value)
|
|
894
|
-
.replace(/&/g, '&')
|
|
895
|
-
.replace(/</g, '<')
|
|
896
|
-
.replace(/>/g, '>')
|
|
897
|
-
.replace(/"/g, '"')
|
|
898
|
-
.replace(/'/g, ''')
|
|
899
|
-
}
|
|
900
|
-
|
|
901
918
|
/**
|
|
902
919
|
* Resolves one archive base name from the mounted document metadata.
|
|
903
920
|
* @param {{ summary?: { title?: string }, fileName?: string } | null} documentModel
|
|
@@ -263,7 +263,10 @@ export class PcbScene3dCopperFactory {
|
|
|
263
263
|
color: 0xd9a61d,
|
|
264
264
|
roughness: 0.38,
|
|
265
265
|
metalness: 0.55,
|
|
266
|
-
side: THREE.DoubleSide
|
|
266
|
+
side: THREE.DoubleSide,
|
|
267
|
+
polygonOffset: true,
|
|
268
|
+
polygonOffsetFactor: -2,
|
|
269
|
+
polygonOffsetUnits: -2
|
|
267
270
|
})
|
|
268
271
|
}
|
|
269
272
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PcbScene3dCircularCutoutOverlap } from './PcbScene3dCircularCutoutOverlap.mjs'
|
|
2
2
|
import { PcbScene3dCutoutCircleDetector } from './PcbScene3dCutoutCircleDetector.mjs'
|
|
3
|
+
import { PcbScene3dGeometryBoundsResolver } from './PcbScene3dGeometryBoundsResolver.mjs'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Clips filled 2D geometry against drill-cutout polygons.
|
|
@@ -29,7 +30,6 @@ export class PcbScene3dCutoutGeometryFilter {
|
|
|
29
30
|
) {
|
|
30
31
|
return geometry
|
|
31
32
|
}
|
|
32
|
-
|
|
33
33
|
const sourceGeometry =
|
|
34
34
|
geometry.index && geometry.toNonIndexed
|
|
35
35
|
? geometry.toNonIndexed()
|
|
@@ -38,9 +38,16 @@ export class PcbScene3dCutoutGeometryFilter {
|
|
|
38
38
|
if (!position?.count) {
|
|
39
39
|
return geometry
|
|
40
40
|
}
|
|
41
|
-
|
|
42
41
|
const preparedCutouts =
|
|
43
42
|
PcbScene3dCutoutGeometryFilter.#prepareCutouts(cutouts)
|
|
43
|
+
if (
|
|
44
|
+
PcbScene3dGeometryBoundsResolver.missesAllPositionBounds(
|
|
45
|
+
position,
|
|
46
|
+
preparedCutouts,
|
|
47
|
+
PcbScene3dCutoutGeometryFilter.#GEOMETRY_EPSILON
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
return geometry
|
|
44
51
|
const cutoutIndex =
|
|
45
52
|
PcbScene3dCutoutGeometryFilter.#buildCutoutSpatialIndex(
|
|
46
53
|
preparedCutouts
|
|
@@ -69,7 +76,6 @@ export class PcbScene3dCutoutGeometryFilter {
|
|
|
69
76
|
if (!state.changed) {
|
|
70
77
|
return geometry
|
|
71
78
|
}
|
|
72
|
-
|
|
73
79
|
const filteredGeometry = new THREE.BufferGeometry()
|
|
74
80
|
filteredGeometry.setAttribute(
|
|
75
81
|
'position',
|