pcb-scene3d-viewer 1.1.47 → 1.1.49
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 +2 -1
- package/src/PcbScene3dBoardAssemblyPresentation.mjs +10 -4
- package/src/PcbScene3dBoardCutoutPathFactory.mjs +83 -0
- package/src/PcbScene3dBoardMaterialPalette.mjs +17 -2
- package/src/PcbScene3dBoardShapeFactory.mjs +16 -3
- package/src/PcbScene3dBoardSolderMaskFactory.mjs +20 -10
- package/src/PcbScene3dController.mjs +20 -0
- package/src/PcbScene3dCopperDetailGroupBuilder.mjs +63 -4
- package/src/PcbScene3dCopperFactory.mjs +64 -93
- package/src/PcbScene3dCopperFillAreaClipper.mjs +726 -0
- package/src/PcbScene3dCopperFillMeshBuilder.mjs +530 -8
- package/src/PcbScene3dCopperFillPolygonBoolean.mjs +235 -0
- package/src/PcbScene3dCopperPrismBuilder.mjs +92 -0
- package/src/PcbScene3dDrillVoidFactory.mjs +15 -71
- package/src/PcbScene3dExternalModels.mjs +16 -3
- package/src/PcbScene3dGeometryZCompressor.mjs +5 -2
- package/src/PcbScene3dMaskCoveredCopperMaterial.mjs +1 -1
- package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +174 -6
- package/src/PcbScene3dMaskCoveredCopperSurfaceFilter.mjs +64 -0
- package/src/PcbScene3dOutlineBuilder.mjs +54 -0
- package/src/PcbScene3dRuntime.mjs +1 -1
- package/src/PcbScene3dShellRenderer.mjs +38 -4
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import polygonClipping from 'polygon-clipping'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes and subtracts copper fill loop sets with polygon booleans.
|
|
5
|
+
*/
|
|
6
|
+
export class PcbScene3dCopperFillPolygonBoolean {
|
|
7
|
+
static #AREA_EPSILON = 0.001
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the remaining loop sets after subtracting already emitted
|
|
11
|
+
* copper areas.
|
|
12
|
+
* @param {{ outer: number[][], holes: number[][][], bounds: object }} loopSet Candidate loop set.
|
|
13
|
+
* @param {number[][][][]} emittedPolygons Already emitted polygon-clipping polygons.
|
|
14
|
+
* @returns {{ outer: number[][], holes: number[][][], bounds: object }[] | null}
|
|
15
|
+
*/
|
|
16
|
+
static resolveRemainingLoopSets(loopSet, emittedPolygons) {
|
|
17
|
+
const subjectPolygons =
|
|
18
|
+
PcbScene3dCopperFillPolygonBoolean.resolveNormalizedPolygons(
|
|
19
|
+
loopSet
|
|
20
|
+
)
|
|
21
|
+
if (!subjectPolygons.length) {
|
|
22
|
+
return []
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!emittedPolygons.length) {
|
|
26
|
+
return PcbScene3dCopperFillPolygonBoolean.#multiPolygonToLoopSets(
|
|
27
|
+
subjectPolygons
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
return PcbScene3dCopperFillPolygonBoolean.#multiPolygonToLoopSets(
|
|
33
|
+
polygonClipping.difference(subjectPolygons, ...emittedPolygons)
|
|
34
|
+
)
|
|
35
|
+
} catch {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes one loop set with polygon boolean semantics.
|
|
42
|
+
* @param {{ outer: number[][], holes: number[][][] }} loopSet Normalized loop set.
|
|
43
|
+
* @returns {number[][][][]}
|
|
44
|
+
*/
|
|
45
|
+
static resolveNormalizedPolygons(loopSet) {
|
|
46
|
+
const subject =
|
|
47
|
+
PcbScene3dCopperFillPolygonBoolean.#loopSetToPolygon(loopSet)
|
|
48
|
+
if (!subject) {
|
|
49
|
+
return []
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
return polygonClipping.union(subject)
|
|
54
|
+
} catch {
|
|
55
|
+
return [subject]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Converts one normalized loop set to a polygon-clipping polygon.
|
|
61
|
+
* @param {{ outer: number[][], holes: number[][][] }} loopSet Normalized loop set.
|
|
62
|
+
* @returns {number[][][] | null}
|
|
63
|
+
*/
|
|
64
|
+
static #loopSetToPolygon(loopSet) {
|
|
65
|
+
const outer = PcbScene3dCopperFillPolygonBoolean.#closedRing(
|
|
66
|
+
loopSet?.outer
|
|
67
|
+
)
|
|
68
|
+
if (!outer.length) {
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return [
|
|
73
|
+
outer,
|
|
74
|
+
...(loopSet?.holes || [])
|
|
75
|
+
.map((hole) =>
|
|
76
|
+
PcbScene3dCopperFillPolygonBoolean.#closedRing(hole)
|
|
77
|
+
)
|
|
78
|
+
.filter((hole) => hole.length)
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Converts polygon-clipping output into normalized loop sets.
|
|
84
|
+
* @param {number[][][][]} multiPolygon Clipped multipolygon.
|
|
85
|
+
* @returns {{ outer: number[][], holes: number[][][], bounds: object }[]}
|
|
86
|
+
*/
|
|
87
|
+
static #multiPolygonToLoopSets(multiPolygon) {
|
|
88
|
+
return (multiPolygon || [])
|
|
89
|
+
.map((polygon) => {
|
|
90
|
+
const outer = PcbScene3dCopperFillPolygonBoolean.#cleanLoop(
|
|
91
|
+
polygon?.[0] || []
|
|
92
|
+
)
|
|
93
|
+
if (!PcbScene3dCopperFillPolygonBoolean.#isValidLoop(outer)) {
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const holes = (polygon || [])
|
|
98
|
+
.slice(1)
|
|
99
|
+
.map((ring) =>
|
|
100
|
+
PcbScene3dCopperFillPolygonBoolean.#cleanLoop(ring)
|
|
101
|
+
)
|
|
102
|
+
.filter((ring) =>
|
|
103
|
+
PcbScene3dCopperFillPolygonBoolean.#isValidLoop(ring)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
outer,
|
|
108
|
+
holes,
|
|
109
|
+
bounds: PcbScene3dCopperFillPolygonBoolean.#loopBounds(
|
|
110
|
+
outer
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
.filter(Boolean)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Closes one loop for polygon boolean operations.
|
|
119
|
+
* @param {number[][]} loop Candidate loop.
|
|
120
|
+
* @returns {number[][]}
|
|
121
|
+
*/
|
|
122
|
+
static #closedRing(loop) {
|
|
123
|
+
const cleanLoop = PcbScene3dCopperFillPolygonBoolean.#cleanLoop(loop)
|
|
124
|
+
if (!PcbScene3dCopperFillPolygonBoolean.#isValidLoop(cleanLoop)) {
|
|
125
|
+
return []
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const first = cleanLoop[0]
|
|
129
|
+
const last = cleanLoop[cleanLoop.length - 1]
|
|
130
|
+
const closed = cleanLoop.map((point) => [point[0], point[1]])
|
|
131
|
+
if (
|
|
132
|
+
Math.abs(first[0] - last[0]) >=
|
|
133
|
+
PcbScene3dCopperFillPolygonBoolean.#AREA_EPSILON ||
|
|
134
|
+
Math.abs(first[1] - last[1]) >=
|
|
135
|
+
PcbScene3dCopperFillPolygonBoolean.#AREA_EPSILON
|
|
136
|
+
) {
|
|
137
|
+
closed.push([first[0], first[1]])
|
|
138
|
+
}
|
|
139
|
+
return closed
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Removes invalid and duplicate loop points.
|
|
144
|
+
* @param {number[][]} points Candidate points.
|
|
145
|
+
* @returns {number[][]}
|
|
146
|
+
*/
|
|
147
|
+
static #cleanLoop(points) {
|
|
148
|
+
const loop = []
|
|
149
|
+
for (const point of points || []) {
|
|
150
|
+
const x = Number(point?.[0])
|
|
151
|
+
const y = Number(point?.[1])
|
|
152
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const previous = loop[loop.length - 1]
|
|
157
|
+
if (
|
|
158
|
+
previous &&
|
|
159
|
+
Math.abs(previous[0] - x) <
|
|
160
|
+
PcbScene3dCopperFillPolygonBoolean.#AREA_EPSILON &&
|
|
161
|
+
Math.abs(previous[1] - y) <
|
|
162
|
+
PcbScene3dCopperFillPolygonBoolean.#AREA_EPSILON
|
|
163
|
+
) {
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
loop.push([x, y])
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const first = loop[0]
|
|
170
|
+
const last = loop[loop.length - 1]
|
|
171
|
+
if (
|
|
172
|
+
first &&
|
|
173
|
+
last &&
|
|
174
|
+
Math.abs(first[0] - last[0]) <
|
|
175
|
+
PcbScene3dCopperFillPolygonBoolean.#AREA_EPSILON &&
|
|
176
|
+
Math.abs(first[1] - last[1]) <
|
|
177
|
+
PcbScene3dCopperFillPolygonBoolean.#AREA_EPSILON
|
|
178
|
+
) {
|
|
179
|
+
loop.pop()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return loop
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Checks whether one loop has enough non-collinear area.
|
|
187
|
+
* @param {number[][]} loop Candidate loop.
|
|
188
|
+
* @returns {boolean}
|
|
189
|
+
*/
|
|
190
|
+
static #isValidLoop(loop) {
|
|
191
|
+
return (
|
|
192
|
+
Array.isArray(loop) &&
|
|
193
|
+
loop.length >= 3 &&
|
|
194
|
+
Math.abs(PcbScene3dCopperFillPolygonBoolean.#signedArea(loop)) >
|
|
195
|
+
PcbScene3dCopperFillPolygonBoolean.#AREA_EPSILON
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Computes axis-aligned bounds for one loop.
|
|
201
|
+
* @param {number[][]} loop Candidate loop.
|
|
202
|
+
* @returns {{ minX: number, minY: number, maxX: number, maxY: number }}
|
|
203
|
+
*/
|
|
204
|
+
static #loopBounds(loop) {
|
|
205
|
+
return (loop || []).reduce(
|
|
206
|
+
(bounds, point) => ({
|
|
207
|
+
minX: Math.min(bounds.minX, Number(point?.[0])),
|
|
208
|
+
minY: Math.min(bounds.minY, Number(point?.[1])),
|
|
209
|
+
maxX: Math.max(bounds.maxX, Number(point?.[0])),
|
|
210
|
+
maxY: Math.max(bounds.maxY, Number(point?.[1]))
|
|
211
|
+
}),
|
|
212
|
+
{
|
|
213
|
+
minX: Infinity,
|
|
214
|
+
minY: Infinity,
|
|
215
|
+
maxX: -Infinity,
|
|
216
|
+
maxY: -Infinity
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Computes signed loop area.
|
|
223
|
+
* @param {number[][]} loop Candidate loop.
|
|
224
|
+
* @returns {number}
|
|
225
|
+
*/
|
|
226
|
+
static #signedArea(loop) {
|
|
227
|
+
let area = 0
|
|
228
|
+
for (let index = 0; index < loop.length; index += 1) {
|
|
229
|
+
const current = loop[index]
|
|
230
|
+
const next = loop[(index + 1) % loop.length]
|
|
231
|
+
area += current[0] * next[1] - next[0] * current[1]
|
|
232
|
+
}
|
|
233
|
+
return area / 2
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Appends shallow copper-prism triangles to packed position buffers.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dCopperPrismBuilder {
|
|
5
|
+
static #COPPER_THICKNESS_MIL = 2.2
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns the visual copper extrusion thickness.
|
|
9
|
+
* @returns {number}
|
|
10
|
+
*/
|
|
11
|
+
static thicknessMil() {
|
|
12
|
+
return PcbScene3dCopperPrismBuilder.#COPPER_THICKNESS_MIL
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns half the visual copper extrusion thickness.
|
|
17
|
+
* @returns {number}
|
|
18
|
+
*/
|
|
19
|
+
static halfThicknessMil() {
|
|
20
|
+
return PcbScene3dCopperPrismBuilder.#COPPER_THICKNESS_MIL / 2
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Appends one shallow triangular prism into the position buffer.
|
|
25
|
+
* @param {number[]} positions Position buffer.
|
|
26
|
+
* @param {{ x: number, y: number }} a First point.
|
|
27
|
+
* @param {{ x: number, y: number }} b Second point.
|
|
28
|
+
* @param {{ x: number, y: number }} c Third point.
|
|
29
|
+
* @param {number} z Center Z position.
|
|
30
|
+
* @returns {void}
|
|
31
|
+
*/
|
|
32
|
+
static appendTriangle(positions, a, b, c, z) {
|
|
33
|
+
const halfThickness = PcbScene3dCopperPrismBuilder.halfThicknessMil()
|
|
34
|
+
const topZ = z + halfThickness
|
|
35
|
+
const bottomZ = z - halfThickness
|
|
36
|
+
positions.push(
|
|
37
|
+
a.x,
|
|
38
|
+
a.y,
|
|
39
|
+
topZ,
|
|
40
|
+
b.x,
|
|
41
|
+
b.y,
|
|
42
|
+
topZ,
|
|
43
|
+
c.x,
|
|
44
|
+
c.y,
|
|
45
|
+
topZ,
|
|
46
|
+
c.x,
|
|
47
|
+
c.y,
|
|
48
|
+
bottomZ,
|
|
49
|
+
b.x,
|
|
50
|
+
b.y,
|
|
51
|
+
bottomZ,
|
|
52
|
+
a.x,
|
|
53
|
+
a.y,
|
|
54
|
+
bottomZ
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Appends a side wall for one actual copper boundary edge.
|
|
60
|
+
* @param {number[]} positions Position buffer.
|
|
61
|
+
* @param {{ x: number, y: number }} start Wall start point.
|
|
62
|
+
* @param {{ x: number, y: number }} end Wall end point.
|
|
63
|
+
* @param {number} z Center Z position.
|
|
64
|
+
* @returns {void}
|
|
65
|
+
*/
|
|
66
|
+
static appendBoundarySideTriangles(positions, start, end, z) {
|
|
67
|
+
const halfThickness = PcbScene3dCopperPrismBuilder.halfThicknessMil()
|
|
68
|
+
const topZ = z + halfThickness
|
|
69
|
+
const bottomZ = z - halfThickness
|
|
70
|
+
|
|
71
|
+
positions.push(
|
|
72
|
+
start.x,
|
|
73
|
+
start.y,
|
|
74
|
+
topZ,
|
|
75
|
+
end.x,
|
|
76
|
+
end.y,
|
|
77
|
+
topZ,
|
|
78
|
+
end.x,
|
|
79
|
+
end.y,
|
|
80
|
+
bottomZ,
|
|
81
|
+
start.x,
|
|
82
|
+
start.y,
|
|
83
|
+
topZ,
|
|
84
|
+
end.x,
|
|
85
|
+
end.y,
|
|
86
|
+
bottomZ,
|
|
87
|
+
start.x,
|
|
88
|
+
start.y,
|
|
89
|
+
bottomZ
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -33,15 +33,13 @@ export class PcbScene3dDrillVoidFactory {
|
|
|
33
33
|
return group
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const material = PcbScene3dDrillVoidFactory.#
|
|
36
|
+
const material = PcbScene3dDrillVoidFactory.#buildInteriorMaterial(
|
|
37
37
|
THREE,
|
|
38
38
|
options
|
|
39
39
|
)
|
|
40
40
|
const geometryCache = new Map()
|
|
41
41
|
const depth = Math.max(Math.abs(Number(topZ) - Number(bottomZ)), 1)
|
|
42
42
|
const centerZ = (Number(topZ || 0) + Number(bottomZ || 0)) / 2
|
|
43
|
-
const platedDrillKeys =
|
|
44
|
-
PcbScene3dDrillVoidFactory.#buildPlatedDrillKeySet(detail)
|
|
45
43
|
const edgeDrillKeys = PcbScene3dDrillVoidFactory.#buildEdgeDrillKeySet(
|
|
46
44
|
THREE,
|
|
47
45
|
detail,
|
|
@@ -52,9 +50,7 @@ export class PcbScene3dDrillVoidFactory {
|
|
|
52
50
|
PcbScene3dDrillPathFactory.resolveBoardDrillSpecs(detail).forEach(
|
|
53
51
|
(drillSpec) => {
|
|
54
52
|
if (
|
|
55
|
-
|
|
56
|
-
PcbScene3dDrillVoidFactory.#drillKey(drillSpec)
|
|
57
|
-
) ||
|
|
53
|
+
PcbScene3dDrillVoidFactory.#isSlottedDrill(drillSpec) ||
|
|
58
54
|
edgeDrillKeys.has(
|
|
59
55
|
PcbScene3dDrillVoidFactory.#drillKey(drillSpec)
|
|
60
56
|
)
|
|
@@ -101,63 +97,6 @@ export class PcbScene3dDrillVoidFactory {
|
|
|
101
97
|
)
|
|
102
98
|
}
|
|
103
99
|
|
|
104
|
-
/**
|
|
105
|
-
* Builds a lookup for drill holes that already have copper barrel geometry.
|
|
106
|
-
* @param {{ pads?: any[], vias?: any[] }} detail
|
|
107
|
-
* @returns {Set<string>}
|
|
108
|
-
*/
|
|
109
|
-
static #buildPlatedDrillKeySet(detail) {
|
|
110
|
-
const platedDrills = new Set()
|
|
111
|
-
|
|
112
|
-
for (const via of detail?.vias || []) {
|
|
113
|
-
if (PcbScene3dDrillVoidFactory.#hasViaCopperAnnulus(via)) {
|
|
114
|
-
platedDrills.add(PcbScene3dDrillVoidFactory.#drillKey(via))
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
for (const pad of detail?.pads || []) {
|
|
119
|
-
if (PcbScene3dDrillVoidFactory.#hasPadCopperAnnulus(pad)) {
|
|
120
|
-
platedDrills.add(PcbScene3dDrillVoidFactory.#drillKey(pad))
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return platedDrills
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Checks whether one via has a copper annulus around its drill.
|
|
129
|
-
* @param {any} via Via primitive.
|
|
130
|
-
* @returns {boolean}
|
|
131
|
-
*/
|
|
132
|
-
static #hasViaCopperAnnulus(via) {
|
|
133
|
-
const holeDiameter = Number(via?.holeDiameter || 0)
|
|
134
|
-
return (
|
|
135
|
-
holeDiameter > 0 &&
|
|
136
|
-
Number(via?.diameter || 0) > holeDiameter + 0.001
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Checks whether one pad has copper larger than its drill aperture.
|
|
142
|
-
* @param {any} pad Pad primitive.
|
|
143
|
-
* @returns {boolean}
|
|
144
|
-
*/
|
|
145
|
-
static #hasPadCopperAnnulus(pad) {
|
|
146
|
-
const holeDiameter = Number(pad?.holeDiameter || 0)
|
|
147
|
-
if (holeDiameter <= 0) {
|
|
148
|
-
return false
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return [
|
|
152
|
-
pad?.sizeTopX,
|
|
153
|
-
pad?.sizeTopY,
|
|
154
|
-
pad?.sizeMidX,
|
|
155
|
-
pad?.sizeMidY,
|
|
156
|
-
pad?.sizeBottomX,
|
|
157
|
-
pad?.sizeBottomY
|
|
158
|
-
].some((size) => Number(size || 0) > holeDiameter + 0.001)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
100
|
/**
|
|
162
101
|
* Builds a stable lookup key for one circular drill.
|
|
163
102
|
* @param {{ x?: number, y?: number, holeDiameter?: number, diameter?: number }} drill
|
|
@@ -177,7 +116,7 @@ export class PcbScene3dDrillVoidFactory {
|
|
|
177
116
|
* @param {{ color?: number }} options
|
|
178
117
|
* @returns {any}
|
|
179
118
|
*/
|
|
180
|
-
static #
|
|
119
|
+
static #buildInteriorMaterial(THREE, options) {
|
|
181
120
|
return new THREE.MeshStandardMaterial({
|
|
182
121
|
color: Number.isInteger(options?.color)
|
|
183
122
|
? options.color
|
|
@@ -188,6 +127,18 @@ export class PcbScene3dDrillVoidFactory {
|
|
|
188
127
|
})
|
|
189
128
|
}
|
|
190
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Checks whether one drill is a routed slot.
|
|
132
|
+
* @param {{ diameter?: number, slotLength?: number | null }} drillSpec Drill spec.
|
|
133
|
+
* @returns {boolean}
|
|
134
|
+
*/
|
|
135
|
+
static #isSlottedDrill(drillSpec) {
|
|
136
|
+
return (
|
|
137
|
+
Number(drillSpec?.slotLength || 0) >
|
|
138
|
+
Number(drillSpec?.diameter || 0) + 0.001
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
191
142
|
/**
|
|
192
143
|
* Builds one open circular drill-interior mesh.
|
|
193
144
|
* @param {any} THREE
|
|
@@ -208,13 +159,6 @@ export class PcbScene3dDrillVoidFactory {
|
|
|
208
159
|
material,
|
|
209
160
|
normalizeBoardPoint
|
|
210
161
|
) {
|
|
211
|
-
if (
|
|
212
|
-
Number(drillSpec?.slotLength || 0) >
|
|
213
|
-
Number(drillSpec?.diameter || 0) + 0.001
|
|
214
|
-
) {
|
|
215
|
-
return null
|
|
216
|
-
}
|
|
217
|
-
|
|
218
162
|
const point = normalizeBoardPoint(
|
|
219
163
|
Number(drillSpec?.x || 0),
|
|
220
164
|
Number(drillSpec?.y || 0)
|
|
@@ -281,7 +281,8 @@ export class PcbScene3dExternalModels {
|
|
|
281
281
|
return PcbScene3dExternalModels.#buildBoardAssemblyWrapper(
|
|
282
282
|
THREE,
|
|
283
283
|
placement,
|
|
284
|
-
modelGroup
|
|
284
|
+
modelGroup,
|
|
285
|
+
sceneDescription
|
|
285
286
|
)
|
|
286
287
|
}
|
|
287
288
|
|
|
@@ -371,13 +372,25 @@ export class PcbScene3dExternalModels {
|
|
|
371
372
|
* @param {any} THREE
|
|
372
373
|
* @param {{ positionMil?: { x?: number, y?: number, z?: number }, board?: { widthMil?: number, heightMil?: number, thicknessMil?: number }, sourceFrameScale?: { y?: number } }} placement
|
|
373
374
|
* @param {any} modelGroup
|
|
375
|
+
* @param {{ sourceFormat?: string } | null | undefined} sceneDescription Scene description.
|
|
374
376
|
* @returns {any}
|
|
375
377
|
*/
|
|
376
|
-
static #buildBoardAssemblyWrapper(
|
|
378
|
+
static #buildBoardAssemblyWrapper(
|
|
379
|
+
THREE,
|
|
380
|
+
placement,
|
|
381
|
+
modelGroup,
|
|
382
|
+
sceneDescription
|
|
383
|
+
) {
|
|
377
384
|
const wrapperGroup = new THREE.Group()
|
|
378
385
|
const positionMil = placement?.positionMil || {}
|
|
379
386
|
|
|
380
|
-
PcbScene3dBoardAssemblyPresentation.apply(
|
|
387
|
+
PcbScene3dBoardAssemblyPresentation.apply(
|
|
388
|
+
modelGroup,
|
|
389
|
+
placement?.board,
|
|
390
|
+
{
|
|
391
|
+
sourceFormat: sceneDescription?.sourceFormat
|
|
392
|
+
}
|
|
393
|
+
)
|
|
381
394
|
PcbScene3dBoardAssemblyTransform.apply(modelGroup, placement)
|
|
382
395
|
wrapperGroup.position.set(
|
|
383
396
|
Number(positionMil.x || 0),
|
|
@@ -9,13 +9,16 @@ 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
13
|
* @returns {void}
|
|
13
14
|
*/
|
|
14
|
-
static compressMaskCoveredCopperMesh(mesh, sourceCenterZ) {
|
|
15
|
+
static compressMaskCoveredCopperMesh(mesh, sourceCenterZ, options = {}) {
|
|
15
16
|
PcbScene3dGeometryZCompressor.compressMesh(
|
|
16
17
|
mesh,
|
|
17
18
|
sourceCenterZ,
|
|
18
|
-
sourceCenterZ +
|
|
19
|
+
sourceCenterZ +
|
|
20
|
+
MASK_COVERED_CENTER_OFFSET_MIL +
|
|
21
|
+
Number(options?.centerOffsetMil || 0),
|
|
19
22
|
MASK_COVERED_THICKNESS_MIL
|
|
20
23
|
)
|
|
21
24
|
}
|
|
@@ -6,7 +6,7 @@ import { PcbScene3dMaterialFinish } from './PcbScene3dMaterialFinish.mjs'
|
|
|
6
6
|
export class PcbScene3dMaskCoveredCopperMaterial {
|
|
7
7
|
static #COPPER_COLOR = 0xd9a61d
|
|
8
8
|
static #DEFAULT_SOLDER_MASK_COLOR = 0x2a5f27
|
|
9
|
-
static #COPPER_BLEND = 0.
|
|
9
|
+
static #COPPER_BLEND = 0.04
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Builds a mask-covered copper material.
|