pcb-scene3d-viewer 1.1.49 → 1.1.50
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/package.json +1 -1
- package/src/PcbScene3dBoardAssemblyPresentation.mjs +1 -1
- package/src/PcbScene3dBoardMaterialPalette.mjs +29 -0
- package/src/PcbScene3dBoardSolderMaskFactory.mjs +7 -4
- package/src/PcbScene3dDrillVoidFactory.mjs +29 -5
- package/src/PcbScene3dGeometryZCompressor.mjs +4 -2
- package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +18 -4
- package/src/PcbScene3dMaskCoveredCopperSurfaceFilter.mjs +75 -10
- package/src/PcbScene3dRuntime.mjs +5 -5
- package/src/PcbScene3dRuntimeBoardMeshes.mjs +1 -1
package/package.json
CHANGED
|
@@ -420,7 +420,7 @@ export class PcbScene3dBoardAssemblyPresentation {
|
|
|
420
420
|
* @returns {number}
|
|
421
421
|
*/
|
|
422
422
|
static #resolveSurfaceColor(board, options) {
|
|
423
|
-
return PcbScene3dBoardMaterialPalette.
|
|
423
|
+
return PcbScene3dBoardMaterialPalette.resolveBoardSurfaceColor(board, {
|
|
424
424
|
hasBoardAssemblyModel: true,
|
|
425
425
|
sourceFormat: options?.sourceFormat
|
|
426
426
|
})
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export class PcbScene3dBoardMaterialPalette {
|
|
5
5
|
static #DEFAULT_SURFACE_COLOR = 0x2a5f27
|
|
6
|
+
static #BOARD_SURFACE_DARKEN_RATIO = 0.88
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Resolves the solder-mask face color for the generated board shell.
|
|
@@ -25,6 +26,19 @@ export class PcbScene3dBoardMaterialPalette {
|
|
|
25
26
|
: PcbScene3dBoardMaterialPalette.#DEFAULT_SURFACE_COLOR
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Resolves the darker display color for visible board solder-mask faces.
|
|
31
|
+
* @param {{ surfaceColor?: number } | undefined} board Board metadata.
|
|
32
|
+
* @param {{ hasBoardAssemblyModel?: boolean, sourceFormat?: string }} [options] Scene options.
|
|
33
|
+
* @returns {number}
|
|
34
|
+
*/
|
|
35
|
+
static resolveBoardSurfaceColor(board, options = {}) {
|
|
36
|
+
return PcbScene3dBoardMaterialPalette.#darkenHexColor(
|
|
37
|
+
PcbScene3dBoardMaterialPalette.resolveSurfaceColor(board, options),
|
|
38
|
+
PcbScene3dBoardMaterialPalette.#BOARD_SURFACE_DARKEN_RATIO
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
/**
|
|
29
43
|
* Returns true when the source format supplies display-stable board colors.
|
|
30
44
|
* @param {{ sourceFormat?: string }} options Scene options.
|
|
@@ -52,4 +66,19 @@ export class PcbScene3dBoardMaterialPalette {
|
|
|
52
66
|
static isGeneratedBodyVisible(options = {}) {
|
|
53
67
|
return true
|
|
54
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Darkens a packed RGB color while preserving its hue.
|
|
72
|
+
* @param {number} color Packed RGB color.
|
|
73
|
+
* @param {number} ratio Channel multiplier.
|
|
74
|
+
* @returns {number}
|
|
75
|
+
*/
|
|
76
|
+
static #darkenHexColor(color, ratio) {
|
|
77
|
+
const multiplier = Math.min(Math.max(Number(ratio || 0), 0), 1)
|
|
78
|
+
|
|
79
|
+
return [16, 8, 0].reduce((output, shift) => {
|
|
80
|
+
const channel = Math.round(((color >> shift) & 255) * multiplier)
|
|
81
|
+
return output | (channel << shift)
|
|
82
|
+
}, 0)
|
|
83
|
+
}
|
|
55
84
|
}
|
|
@@ -609,10 +609,13 @@ export class PcbScene3dBoardSolderMaskFactory {
|
|
|
609
609
|
*/
|
|
610
610
|
static #buildMaterial(THREE, board, side, sourceFormat) {
|
|
611
611
|
return new THREE.MeshStandardMaterial({
|
|
612
|
-
color: PcbScene3dBoardMaterialPalette.
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
612
|
+
color: PcbScene3dBoardMaterialPalette.resolveBoardSurfaceColor(
|
|
613
|
+
board,
|
|
614
|
+
{
|
|
615
|
+
hasBoardAssemblyModel: true,
|
|
616
|
+
sourceFormat
|
|
617
|
+
}
|
|
618
|
+
),
|
|
616
619
|
...PcbScene3dMaterialFinish.semiMatteSolderMaskProperties(),
|
|
617
620
|
polygonOffset: true,
|
|
618
621
|
polygonOffsetFactor: -1,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PcbScene3dBoardShapeFactory } from './PcbScene3dBoardShapeFactory.mjs'
|
|
2
|
+
import { PcbScene3dBoardMaterialPalette } from './PcbScene3dBoardMaterialPalette.mjs'
|
|
2
3
|
import { PcbScene3dDrillPathFactory } from './PcbScene3dDrillPathFactory.mjs'
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -16,7 +17,7 @@ export class PcbScene3dDrillVoidFactory {
|
|
|
16
17
|
* @param {number} [topZ]
|
|
17
18
|
* @param {number} [bottomZ]
|
|
18
19
|
* @param {(x: number, y: number) => { x: number, y: number }} [normalizeBoardPoint]
|
|
19
|
-
* @param {{ enabled?: boolean, color?: number, board?: object }} [options]
|
|
20
|
+
* @param {{ enabled?: boolean, color?: number, board?: object, hasBoardAssemblyModel?: boolean, sourceFormat?: string }} [options]
|
|
20
21
|
* @returns {any}
|
|
21
22
|
*/
|
|
22
23
|
static buildGroup(
|
|
@@ -113,20 +114,43 @@ export class PcbScene3dDrillVoidFactory {
|
|
|
113
114
|
/**
|
|
114
115
|
* Builds the shared drill-interior material.
|
|
115
116
|
* @param {any} THREE
|
|
116
|
-
* @param {{ color?: number }} options
|
|
117
|
+
* @param {{ color?: number, board?: object, hasBoardAssemblyModel?: boolean, sourceFormat?: string }} options
|
|
117
118
|
* @returns {any}
|
|
118
119
|
*/
|
|
119
120
|
static #buildInteriorMaterial(THREE, options) {
|
|
120
121
|
return new THREE.MeshStandardMaterial({
|
|
121
|
-
color:
|
|
122
|
-
? options.color
|
|
123
|
-
: PcbScene3dDrillVoidFactory.#DEFAULT_INTERIOR_COLOR,
|
|
122
|
+
color: PcbScene3dDrillVoidFactory.#resolveInteriorColor(options),
|
|
124
123
|
roughness: 0.82,
|
|
125
124
|
metalness: 0,
|
|
126
125
|
side: THREE.DoubleSide
|
|
127
126
|
})
|
|
128
127
|
}
|
|
129
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Resolves the drill-interior display color.
|
|
131
|
+
* @param {{ color?: number, board?: object, hasBoardAssemblyModel?: boolean, sourceFormat?: string }} options
|
|
132
|
+
* @returns {number}
|
|
133
|
+
*/
|
|
134
|
+
static #resolveInteriorColor(options) {
|
|
135
|
+
if (Number.isInteger(options?.color)) {
|
|
136
|
+
return options.color
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (options?.board) {
|
|
140
|
+
return PcbScene3dBoardMaterialPalette.resolveBoardSurfaceColor(
|
|
141
|
+
options.board,
|
|
142
|
+
{
|
|
143
|
+
hasBoardAssemblyModel: Boolean(
|
|
144
|
+
options?.hasBoardAssemblyModel
|
|
145
|
+
),
|
|
146
|
+
sourceFormat: options?.sourceFormat
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return PcbScene3dDrillVoidFactory.#DEFAULT_INTERIOR_COLOR
|
|
152
|
+
}
|
|
153
|
+
|
|
130
154
|
/**
|
|
131
155
|
* Checks whether one drill is a routed slot.
|
|
132
156
|
* @param {{ diameter?: number, slotLength?: number | null }} drillSpec Drill spec.
|
|
@@ -9,7 +9,7 @@ export class PcbScene3dGeometryZCompressor {
|
|
|
9
9
|
* Rewrites mask-covered copper relief below exposed copper height.
|
|
10
10
|
* @param {any | null} mesh Mesh with a position attribute.
|
|
11
11
|
* @param {number} sourceCenterZ Original geometry center Z.
|
|
12
|
-
* @param {{ centerOffsetMil?: number }} [options] Compression options.
|
|
12
|
+
* @param {{ centerOffsetMil?: number, thicknessMil?: number }} [options] Compression options.
|
|
13
13
|
* @returns {void}
|
|
14
14
|
*/
|
|
15
15
|
static compressMaskCoveredCopperMesh(mesh, sourceCenterZ, options = {}) {
|
|
@@ -19,7 +19,9 @@ export class PcbScene3dGeometryZCompressor {
|
|
|
19
19
|
sourceCenterZ +
|
|
20
20
|
MASK_COVERED_CENTER_OFFSET_MIL +
|
|
21
21
|
Number(options?.centerOffsetMil || 0),
|
|
22
|
-
|
|
22
|
+
Number.isFinite(Number(options?.thicknessMil))
|
|
23
|
+
? Math.max(Number(options.thicknessMil), 0)
|
|
24
|
+
: MASK_COVERED_THICKNESS_MIL
|
|
23
25
|
)
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -10,7 +10,8 @@ export class PcbScene3dMaskCoveredCopperSideGroupBuilder {
|
|
|
10
10
|
static #TRACK_COPPER_BLEND = 0.7
|
|
11
11
|
static #FILL_RENDER_ORDER = 10
|
|
12
12
|
static #TRACK_RENDER_ORDER = 12
|
|
13
|
-
static #TRACK_CENTER_OFFSET_MIL = 0.
|
|
13
|
+
static #TRACK_CENTER_OFFSET_MIL = 0.25
|
|
14
|
+
static #TRACK_THICKNESS_MIL = 1.05
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Builds one side group from prepared mask-covered copper meshes.
|
|
@@ -39,6 +40,10 @@ export class PcbScene3dMaskCoveredCopperSideGroupBuilder {
|
|
|
39
40
|
centerOffsetMil:
|
|
40
41
|
PcbScene3dMaskCoveredCopperSideGroupBuilder
|
|
41
42
|
.#TRACK_CENTER_OFFSET_MIL,
|
|
43
|
+
thicknessMil:
|
|
44
|
+
PcbScene3dMaskCoveredCopperSideGroupBuilder
|
|
45
|
+
.#TRACK_THICKNESS_MIL,
|
|
46
|
+
keepSideWalls: true,
|
|
42
47
|
copperBlend:
|
|
43
48
|
PcbScene3dMaskCoveredCopperSideGroupBuilder
|
|
44
49
|
.#TRACK_COPPER_BLEND,
|
|
@@ -56,6 +61,10 @@ export class PcbScene3dMaskCoveredCopperSideGroupBuilder {
|
|
|
56
61
|
centerOffsetMil:
|
|
57
62
|
PcbScene3dMaskCoveredCopperSideGroupBuilder
|
|
58
63
|
.#TRACK_CENTER_OFFSET_MIL,
|
|
64
|
+
thicknessMil:
|
|
65
|
+
PcbScene3dMaskCoveredCopperSideGroupBuilder
|
|
66
|
+
.#TRACK_THICKNESS_MIL,
|
|
67
|
+
keepSideWalls: true,
|
|
59
68
|
copperBlend:
|
|
60
69
|
PcbScene3dMaskCoveredCopperSideGroupBuilder
|
|
61
70
|
.#TRACK_COPPER_BLEND,
|
|
@@ -92,7 +101,7 @@ export class PcbScene3dMaskCoveredCopperSideGroupBuilder {
|
|
|
92
101
|
* @param {any | null} mesh Mesh to add.
|
|
93
102
|
* @param {string} name Scene object name.
|
|
94
103
|
* @param {number} z Source center Z.
|
|
95
|
-
* @param {{ centerOffsetMil?: number, copperBlend?: number, renderOrder?: number }} [options] Presentation options.
|
|
104
|
+
* @param {{ centerOffsetMil?: number, thicknessMil?: number, keepSideWalls?: boolean, copperBlend?: number, renderOrder?: number }} [options] Presentation options.
|
|
96
105
|
* @returns {void}
|
|
97
106
|
*/
|
|
98
107
|
static #addCompressedMesh(group, mesh, name, z, options = {}) {
|
|
@@ -100,9 +109,14 @@ export class PcbScene3dMaskCoveredCopperSideGroupBuilder {
|
|
|
100
109
|
return
|
|
101
110
|
}
|
|
102
111
|
PcbScene3dGeometryZCompressor.compressMaskCoveredCopperMesh(mesh, z, {
|
|
103
|
-
centerOffsetMil: options.centerOffsetMil
|
|
112
|
+
centerOffsetMil: options.centerOffsetMil,
|
|
113
|
+
thicknessMil: options.thicknessMil
|
|
104
114
|
})
|
|
105
|
-
|
|
115
|
+
if (options.keepSideWalls === true) {
|
|
116
|
+
PcbScene3dMaskCoveredCopperSurfaceFilter.keepOuterRelief(mesh)
|
|
117
|
+
} else {
|
|
118
|
+
PcbScene3dMaskCoveredCopperSurfaceFilter.keepOuterSurface(mesh)
|
|
119
|
+
}
|
|
106
120
|
mesh.name = name
|
|
107
121
|
mesh.renderOrder = Number(options.renderOrder || 0)
|
|
108
122
|
PcbScene3dMaskCoveredCopperSideGroupBuilder.#applyCopperTint(
|
|
@@ -10,6 +10,29 @@ export class PcbScene3dMaskCoveredCopperSurfaceFilter {
|
|
|
10
10
|
* @returns {void}
|
|
11
11
|
*/
|
|
12
12
|
static keepOuterSurface(mesh) {
|
|
13
|
+
PcbScene3dMaskCoveredCopperSurfaceFilter.#filterOuterTriangles(mesh, {
|
|
14
|
+
keepSideWalls: false
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Removes the hidden underside while keeping shallow edge walls.
|
|
20
|
+
* @param {any | null} mesh Copper relief mesh.
|
|
21
|
+
* @returns {void}
|
|
22
|
+
*/
|
|
23
|
+
static keepOuterRelief(mesh) {
|
|
24
|
+
PcbScene3dMaskCoveredCopperSurfaceFilter.#filterOuterTriangles(mesh, {
|
|
25
|
+
keepSideWalls: true
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Keeps only triangles that are visible above the solder mask.
|
|
31
|
+
* @param {any | null} mesh Copper relief mesh.
|
|
32
|
+
* @param {{ keepSideWalls?: boolean }} options Filter options.
|
|
33
|
+
* @returns {void}
|
|
34
|
+
*/
|
|
35
|
+
static #filterOuterTriangles(mesh, options) {
|
|
13
36
|
const geometry = mesh?.geometry
|
|
14
37
|
const position = geometry?.getAttribute?.('position')
|
|
15
38
|
const source = position?.array
|
|
@@ -17,14 +40,16 @@ export class PcbScene3dMaskCoveredCopperSurfaceFilter {
|
|
|
17
40
|
return
|
|
18
41
|
}
|
|
19
42
|
|
|
20
|
-
const
|
|
43
|
+
const zBounds =
|
|
44
|
+
PcbScene3dMaskCoveredCopperSurfaceFilter.#zBounds(source)
|
|
21
45
|
const filtered = []
|
|
22
46
|
for (let index = 0; index + 8 < source.length; index += 9) {
|
|
23
47
|
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
PcbScene3dMaskCoveredCopperSurfaceFilter.#keepsTriangle(
|
|
49
|
+
source,
|
|
50
|
+
index,
|
|
51
|
+
zBounds,
|
|
52
|
+
options
|
|
28
53
|
)
|
|
29
54
|
) {
|
|
30
55
|
filtered.push(...source.slice(index, index + 9))
|
|
@@ -50,15 +75,55 @@ export class PcbScene3dMaskCoveredCopperSurfaceFilter {
|
|
|
50
75
|
}
|
|
51
76
|
|
|
52
77
|
/**
|
|
53
|
-
*
|
|
78
|
+
* Checks whether one triangle should stay in the visible relief mesh.
|
|
79
|
+
* @param {ArrayLike<number>} source Position buffer.
|
|
80
|
+
* @param {number} index Triangle start index.
|
|
81
|
+
* @param {{ minZ: number, maxZ: number }} zBounds Geometry Z bounds.
|
|
82
|
+
* @param {{ keepSideWalls?: boolean }} options Filter options.
|
|
83
|
+
* @returns {boolean}
|
|
84
|
+
*/
|
|
85
|
+
static #keepsTriangle(source, index, zBounds, options) {
|
|
86
|
+
const zValues = [2, 5, 8].map((offset) => source[index + offset])
|
|
87
|
+
const hasTop = zValues.some((z) =>
|
|
88
|
+
PcbScene3dMaskCoveredCopperSurfaceFilter.#matchesZ(z, zBounds.maxZ)
|
|
89
|
+
)
|
|
90
|
+
const hasBottom = zValues.some((z) =>
|
|
91
|
+
PcbScene3dMaskCoveredCopperSurfaceFilter.#matchesZ(z, zBounds.minZ)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if (hasTop && !hasBottom) {
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return options?.keepSideWalls === true && hasTop && hasBottom
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Checks whether a Z value matches one target plane.
|
|
103
|
+
* @param {number} value Candidate Z.
|
|
104
|
+
* @param {number} target Target Z.
|
|
105
|
+
* @returns {boolean}
|
|
106
|
+
*/
|
|
107
|
+
static #matchesZ(value, target) {
|
|
108
|
+
return (
|
|
109
|
+
Math.abs(Number(value) - Number(target)) <=
|
|
110
|
+
PcbScene3dMaskCoveredCopperSurfaceFilter.#Z_EPSILON
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Resolves the lowest and highest Z planes in one packed XYZ buffer.
|
|
54
116
|
* @param {ArrayLike<number>} source Position buffer.
|
|
55
|
-
* @returns {number}
|
|
117
|
+
* @returns {{ minZ: number, maxZ: number }}
|
|
56
118
|
*/
|
|
57
|
-
static #
|
|
119
|
+
static #zBounds(source) {
|
|
120
|
+
let minZ = Infinity
|
|
58
121
|
let maxZ = -Infinity
|
|
59
122
|
for (let index = 2; index < source.length; index += 3) {
|
|
60
|
-
|
|
123
|
+
const z = Number(source[index])
|
|
124
|
+
minZ = Math.min(minZ, z)
|
|
125
|
+
maxZ = Math.max(maxZ, z)
|
|
61
126
|
}
|
|
62
|
-
return maxZ
|
|
127
|
+
return { minZ, maxZ }
|
|
63
128
|
}
|
|
64
129
|
}
|
|
@@ -264,7 +264,6 @@ export class PcbScene3dRuntime {
|
|
|
264
264
|
this.#settleReady()
|
|
265
265
|
return
|
|
266
266
|
}
|
|
267
|
-
|
|
268
267
|
try {
|
|
269
268
|
const { THREE, OrbitControls } =
|
|
270
269
|
(await this.#hooks.loadRuntimeModules?.()) ||
|
|
@@ -274,7 +273,6 @@ export class PcbScene3dRuntime {
|
|
|
274
273
|
if (this.#isDisposed || !this.#viewportNode) {
|
|
275
274
|
return
|
|
276
275
|
}
|
|
277
|
-
|
|
278
276
|
this.#createRenderer()
|
|
279
277
|
this.#createSceneGraph()
|
|
280
278
|
this.#createControls()
|
|
@@ -299,7 +297,6 @@ export class PcbScene3dRuntime {
|
|
|
299
297
|
#createRenderer() {
|
|
300
298
|
const THREE = this.#three
|
|
301
299
|
const size = PcbScene3dViewportResize.resolveSize(this.#viewportNode)
|
|
302
|
-
|
|
303
300
|
this.#renderer = new THREE.WebGLRenderer({
|
|
304
301
|
antialias: true,
|
|
305
302
|
alpha: true,
|
|
@@ -310,7 +307,6 @@ export class PcbScene3dRuntime {
|
|
|
310
307
|
this.#renderer.domElement.className = 'scene-3d__canvas'
|
|
311
308
|
this.#renderer.domElement.style.width = '100%'
|
|
312
309
|
this.#renderer.domElement.style.height = '100%'
|
|
313
|
-
|
|
314
310
|
this.#scene = new THREE.Scene()
|
|
315
311
|
this.#camera = new THREE.PerspectiveCamera(
|
|
316
312
|
38,
|
|
@@ -368,7 +364,11 @@ export class PcbScene3dRuntime {
|
|
|
368
364
|
(x, y) => this.#normalizeDetailPoint(x, y),
|
|
369
365
|
{
|
|
370
366
|
enabled: true,
|
|
371
|
-
board
|
|
367
|
+
board,
|
|
368
|
+
hasBoardAssemblyModel: Boolean(
|
|
369
|
+
this.#sceneDescription.boardAssemblyModel
|
|
370
|
+
),
|
|
371
|
+
sourceFormat: this.#sceneDescription.sourceFormat
|
|
372
372
|
}
|
|
373
373
|
)
|
|
374
374
|
)
|
|
@@ -97,7 +97,7 @@ export class PcbScene3dRuntimeBoardMeshes {
|
|
|
97
97
|
: 0xc9ca78
|
|
98
98
|
const surfaceColor = hasBoardAssemblyModel
|
|
99
99
|
? resolvedEdgeColor
|
|
100
|
-
: PcbScene3dBoardMaterialPalette.
|
|
100
|
+
: PcbScene3dBoardMaterialPalette.resolveBoardSurfaceColor(board, {
|
|
101
101
|
hasBoardAssemblyModel
|
|
102
102
|
})
|
|
103
103
|
|