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
|
@@ -4,6 +4,7 @@ import { PcbScene3dCircuitJsonAdapter } from './PcbScene3dCircuitJsonAdapter.mjs
|
|
|
4
4
|
import { PcbScene3dInteractionHints } from './PcbScene3dInteractionHints.mjs'
|
|
5
5
|
import { PcbScene3dRuntime } from './PcbScene3dRuntime.mjs'
|
|
6
6
|
import { PcbScene3dSelectionInspectorRenderer } from './PcbScene3dSelectionInspectorRenderer.mjs'
|
|
7
|
+
import { PcbScene3dSelectionIndexBuilder } from './PcbScene3dSelectionIndexBuilder.mjs'
|
|
7
8
|
import { PcbScene3dSelectionVisibilityBinder } from './PcbScene3dSelectionVisibilityBinder.mjs'
|
|
8
9
|
import { PcbScene3dText } from './PcbScene3dText.mjs'
|
|
9
10
|
|
|
@@ -37,7 +38,7 @@ export class PcbScene3dController {
|
|
|
37
38
|
/** @type {Array<{ node: EventTarget, type: string, listener: (event: any) => void }>} */
|
|
38
39
|
#listeners
|
|
39
40
|
|
|
40
|
-
/** @type {Map<string, { component: any | null, externalPlacement: any | null }>} */
|
|
41
|
+
/** @type {Map<string, { component: any | null, externalPlacement: any | null, staticBodyPlacement: any | null }>} */
|
|
41
42
|
#selectionIndex
|
|
42
43
|
|
|
43
44
|
/** @type {Map<string, { scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }>} */
|
|
@@ -259,7 +260,12 @@ export class PcbScene3dController {
|
|
|
259
260
|
* @returns {void}
|
|
260
261
|
*/
|
|
261
262
|
setAutoSearchMissingModels(enabled) {
|
|
262
|
-
|
|
263
|
+
const nextEnabled = enabled === true
|
|
264
|
+
if (this.#autoSearchMissingModels === nextEnabled) {
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
this.#autoSearchMissingModels = nextEnabled
|
|
263
269
|
this.#runtime?.setToggle?.(
|
|
264
270
|
'model-search-models',
|
|
265
271
|
this.#autoSearchMissingModels
|
|
@@ -451,7 +457,7 @@ export class PcbScene3dController {
|
|
|
451
457
|
PcbScene3dController.#normalizeSceneDescription(sceneDescription)
|
|
452
458
|
this.#sceneDescription = renderModel
|
|
453
459
|
this.#selectionIndex =
|
|
454
|
-
|
|
460
|
+
PcbScene3dSelectionIndexBuilder.build(renderModel)
|
|
455
461
|
const createRuntime =
|
|
456
462
|
options.createRuntime ||
|
|
457
463
|
((nextViewportNode, nextSceneDescription, hooks) =>
|
|
@@ -716,7 +722,9 @@ export class PcbScene3dController {
|
|
|
716
722
|
this.#selectedComponentKey
|
|
717
723
|
? {
|
|
718
724
|
designator: this.#selectedComponentKey,
|
|
719
|
-
sourceType:
|
|
725
|
+
sourceType: this.#resolveSelectionSourceType(
|
|
726
|
+
this.#selectedComponentKey
|
|
727
|
+
)
|
|
720
728
|
}
|
|
721
729
|
: null
|
|
722
730
|
)
|
|
@@ -783,7 +791,7 @@ export class PcbScene3dController {
|
|
|
783
791
|
|
|
784
792
|
/**
|
|
785
793
|
* Renders selected component transform controls into the external host.
|
|
786
|
-
* @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.
|
|
794
|
+
* @param {{ designator?: string, selection?: { sourceType?: string } | null, selectionEntry?: { component: any | null, externalPlacement: any | null, staticBodyPlacement?: 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.
|
|
787
795
|
* @returns {void}
|
|
788
796
|
*/
|
|
789
797
|
#renderAdjustmentHost(state = null) {
|
|
@@ -855,9 +863,18 @@ export class PcbScene3dController {
|
|
|
855
863
|
* @returns {string}
|
|
856
864
|
*/
|
|
857
865
|
#resolveSelectionSourceType(designator) {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
866
|
+
const selectionEntry = this.#selectionIndex.get(designator)
|
|
867
|
+
if (selectionEntry?.externalPlacement) {
|
|
868
|
+
return 'external-model'
|
|
869
|
+
}
|
|
870
|
+
if (selectionEntry?.component) {
|
|
871
|
+
return 'component'
|
|
872
|
+
}
|
|
873
|
+
if (selectionEntry?.staticBodyPlacement) {
|
|
874
|
+
return 'static-body'
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return 'component'
|
|
861
878
|
}
|
|
862
879
|
|
|
863
880
|
/**
|
|
@@ -893,53 +910,6 @@ export class PcbScene3dController {
|
|
|
893
910
|
: sceneDescription
|
|
894
911
|
}
|
|
895
912
|
|
|
896
|
-
/**
|
|
897
|
-
* Builds one designator-keyed inspector lookup from the scene
|
|
898
|
-
* description.
|
|
899
|
-
* @param {{ components?: any[], externalPlacements?: any[] }} sceneDescription
|
|
900
|
-
* @returns {Map<string, { component: any | null, externalPlacement: any | null }>}
|
|
901
|
-
*/
|
|
902
|
-
static #buildSelectionIndex(sceneDescription) {
|
|
903
|
-
const index = new Map()
|
|
904
|
-
const components = Array.isArray(sceneDescription?.components)
|
|
905
|
-
? sceneDescription.components
|
|
906
|
-
: []
|
|
907
|
-
const externalPlacements = Array.isArray(
|
|
908
|
-
sceneDescription?.externalPlacements
|
|
909
|
-
)
|
|
910
|
-
? sceneDescription.externalPlacements
|
|
911
|
-
: []
|
|
912
|
-
|
|
913
|
-
components.forEach((component) => {
|
|
914
|
-
const designator = String(component?.designator || '').trim()
|
|
915
|
-
if (!designator) {
|
|
916
|
-
return
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
index.set(designator, {
|
|
920
|
-
component,
|
|
921
|
-
externalPlacement:
|
|
922
|
-
index.get(designator)?.externalPlacement || null
|
|
923
|
-
})
|
|
924
|
-
})
|
|
925
|
-
|
|
926
|
-
externalPlacements.forEach((externalPlacement) => {
|
|
927
|
-
const designator = String(
|
|
928
|
-
externalPlacement?.designator || ''
|
|
929
|
-
).trim()
|
|
930
|
-
if (!designator) {
|
|
931
|
-
return
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
index.set(designator, {
|
|
935
|
-
component: index.get(designator)?.component || null,
|
|
936
|
-
externalPlacement
|
|
937
|
-
})
|
|
938
|
-
})
|
|
939
|
-
|
|
940
|
-
return index
|
|
941
|
-
}
|
|
942
|
-
|
|
943
913
|
/**
|
|
944
914
|
* Resolves one archive base name from the mounted document metadata.
|
|
945
915
|
* @param {{ summary?: { title?: string }, fileName?: string } | null} documentModel
|
|
@@ -31,18 +31,27 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
31
31
|
arcs: PcbScene3dCopperDetailFilter.#filterMaskOpenPrimitives(
|
|
32
32
|
detail.arcs
|
|
33
33
|
),
|
|
34
|
+
fills: PcbScene3dCopperDetailFilter.#filterMaskOpenPrimitives(
|
|
35
|
+
detail.fills
|
|
36
|
+
),
|
|
37
|
+
polygons: PcbScene3dCopperDetailFilter.#filterMaskOpenPrimitives(
|
|
38
|
+
detail.polygons
|
|
39
|
+
),
|
|
34
40
|
copperTexts: PcbScene3dCopperDetailFilter.#filterMaskOpenPrimitives(
|
|
35
41
|
detail.copperTexts,
|
|
36
42
|
copperTextMaskMatcher
|
|
37
43
|
),
|
|
38
|
-
vias: PcbScene3dCopperDetailFilter.#filterExposedVias(
|
|
44
|
+
vias: PcbScene3dCopperDetailFilter.#filterExposedVias(
|
|
45
|
+
detail.vias,
|
|
46
|
+
sceneDescription
|
|
47
|
+
)
|
|
39
48
|
}
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
/**
|
|
43
52
|
* Resolves trace-like copper that should be visible through solder mask.
|
|
44
53
|
* @param {object} sceneDescription 3D scene description.
|
|
45
|
-
* @returns {{ tracks: any[], arcs: any[], copperTexts: any[], vias: any[] }}
|
|
54
|
+
* @returns {{ tracks: any[], arcs: any[], fills: any[], polygons: any[], copperTexts: any[], vias: any[] }}
|
|
46
55
|
*/
|
|
47
56
|
static resolveCoveredByMask(sceneDescription) {
|
|
48
57
|
const detail = sceneDescription?.detail || {}
|
|
@@ -52,7 +61,14 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
52
61
|
sceneDescription
|
|
53
62
|
)
|
|
54
63
|
) {
|
|
55
|
-
return {
|
|
64
|
+
return {
|
|
65
|
+
tracks: [],
|
|
66
|
+
arcs: [],
|
|
67
|
+
fills: [],
|
|
68
|
+
polygons: [],
|
|
69
|
+
copperTexts: [],
|
|
70
|
+
vias: []
|
|
71
|
+
}
|
|
56
72
|
}
|
|
57
73
|
|
|
58
74
|
const defaultCovered =
|
|
@@ -69,6 +85,14 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
69
85
|
detail.arcs,
|
|
70
86
|
defaultCovered
|
|
71
87
|
),
|
|
88
|
+
fills: PcbScene3dCopperDetailFilter.#filterMaskCoveredPrimitives(
|
|
89
|
+
detail.fills,
|
|
90
|
+
defaultCovered
|
|
91
|
+
),
|
|
92
|
+
polygons: PcbScene3dCopperDetailFilter.#filterMaskCoveredPrimitives(
|
|
93
|
+
detail.polygons,
|
|
94
|
+
defaultCovered
|
|
95
|
+
),
|
|
72
96
|
copperTexts: [],
|
|
73
97
|
vias: []
|
|
74
98
|
}
|
|
@@ -104,7 +128,10 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
104
128
|
const vias = PcbScene3dCopperDetailFilter.#usesRealisticMasking(
|
|
105
129
|
sceneDescription
|
|
106
130
|
)
|
|
107
|
-
? PcbScene3dCopperDetailFilter.#filterExposedVias(
|
|
131
|
+
? PcbScene3dCopperDetailFilter.#filterExposedVias(
|
|
132
|
+
detail.vias,
|
|
133
|
+
sceneDescription
|
|
134
|
+
)
|
|
108
135
|
: detail.vias || []
|
|
109
136
|
|
|
110
137
|
return PcbScene3dCopperDetailFilter.#appendPadBarrelSpecs(
|
|
@@ -113,6 +140,28 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
113
140
|
)
|
|
114
141
|
}
|
|
115
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Resolves via annuli that should be visible through solder mask.
|
|
145
|
+
* @param {object} sceneDescription 3D scene description.
|
|
146
|
+
* @returns {any[]}
|
|
147
|
+
*/
|
|
148
|
+
static resolveCoveredStandaloneVias(sceneDescription) {
|
|
149
|
+
const detail = sceneDescription?.detail || {}
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
!PcbScene3dCopperDetailFilter.#usesRealisticMasking(
|
|
153
|
+
sceneDescription
|
|
154
|
+
)
|
|
155
|
+
) {
|
|
156
|
+
return []
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return PcbScene3dCopperDetailFilter.#filterMaskCoveredVias(
|
|
160
|
+
detail.vias,
|
|
161
|
+
sceneDescription
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
116
165
|
/**
|
|
117
166
|
* Checks whether one scene should use solder-mask visibility.
|
|
118
167
|
* @param {object} sceneDescription 3D scene description.
|
|
@@ -163,6 +212,8 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
163
212
|
return [
|
|
164
213
|
detail?.tracks,
|
|
165
214
|
detail?.arcs,
|
|
215
|
+
detail?.fills,
|
|
216
|
+
detail?.polygons,
|
|
166
217
|
detail?.copperTexts,
|
|
167
218
|
detail?.vias,
|
|
168
219
|
detail?.pads
|
|
@@ -205,14 +256,40 @@ export class PcbScene3dCopperDetailFilter {
|
|
|
205
256
|
}
|
|
206
257
|
|
|
207
258
|
/**
|
|
208
|
-
* Keeps vias
|
|
259
|
+
* Keeps vias that should expose copper annuli in the 3D view.
|
|
209
260
|
* @param {any[] | undefined} vias Via list.
|
|
261
|
+
* @param {object} sceneDescription 3D scene description.
|
|
210
262
|
* @returns {any[]}
|
|
211
263
|
*/
|
|
212
|
-
static #filterExposedVias(vias) {
|
|
213
|
-
return (vias || []).filter((via) =>
|
|
214
|
-
|
|
215
|
-
|
|
264
|
+
static #filterExposedVias(vias, sceneDescription) {
|
|
265
|
+
return (vias || []).filter((via) =>
|
|
266
|
+
PcbScene3dCopperDetailFilter.#isViaExplicitlyOpen(via)
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Keeps KiCad via annuli that are covered by solder mask.
|
|
272
|
+
* @param {any[] | undefined} vias Via list.
|
|
273
|
+
* @param {object} sceneDescription 3D scene description.
|
|
274
|
+
* @returns {any[]}
|
|
275
|
+
*/
|
|
276
|
+
static #filterMaskCoveredVias(vias, sceneDescription) {
|
|
277
|
+
if (!PcbScene3dCopperDetailFilter.#isKiCadScene(sceneDescription)) {
|
|
278
|
+
return []
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return (vias || []).filter(
|
|
282
|
+
(via) => !PcbScene3dCopperDetailFilter.#isViaExplicitlyOpen(via)
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Checks whether one via explicitly exposes copper on either side.
|
|
288
|
+
* @param {object} via Via primitive.
|
|
289
|
+
* @returns {boolean}
|
|
290
|
+
*/
|
|
291
|
+
static #isViaExplicitlyOpen(via) {
|
|
292
|
+
return via?.isTentingTop === false || via?.isTentingBottom === false
|
|
216
293
|
}
|
|
217
294
|
|
|
218
295
|
/**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { PcbScene3dBoardMaterialPalette } from './PcbScene3dBoardMaterialPalette.mjs'
|
|
2
2
|
import { PcbScene3dCopperDetailFilter } from './PcbScene3dCopperDetailFilter.mjs'
|
|
3
|
+
import { PcbScene3dCopperDrillCutoutBuilder } from './PcbScene3dCopperDrillCutoutBuilder.mjs'
|
|
3
4
|
import { PcbScene3dCopperFactory } from './PcbScene3dCopperFactory.mjs'
|
|
5
|
+
import { PcbScene3dMaskCoveredCopperMaterial } from './PcbScene3dMaskCoveredCopperMaterial.mjs'
|
|
4
6
|
import { PcbScene3dPadFactory } from './PcbScene3dPadFactory.mjs'
|
|
5
7
|
import { PcbScene3dViaFactory } from './PcbScene3dViaFactory.mjs'
|
|
6
8
|
|
|
@@ -9,6 +11,8 @@ import { PcbScene3dViaFactory } from './PcbScene3dViaFactory.mjs'
|
|
|
9
11
|
*/
|
|
10
12
|
export class PcbScene3dCopperDetailGroupBuilder {
|
|
11
13
|
static #CIRCLE_SEGMENTS = 64
|
|
14
|
+
static #COVERED_COPPER_UNDERLAP_MIL = 6
|
|
15
|
+
static #MAX_SILKSCREEN_OCCLUSION_SPAN_MIL = 500
|
|
12
16
|
static #ROUNDED_CORNER_SEGMENTS = 12
|
|
13
17
|
|
|
14
18
|
/**
|
|
@@ -21,11 +25,21 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
21
25
|
*/
|
|
22
26
|
static build(THREE, sceneDescription, topZ, normalizePoint) {
|
|
23
27
|
const group = new THREE.Group()
|
|
28
|
+
const drillCutouts = PcbScene3dCopperDrillCutoutBuilder.resolve(
|
|
29
|
+
sceneDescription?.detail
|
|
30
|
+
)
|
|
24
31
|
const coveredGroup =
|
|
25
32
|
PcbScene3dCopperDetailGroupBuilder.#buildCoveredGroup(
|
|
26
33
|
THREE,
|
|
27
34
|
sceneDescription,
|
|
28
35
|
topZ,
|
|
36
|
+
normalizePoint,
|
|
37
|
+
drillCutouts
|
|
38
|
+
)
|
|
39
|
+
const coveredViaGroup =
|
|
40
|
+
PcbScene3dCopperDetailGroupBuilder.#buildCoveredViaGroup(
|
|
41
|
+
THREE,
|
|
42
|
+
sceneDescription,
|
|
29
43
|
normalizePoint
|
|
30
44
|
)
|
|
31
45
|
const exposedGroup =
|
|
@@ -33,7 +47,8 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
33
47
|
THREE,
|
|
34
48
|
sceneDescription,
|
|
35
49
|
topZ,
|
|
36
|
-
normalizePoint
|
|
50
|
+
normalizePoint,
|
|
51
|
+
drillCutouts
|
|
37
52
|
)
|
|
38
53
|
const viaGroup = PcbScene3dCopperDetailGroupBuilder.#buildViaGroup(
|
|
39
54
|
THREE,
|
|
@@ -41,7 +56,7 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
41
56
|
normalizePoint
|
|
42
57
|
)
|
|
43
58
|
|
|
44
|
-
;[coveredGroup, exposedGroup, viaGroup]
|
|
59
|
+
;[coveredGroup, coveredViaGroup, exposedGroup, viaGroup]
|
|
45
60
|
.filter((child) => child.children.length)
|
|
46
61
|
.forEach((child) => group.add(child))
|
|
47
62
|
|
|
@@ -54,9 +69,16 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
54
69
|
* @param {object} sceneDescription Scene description.
|
|
55
70
|
* @param {number} topZ Top copper center Z.
|
|
56
71
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizePoint
|
|
72
|
+
* @param {{ x: number, y: number }[][]} drillCutouts Board drill cutouts.
|
|
57
73
|
* @returns {any}
|
|
58
74
|
*/
|
|
59
|
-
static #buildCoveredGroup(
|
|
75
|
+
static #buildCoveredGroup(
|
|
76
|
+
THREE,
|
|
77
|
+
sceneDescription,
|
|
78
|
+
topZ,
|
|
79
|
+
normalizePoint,
|
|
80
|
+
drillCutouts
|
|
81
|
+
) {
|
|
60
82
|
return PcbScene3dCopperFactory.buildMaskCoveredGroup(
|
|
61
83
|
THREE,
|
|
62
84
|
PcbScene3dCopperDetailFilter.resolveCoveredByMask(sceneDescription),
|
|
@@ -64,15 +86,11 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
64
86
|
-topZ,
|
|
65
87
|
normalizePoint,
|
|
66
88
|
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
sceneDescription?.boardAssemblyModel
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
),
|
|
89
|
+
...PcbScene3dCopperDetailGroupBuilder.#coveredCopperMaterialOptions(
|
|
90
|
+
sceneDescription
|
|
91
|
+
),
|
|
92
|
+
drillCutouts,
|
|
93
|
+
drillDetail: sceneDescription?.detail,
|
|
76
94
|
occlusionCutouts:
|
|
77
95
|
PcbScene3dCopperDetailGroupBuilder.#resolveCoveredCopperOcclusions(
|
|
78
96
|
sceneDescription?.detail
|
|
@@ -81,22 +99,65 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
81
99
|
)
|
|
82
100
|
}
|
|
83
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Builds solder-mask-covered via annuli.
|
|
104
|
+
* @param {any} THREE Three.js namespace.
|
|
105
|
+
* @param {object} sceneDescription Scene description.
|
|
106
|
+
* @param {(x: number, y: number) => { x: number, y: number }} normalizePoint
|
|
107
|
+
* @returns {any}
|
|
108
|
+
*/
|
|
109
|
+
static #buildCoveredViaGroup(THREE, sceneDescription, normalizePoint) {
|
|
110
|
+
const vias =
|
|
111
|
+
PcbScene3dCopperDetailFilter.resolveCoveredStandaloneVias(
|
|
112
|
+
sceneDescription
|
|
113
|
+
)
|
|
114
|
+
if (!vias.length) {
|
|
115
|
+
return new THREE.Group()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return PcbScene3dViaFactory.buildGroup(
|
|
119
|
+
THREE,
|
|
120
|
+
vias,
|
|
121
|
+
sceneDescription?.board?.thicknessMil,
|
|
122
|
+
normalizePoint,
|
|
123
|
+
{
|
|
124
|
+
material: PcbScene3dMaskCoveredCopperMaterial.build(
|
|
125
|
+
THREE,
|
|
126
|
+
PcbScene3dCopperDetailGroupBuilder.#coveredCopperMaterialOptions(
|
|
127
|
+
sceneDescription
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
84
134
|
/**
|
|
85
135
|
* Builds exposed copper detail.
|
|
86
136
|
* @param {any} THREE Three.js namespace.
|
|
87
137
|
* @param {object} sceneDescription Scene description.
|
|
88
138
|
* @param {number} topZ Top copper center Z.
|
|
89
139
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizePoint
|
|
140
|
+
* @param {{ x: number, y: number }[][]} drillCutouts Board drill cutouts.
|
|
90
141
|
* @returns {any}
|
|
91
142
|
*/
|
|
92
|
-
static #buildExposedGroup(
|
|
143
|
+
static #buildExposedGroup(
|
|
144
|
+
THREE,
|
|
145
|
+
sceneDescription,
|
|
146
|
+
topZ,
|
|
147
|
+
normalizePoint,
|
|
148
|
+
drillCutouts
|
|
149
|
+
) {
|
|
93
150
|
return PcbScene3dCopperFactory.buildGroup(
|
|
94
151
|
THREE,
|
|
95
152
|
PcbScene3dCopperDetailFilter.resolve(sceneDescription),
|
|
96
153
|
topZ,
|
|
97
154
|
-topZ,
|
|
98
155
|
normalizePoint,
|
|
99
|
-
{
|
|
156
|
+
{
|
|
157
|
+
coordinateSystem: sceneDescription?.coordinateSystem,
|
|
158
|
+
drillCutouts,
|
|
159
|
+
drillDetail: sceneDescription?.detail
|
|
160
|
+
}
|
|
100
161
|
)
|
|
101
162
|
}
|
|
102
163
|
|
|
@@ -126,6 +187,24 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
126
187
|
)
|
|
127
188
|
}
|
|
128
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Resolves solder-mask material options for covered copper relief.
|
|
192
|
+
* @param {object} sceneDescription Scene description.
|
|
193
|
+
* @returns {{ solderMaskColor: number }}
|
|
194
|
+
*/
|
|
195
|
+
static #coveredCopperMaterialOptions(sceneDescription) {
|
|
196
|
+
return {
|
|
197
|
+
solderMaskColor: PcbScene3dBoardMaterialPalette.resolveSurfaceColor(
|
|
198
|
+
sceneDescription?.board,
|
|
199
|
+
{
|
|
200
|
+
hasBoardAssemblyModel: Boolean(
|
|
201
|
+
sceneDescription?.boardAssemblyModel
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
129
208
|
/**
|
|
130
209
|
* Resolves opaque silkscreen fill polygons that should hide covered copper.
|
|
131
210
|
* @param {{ top?: object, bottom?: object } | undefined} silkscreen Scene silkscreen detail.
|
|
@@ -168,6 +247,11 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
168
247
|
)
|
|
169
248
|
)
|
|
170
249
|
.filter((points) => points.length >= 3)
|
|
250
|
+
.filter((points) =>
|
|
251
|
+
PcbScene3dCopperDetailGroupBuilder.#isBoundedSilkscreenOcclusion(
|
|
252
|
+
points
|
|
253
|
+
)
|
|
254
|
+
)
|
|
171
255
|
}
|
|
172
256
|
|
|
173
257
|
/**
|
|
@@ -198,6 +282,44 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
198
282
|
]
|
|
199
283
|
}
|
|
200
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Checks whether a silkscreen fill is small enough for copper clipping.
|
|
287
|
+
* @param {{ x: number, y: number }[]} points Fill contour points.
|
|
288
|
+
* @returns {boolean}
|
|
289
|
+
*/
|
|
290
|
+
static #isBoundedSilkscreenOcclusion(points) {
|
|
291
|
+
const bounds =
|
|
292
|
+
PcbScene3dCopperDetailGroupBuilder.#resolveSilkscreenBounds(points)
|
|
293
|
+
const span = Math.max(
|
|
294
|
+
bounds.maxX - bounds.minX,
|
|
295
|
+
bounds.maxY - bounds.minY
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
Number.isFinite(span) &&
|
|
300
|
+
span <=
|
|
301
|
+
PcbScene3dCopperDetailGroupBuilder
|
|
302
|
+
.#MAX_SILKSCREEN_OCCLUSION_SPAN_MIL
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Resolves the bounds for one silkscreen fill contour.
|
|
308
|
+
* @param {{ x: number, y: number }[]} points Fill contour points.
|
|
309
|
+
* @returns {{ minX: number, maxX: number, minY: number, maxY: number }}
|
|
310
|
+
*/
|
|
311
|
+
static #resolveSilkscreenBounds(points) {
|
|
312
|
+
return points.reduce(
|
|
313
|
+
(bounds, point) => ({
|
|
314
|
+
minX: Math.min(bounds.minX, point.x),
|
|
315
|
+
maxX: Math.max(bounds.maxX, point.x),
|
|
316
|
+
minY: Math.min(bounds.minY, point.y),
|
|
317
|
+
maxY: Math.max(bounds.maxY, point.y)
|
|
318
|
+
}),
|
|
319
|
+
{ minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity }
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
|
|
201
323
|
/**
|
|
202
324
|
* Resolves opaque geometry that should hide mask-covered copper.
|
|
203
325
|
* @param {object | undefined} detail Scene detail.
|
|
@@ -318,18 +440,67 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
318
440
|
*/
|
|
319
441
|
static #resolvePadSurfacePolygon(pad, side) {
|
|
320
442
|
const spec = PcbScene3dPadFactory.resolvePadSurfaceSpec(pad, side)
|
|
443
|
+
const occlusionSpec =
|
|
444
|
+
PcbScene3dCopperDetailGroupBuilder.#insetPadSurfaceSpec(spec)
|
|
321
445
|
const center = {
|
|
322
446
|
x: Number(pad?.x || 0) + spec.offsetX,
|
|
323
447
|
y: Number(pad?.y || 0) + spec.offsetY
|
|
324
448
|
}
|
|
325
449
|
|
|
450
|
+
if (!occlusionSpec) {
|
|
451
|
+
return []
|
|
452
|
+
}
|
|
453
|
+
|
|
326
454
|
return PcbScene3dCopperDetailGroupBuilder.#transformPoints(
|
|
327
|
-
PcbScene3dCopperDetailGroupBuilder.#buildPadLocalPoints(
|
|
455
|
+
PcbScene3dCopperDetailGroupBuilder.#buildPadLocalPoints(
|
|
456
|
+
occlusionSpec
|
|
457
|
+
),
|
|
328
458
|
center,
|
|
329
459
|
Number(pad?.rotation || 0)
|
|
330
460
|
)
|
|
331
461
|
}
|
|
332
462
|
|
|
463
|
+
/**
|
|
464
|
+
* Insets a pad occlusion so mask-covered traces can visually tuck below
|
|
465
|
+
* exposed copper sidewalls instead of ending at the same vertical plane.
|
|
466
|
+
* @param {{ width: number, height: number, kind: string, cornerRadius: number }} spec Pad surface spec.
|
|
467
|
+
* @returns {{ width: number, height: number, kind: string, cornerRadius: number } | null}
|
|
468
|
+
*/
|
|
469
|
+
static #insetPadSurfaceSpec(spec) {
|
|
470
|
+
const inset = PcbScene3dCopperDetailGroupBuilder.#resolvePadInset(spec)
|
|
471
|
+
const width = Math.max(Number(spec?.width || 0) - inset * 2, 0)
|
|
472
|
+
const height = Math.max(Number(spec?.height || 0) - inset * 2, 0)
|
|
473
|
+
|
|
474
|
+
if (width <= 0 || height <= 0) {
|
|
475
|
+
return null
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
...spec,
|
|
480
|
+
width,
|
|
481
|
+
height,
|
|
482
|
+
radius: Math.max(width, height) / 2,
|
|
483
|
+
cornerRadius: Math.max(Number(spec?.cornerRadius || 0) - inset, 0)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Resolves a bounded pad occlusion inset.
|
|
489
|
+
* @param {{ width: number, height: number }} spec Pad surface spec.
|
|
490
|
+
* @returns {number}
|
|
491
|
+
*/
|
|
492
|
+
static #resolvePadInset(spec) {
|
|
493
|
+
return Math.min(
|
|
494
|
+
PcbScene3dCopperDetailGroupBuilder.#COVERED_COPPER_UNDERLAP_MIL,
|
|
495
|
+
Math.max(
|
|
496
|
+
Math.min(Number(spec?.width || 0), Number(spec?.height || 0)) /
|
|
497
|
+
2 -
|
|
498
|
+
0.001,
|
|
499
|
+
0
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
|
|
333
504
|
/**
|
|
334
505
|
* Builds local points for one pad face.
|
|
335
506
|
* @param {{ width: number, height: number, kind: string, cornerRadius: number }} spec Pad surface spec.
|