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
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { PcbScene3dDrillPathFactory } from './PcbScene3dDrillPathFactory.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds normalized board drill cutouts for copper surface meshes.
|
|
5
|
+
*/
|
|
6
|
+
export class PcbScene3dCopperDrillCutoutBuilder {
|
|
7
|
+
static #DRILL_CUTOUT_SEGMENTS = 48
|
|
8
|
+
static #SLOT_CAP_SEGMENTS = 12
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolves board drill cutouts for copper fill apertures.
|
|
12
|
+
* @param {{ pads?: any[], vias?: any[] }} detail Copper detail.
|
|
13
|
+
* @param {(x: number, y: number) => { x: number, y: number }} [normalizeBoardPoint]
|
|
14
|
+
* @param {boolean} [mirrorY] Whether to mirror underside primitives.
|
|
15
|
+
* @returns {{ x: number, y: number }[][]}
|
|
16
|
+
*/
|
|
17
|
+
static resolve(
|
|
18
|
+
detail,
|
|
19
|
+
normalizeBoardPoint = (x, y) => ({ x, y }),
|
|
20
|
+
mirrorY = false
|
|
21
|
+
) {
|
|
22
|
+
return PcbScene3dDrillPathFactory.resolveBoardDrillSpecs(detail)
|
|
23
|
+
.map((drillSpec) =>
|
|
24
|
+
PcbScene3dCopperDrillCutoutBuilder.#resolveCutoutPoints(
|
|
25
|
+
drillSpec,
|
|
26
|
+
normalizeBoardPoint,
|
|
27
|
+
mirrorY
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
.filter((points) => points.length >= 3)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolves cutouts from explicit options or fallback drill detail.
|
|
35
|
+
* @param {{ drillCutouts?: any[], drillDetail?: object } | undefined} options
|
|
36
|
+
* @param {{ pads?: any[], vias?: any[] }} fallbackDetail Fallback detail.
|
|
37
|
+
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
38
|
+
* @param {boolean} mirrorY Whether to mirror underside primitives.
|
|
39
|
+
* @returns {{ x: number, y: number }[][]}
|
|
40
|
+
*/
|
|
41
|
+
static resolveFromOptions(
|
|
42
|
+
options,
|
|
43
|
+
fallbackDetail,
|
|
44
|
+
normalizeBoardPoint,
|
|
45
|
+
mirrorY
|
|
46
|
+
) {
|
|
47
|
+
if (Array.isArray(options?.drillCutouts)) {
|
|
48
|
+
return PcbScene3dCopperDrillCutoutBuilder.#normalizeCutouts(
|
|
49
|
+
options.drillCutouts,
|
|
50
|
+
normalizeBoardPoint,
|
|
51
|
+
mirrorY
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return PcbScene3dCopperDrillCutoutBuilder.resolve(
|
|
56
|
+
options?.drillDetail || fallbackDetail,
|
|
57
|
+
normalizeBoardPoint,
|
|
58
|
+
mirrorY
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolves one drill cutout point loop.
|
|
64
|
+
* @param {{ x: number, y: number, diameter: number, slotLength?: number | null, rotationDeg?: number | null }} drillSpec
|
|
65
|
+
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
66
|
+
* @param {boolean} mirrorY Whether to mirror underside primitives.
|
|
67
|
+
* @returns {{ x: number, y: number }[]}
|
|
68
|
+
*/
|
|
69
|
+
static #resolveCutoutPoints(drillSpec, normalizeBoardPoint, mirrorY) {
|
|
70
|
+
const points =
|
|
71
|
+
Number(drillSpec?.slotLength || 0) >
|
|
72
|
+
Number(drillSpec?.diameter || 0) + 0.001
|
|
73
|
+
? PcbScene3dCopperDrillCutoutBuilder.#buildSlotPoints(drillSpec)
|
|
74
|
+
: PcbScene3dCopperDrillCutoutBuilder.#buildCirclePoints(
|
|
75
|
+
drillSpec
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return points.map((point) =>
|
|
79
|
+
PcbScene3dCopperDrillCutoutBuilder.#normalizePoint(
|
|
80
|
+
point,
|
|
81
|
+
normalizeBoardPoint,
|
|
82
|
+
mirrorY
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Normalizes cutout point loops.
|
|
89
|
+
* @param {{ x?: number, y?: number }[][]} cutouts Cutout loops.
|
|
90
|
+
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
91
|
+
* @param {boolean} mirrorY Whether to mirror underside primitives.
|
|
92
|
+
* @returns {{ x: number, y: number }[][]}
|
|
93
|
+
*/
|
|
94
|
+
static #normalizeCutouts(cutouts, normalizeBoardPoint, mirrorY) {
|
|
95
|
+
return cutouts
|
|
96
|
+
.map((cutout) =>
|
|
97
|
+
(Array.isArray(cutout) ? cutout : []).map((point) =>
|
|
98
|
+
PcbScene3dCopperDrillCutoutBuilder.#normalizePoint(
|
|
99
|
+
point,
|
|
100
|
+
normalizeBoardPoint,
|
|
101
|
+
mirrorY
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
.filter((cutout) => cutout.length >= 3)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Normalizes one cutout point.
|
|
110
|
+
* @param {{ x?: number, y?: number }} point Source point.
|
|
111
|
+
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
112
|
+
* @param {boolean} mirrorY Whether to mirror underside primitives.
|
|
113
|
+
* @returns {{ x: number, y: number }}
|
|
114
|
+
*/
|
|
115
|
+
static #normalizePoint(point, normalizeBoardPoint, mirrorY) {
|
|
116
|
+
const normalized = normalizeBoardPoint(
|
|
117
|
+
Number(point?.x || 0),
|
|
118
|
+
Number(point?.y || 0)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
x: Number(normalized?.x || 0),
|
|
123
|
+
y: mirrorY
|
|
124
|
+
? -Number(normalized?.y || 0)
|
|
125
|
+
: Number(normalized?.y || 0)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Builds one sampled circular drill loop.
|
|
131
|
+
* @param {{ x: number, y: number, diameter: number }} drillSpec Drill spec.
|
|
132
|
+
* @returns {{ x: number, y: number }[]}
|
|
133
|
+
*/
|
|
134
|
+
static #buildCirclePoints(drillSpec) {
|
|
135
|
+
const radius = Math.max(Number(drillSpec?.diameter || 0) / 2, 0.6)
|
|
136
|
+
const centerX = Number(drillSpec?.x || 0)
|
|
137
|
+
const centerY = Number(drillSpec?.y || 0)
|
|
138
|
+
|
|
139
|
+
return Array.from(
|
|
140
|
+
{
|
|
141
|
+
length: PcbScene3dCopperDrillCutoutBuilder
|
|
142
|
+
.#DRILL_CUTOUT_SEGMENTS
|
|
143
|
+
},
|
|
144
|
+
(_, index) => {
|
|
145
|
+
const angle =
|
|
146
|
+
(Math.PI * 2 * index) /
|
|
147
|
+
PcbScene3dCopperDrillCutoutBuilder.#DRILL_CUTOUT_SEGMENTS
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
x: centerX + Math.cos(angle) * radius,
|
|
151
|
+
y: centerY + Math.sin(angle) * radius
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Builds one sampled slotted drill loop.
|
|
159
|
+
* @param {{ x: number, y: number, diameter: number, slotLength?: number | null, rotationDeg?: number | null }} drillSpec Drill spec.
|
|
160
|
+
* @returns {{ x: number, y: number }[]}
|
|
161
|
+
*/
|
|
162
|
+
static #buildSlotPoints(drillSpec) {
|
|
163
|
+
const diameter = Math.max(Number(drillSpec?.diameter || 0), 1.2)
|
|
164
|
+
const radius = Math.max(diameter / 2, 0.6)
|
|
165
|
+
const slotLength = Math.max(
|
|
166
|
+
Number(drillSpec?.slotLength || 0),
|
|
167
|
+
diameter
|
|
168
|
+
)
|
|
169
|
+
const straightHalf = Math.max(slotLength / 2 - radius, 0)
|
|
170
|
+
const rotationRad =
|
|
171
|
+
(Number(drillSpec?.rotationDeg || 0) * Math.PI) / 180
|
|
172
|
+
const points = []
|
|
173
|
+
|
|
174
|
+
PcbScene3dCopperDrillCutoutBuilder.#appendArcPoints(
|
|
175
|
+
points,
|
|
176
|
+
straightHalf,
|
|
177
|
+
0,
|
|
178
|
+
radius,
|
|
179
|
+
-Math.PI / 2,
|
|
180
|
+
Math.PI / 2
|
|
181
|
+
)
|
|
182
|
+
PcbScene3dCopperDrillCutoutBuilder.#appendArcPoints(
|
|
183
|
+
points,
|
|
184
|
+
-straightHalf,
|
|
185
|
+
0,
|
|
186
|
+
radius,
|
|
187
|
+
Math.PI / 2,
|
|
188
|
+
(Math.PI * 3) / 2,
|
|
189
|
+
true
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return points.map((point) =>
|
|
193
|
+
PcbScene3dCopperDrillCutoutBuilder.#rotateAndTranslatePoint(
|
|
194
|
+
point,
|
|
195
|
+
rotationRad,
|
|
196
|
+
Number(drillSpec?.x || 0),
|
|
197
|
+
Number(drillSpec?.y || 0)
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Appends sampled points for one slot cap arc.
|
|
204
|
+
* @param {{ x: number, y: number }[]} points Output point list.
|
|
205
|
+
* @param {number} cx Arc center X.
|
|
206
|
+
* @param {number} cy Arc center Y.
|
|
207
|
+
* @param {number} radius Arc radius.
|
|
208
|
+
* @param {number} startAngle Start angle in radians.
|
|
209
|
+
* @param {number} endAngle End angle in radians.
|
|
210
|
+
* @param {boolean} [skipFirst] Whether to skip the first sample.
|
|
211
|
+
* @returns {void}
|
|
212
|
+
*/
|
|
213
|
+
static #appendArcPoints(
|
|
214
|
+
points,
|
|
215
|
+
cx,
|
|
216
|
+
cy,
|
|
217
|
+
radius,
|
|
218
|
+
startAngle,
|
|
219
|
+
endAngle,
|
|
220
|
+
skipFirst = false
|
|
221
|
+
) {
|
|
222
|
+
for (
|
|
223
|
+
let index = 0;
|
|
224
|
+
index <= PcbScene3dCopperDrillCutoutBuilder.#SLOT_CAP_SEGMENTS;
|
|
225
|
+
index += 1
|
|
226
|
+
) {
|
|
227
|
+
if (skipFirst && index === 0) {
|
|
228
|
+
continue
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const t =
|
|
232
|
+
index / PcbScene3dCopperDrillCutoutBuilder.#SLOT_CAP_SEGMENTS
|
|
233
|
+
const angle = startAngle + (endAngle - startAngle) * t
|
|
234
|
+
points.push({
|
|
235
|
+
x: cx + Math.cos(angle) * radius,
|
|
236
|
+
y: cy + Math.sin(angle) * radius
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Rotates one local point around the origin and translates it into place.
|
|
243
|
+
* @param {{ x: number, y: number }} point Local point.
|
|
244
|
+
* @param {number} rotationRad Rotation in radians.
|
|
245
|
+
* @param {number} dx Translation X.
|
|
246
|
+
* @param {number} dy Translation Y.
|
|
247
|
+
* @returns {{ x: number, y: number }}
|
|
248
|
+
*/
|
|
249
|
+
static #rotateAndTranslatePoint(point, rotationRad, dx, dy) {
|
|
250
|
+
const cos = Math.cos(rotationRad)
|
|
251
|
+
const sin = Math.sin(rotationRad)
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
x: point.x * cos - point.y * sin + dx,
|
|
255
|
+
y: point.x * sin + point.y * cos + dy
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -2,9 +2,11 @@ import { PcbScene3dArcUtils } from './PcbScene3dArcUtils.mjs'
|
|
|
2
2
|
import { PcbScene3dPadFactory } from './PcbScene3dPadFactory.mjs'
|
|
3
3
|
import { PcbScene3dCopperTextFactory } from './PcbScene3dCopperTextFactory.mjs'
|
|
4
4
|
import { PcbScene3dMaskCoveredCopperMaterial } from './PcbScene3dMaskCoveredCopperMaterial.mjs'
|
|
5
|
-
import { PcbScene3dGeometryZCompressor } from './PcbScene3dGeometryZCompressor.mjs'
|
|
6
5
|
import { PcbScene3dCopperOcclusionClipper } from './PcbScene3dCopperOcclusionClipper.mjs'
|
|
7
6
|
import { PcbScene3dCopperLayerFilter } from './PcbScene3dCopperLayerFilter.mjs'
|
|
7
|
+
import { PcbScene3dCopperFillMeshBuilder } from './PcbScene3dCopperFillMeshBuilder.mjs'
|
|
8
|
+
import { PcbScene3dMaskCoveredCopperSideGroupBuilder } from './PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs'
|
|
9
|
+
import { PcbScene3dCopperDrillCutoutBuilder } from './PcbScene3dCopperDrillCutoutBuilder.mjs'
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Builds copper-detail meshes for the interactive 3D PCB scene.
|
|
@@ -27,11 +29,11 @@ export class PcbScene3dCopperFactory {
|
|
|
27
29
|
/**
|
|
28
30
|
* Builds the combined top and bottom copper group.
|
|
29
31
|
* @param {any} THREE
|
|
30
|
-
* @param {{ tracks?: any[], arcs?: any[], pads?: any[], vias?: any[], copperTexts?: any[] }} detail
|
|
32
|
+
* @param {{ tracks?: any[], arcs?: any[], fills?: any[], polygons?: any[], pads?: any[], vias?: any[], copperTexts?: any[] }} detail
|
|
31
33
|
* @param {number} topZ
|
|
32
34
|
* @param {number} bottomZ
|
|
33
35
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
34
|
-
* @param {{ coordinateSystem?: string }} [options]
|
|
36
|
+
* @param {{ coordinateSystem?: string, drillCutouts?: any[], drillDetail?: object }} [options]
|
|
35
37
|
* @returns {any}
|
|
36
38
|
*/
|
|
37
39
|
static buildGroup(
|
|
@@ -51,6 +53,10 @@ export class PcbScene3dCopperFactory {
|
|
|
51
53
|
'top'
|
|
52
54
|
),
|
|
53
55
|
arcs: PcbScene3dCopperLayerFilter.arcs(detail?.arcs, 'top'),
|
|
56
|
+
fills: PcbScene3dCopperLayerFilter.fills(
|
|
57
|
+
[...(detail?.fills || []), ...(detail?.polygons || [])],
|
|
58
|
+
'top'
|
|
59
|
+
),
|
|
54
60
|
pads: detail?.pads || [],
|
|
55
61
|
vias: detail?.vias || [],
|
|
56
62
|
copperTexts: detail?.copperTexts || []
|
|
@@ -68,6 +74,10 @@ export class PcbScene3dCopperFactory {
|
|
|
68
74
|
'bottom'
|
|
69
75
|
),
|
|
70
76
|
arcs: PcbScene3dCopperLayerFilter.arcs(detail?.arcs, 'bottom'),
|
|
77
|
+
fills: PcbScene3dCopperLayerFilter.fills(
|
|
78
|
+
[...(detail?.fills || []), ...(detail?.polygons || [])],
|
|
79
|
+
'bottom'
|
|
80
|
+
),
|
|
71
81
|
pads: detail?.pads || [],
|
|
72
82
|
vias: detail?.vias || [],
|
|
73
83
|
copperTexts: detail?.copperTexts || []
|
|
@@ -78,24 +88,20 @@ export class PcbScene3dCopperFactory {
|
|
|
78
88
|
options
|
|
79
89
|
)
|
|
80
90
|
|
|
81
|
-
if (topGroup.children.length)
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
if (bottomGroup.children.length) {
|
|
85
|
-
group.add(bottomGroup)
|
|
86
|
-
}
|
|
91
|
+
if (topGroup.children.length) group.add(topGroup)
|
|
92
|
+
if (bottomGroup.children.length) group.add(bottomGroup)
|
|
87
93
|
|
|
88
94
|
return group
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
/**
|
|
92
|
-
* Builds top and bottom
|
|
98
|
+
* Builds top and bottom copper detail that is covered by solder mask.
|
|
93
99
|
* @param {any} THREE
|
|
94
|
-
* @param {{ tracks?: any[], arcs?: any[] }} detail Mask-covered detail.
|
|
100
|
+
* @param {{ tracks?: any[], arcs?: any[], fills?: any[], polygons?: any[] }} detail Mask-covered detail.
|
|
95
101
|
* @param {number} topZ
|
|
96
102
|
* @param {number} bottomZ
|
|
97
103
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
98
|
-
* @param {{ solderMaskColor?: number, occlusionCutouts?:
|
|
104
|
+
* @param {{ solderMaskColor?: number, occlusionCutouts?: object, drillCutouts?: any[], drillDetail?: object }} [options]
|
|
99
105
|
* @returns {any}
|
|
100
106
|
*/
|
|
101
107
|
static buildMaskCoveredGroup(
|
|
@@ -111,6 +117,21 @@ export class PcbScene3dCopperFactory {
|
|
|
111
117
|
THREE,
|
|
112
118
|
options
|
|
113
119
|
)
|
|
120
|
+
const drillCutouts = Array.isArray(options?.drillCutouts)
|
|
121
|
+
? options.drillCutouts
|
|
122
|
+
: PcbScene3dCopperDrillCutoutBuilder.resolve(
|
|
123
|
+
options?.drillDetail || detail
|
|
124
|
+
)
|
|
125
|
+
const topCutouts = PcbScene3dCopperOcclusionClipper.normalizeCutouts(
|
|
126
|
+
[...(options?.occlusionCutouts?.top || []), ...drillCutouts],
|
|
127
|
+
normalizeBoardPoint,
|
|
128
|
+
false
|
|
129
|
+
)
|
|
130
|
+
const bottomCutouts = PcbScene3dCopperOcclusionClipper.normalizeCutouts(
|
|
131
|
+
[...(options?.occlusionCutouts?.bottom || []), ...drillCutouts],
|
|
132
|
+
normalizeBoardPoint,
|
|
133
|
+
true
|
|
134
|
+
)
|
|
114
135
|
const topGroup = PcbScene3dCopperFactory.#buildMaskCoveredSideGroup(
|
|
115
136
|
THREE,
|
|
116
137
|
{
|
|
@@ -118,17 +139,17 @@ export class PcbScene3dCopperFactory {
|
|
|
118
139
|
detail?.tracks,
|
|
119
140
|
'top'
|
|
120
141
|
),
|
|
121
|
-
arcs: PcbScene3dCopperLayerFilter.arcs(detail?.arcs, 'top')
|
|
142
|
+
arcs: PcbScene3dCopperLayerFilter.arcs(detail?.arcs, 'top'),
|
|
143
|
+
fills: PcbScene3dCopperLayerFilter.fills(
|
|
144
|
+
[...(detail?.fills || []), ...(detail?.polygons || [])],
|
|
145
|
+
'top'
|
|
146
|
+
)
|
|
122
147
|
},
|
|
123
148
|
Math.abs(Number(topZ || 0)),
|
|
124
149
|
normalizeBoardPoint,
|
|
125
150
|
false,
|
|
126
151
|
material,
|
|
127
|
-
|
|
128
|
-
options?.occlusionCutouts?.top,
|
|
129
|
-
normalizeBoardPoint,
|
|
130
|
-
false
|
|
131
|
-
)
|
|
152
|
+
topCutouts
|
|
132
153
|
)
|
|
133
154
|
const bottomGroup = PcbScene3dCopperFactory.#buildMaskCoveredSideGroup(
|
|
134
155
|
THREE,
|
|
@@ -137,25 +158,21 @@ export class PcbScene3dCopperFactory {
|
|
|
137
158
|
detail?.tracks,
|
|
138
159
|
'bottom'
|
|
139
160
|
),
|
|
140
|
-
arcs: PcbScene3dCopperLayerFilter.arcs(detail?.arcs, 'bottom')
|
|
161
|
+
arcs: PcbScene3dCopperLayerFilter.arcs(detail?.arcs, 'bottom'),
|
|
162
|
+
fills: PcbScene3dCopperLayerFilter.fills(
|
|
163
|
+
[...(detail?.fills || []), ...(detail?.polygons || [])],
|
|
164
|
+
'bottom'
|
|
165
|
+
)
|
|
141
166
|
},
|
|
142
167
|
Math.abs(Number(bottomZ || 0)),
|
|
143
168
|
normalizeBoardPoint,
|
|
144
169
|
true,
|
|
145
170
|
material,
|
|
146
|
-
|
|
147
|
-
options?.occlusionCutouts?.bottom,
|
|
148
|
-
normalizeBoardPoint,
|
|
149
|
-
true
|
|
150
|
-
)
|
|
171
|
+
bottomCutouts
|
|
151
172
|
)
|
|
152
173
|
|
|
153
|
-
if (topGroup.children.length)
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
if (bottomGroup.children.length) {
|
|
157
|
-
group.add(bottomGroup)
|
|
158
|
-
}
|
|
174
|
+
if (topGroup.children.length) group.add(topGroup)
|
|
175
|
+
if (bottomGroup.children.length) group.add(bottomGroup)
|
|
159
176
|
|
|
160
177
|
return group
|
|
161
178
|
}
|
|
@@ -163,11 +180,11 @@ export class PcbScene3dCopperFactory {
|
|
|
163
180
|
/**
|
|
164
181
|
* Builds one side-specific copper group.
|
|
165
182
|
* @param {any} THREE
|
|
166
|
-
* @param {{ tracks?: any[], arcs?: any[], pads?: any[], vias?: any[], copperTexts?: any[] }} detail
|
|
183
|
+
* @param {{ tracks?: any[], arcs?: any[], fills?: any[], pads?: any[], vias?: any[], copperTexts?: any[] }} detail
|
|
167
184
|
* @param {number} z
|
|
168
185
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
169
186
|
* @param {boolean} mirrorY
|
|
170
|
-
* @param {{ coordinateSystem?: string }} [options]
|
|
187
|
+
* @param {{ coordinateSystem?: string, drillCutouts?: any[], drillDetail?: object }} [options]
|
|
171
188
|
* @returns {any}
|
|
172
189
|
*/
|
|
173
190
|
static #buildSideGroup(
|
|
@@ -179,6 +196,13 @@ export class PcbScene3dCopperFactory {
|
|
|
179
196
|
options = {}
|
|
180
197
|
) {
|
|
181
198
|
const group = new THREE.Group()
|
|
199
|
+
const drillCutouts =
|
|
200
|
+
PcbScene3dCopperDrillCutoutBuilder.resolveFromOptions(
|
|
201
|
+
options,
|
|
202
|
+
detail,
|
|
203
|
+
normalizeBoardPoint,
|
|
204
|
+
mirrorY
|
|
205
|
+
)
|
|
182
206
|
const trackMesh = PcbScene3dCopperFactory.#buildTrackMesh(
|
|
183
207
|
THREE,
|
|
184
208
|
detail?.tracks || [],
|
|
@@ -193,6 +217,16 @@ export class PcbScene3dCopperFactory {
|
|
|
193
217
|
normalizeBoardPoint,
|
|
194
218
|
mirrorY
|
|
195
219
|
)
|
|
220
|
+
const fillMesh = PcbScene3dCopperFillMeshBuilder.build(
|
|
221
|
+
THREE,
|
|
222
|
+
detail?.fills || [],
|
|
223
|
+
z,
|
|
224
|
+
PcbScene3dCopperFactory.#COPPER_THICKNESS_MIL,
|
|
225
|
+
normalizeBoardPoint,
|
|
226
|
+
mirrorY,
|
|
227
|
+
PcbScene3dCopperFactory.#buildMaterial(THREE),
|
|
228
|
+
drillCutouts
|
|
229
|
+
)
|
|
196
230
|
const padGroup = PcbScene3dPadFactory.buildGroup(
|
|
197
231
|
THREE,
|
|
198
232
|
detail?.pads || [],
|
|
@@ -215,29 +249,20 @@ export class PcbScene3dCopperFactory {
|
|
|
215
249
|
}
|
|
216
250
|
)
|
|
217
251
|
|
|
218
|
-
if (trackMesh)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (padGroup.children.length) {
|
|
225
|
-
group.add(padGroup)
|
|
226
|
-
}
|
|
227
|
-
if (textGroup.children.length) {
|
|
228
|
-
group.add(textGroup)
|
|
229
|
-
}
|
|
230
|
-
if (mirrorY && group.children.length) {
|
|
231
|
-
group.rotation.x = Math.PI
|
|
232
|
-
}
|
|
252
|
+
if (trackMesh) group.add(trackMesh)
|
|
253
|
+
if (arcMesh) group.add(arcMesh)
|
|
254
|
+
if (fillMesh) group.add(fillMesh)
|
|
255
|
+
if (padGroup.children.length) group.add(padGroup)
|
|
256
|
+
if (textGroup.children.length) group.add(textGroup)
|
|
257
|
+
if (mirrorY && group.children.length) group.rotation.x = Math.PI
|
|
233
258
|
|
|
234
259
|
return group
|
|
235
260
|
}
|
|
236
261
|
|
|
237
262
|
/**
|
|
238
|
-
* Builds one side of the mask-covered
|
|
263
|
+
* Builds one side of the mask-covered copper relief.
|
|
239
264
|
* @param {any} THREE
|
|
240
|
-
* @param {{ tracks?: any[], arcs?: any[] }} detail Mask-covered detail.
|
|
265
|
+
* @param {{ tracks?: any[], arcs?: any[], fills?: any[] }} detail Mask-covered detail.
|
|
241
266
|
* @param {number} z
|
|
242
267
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
243
268
|
* @param {boolean} mirrorY
|
|
@@ -254,7 +279,6 @@ export class PcbScene3dCopperFactory {
|
|
|
254
279
|
material,
|
|
255
280
|
occlusionCutouts
|
|
256
281
|
) {
|
|
257
|
-
const group = new THREE.Group()
|
|
258
282
|
const trackMesh = PcbScene3dCopperFactory.#buildTrackMesh(
|
|
259
283
|
THREE,
|
|
260
284
|
detail?.tracks || [],
|
|
@@ -273,28 +297,23 @@ export class PcbScene3dCopperFactory {
|
|
|
273
297
|
material,
|
|
274
298
|
occlusionCutouts
|
|
275
299
|
)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
if (mirrorY && group.children.length) {
|
|
294
|
-
group.rotation.x = Math.PI
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return group
|
|
300
|
+
const fillMesh = PcbScene3dCopperFillMeshBuilder.build(
|
|
301
|
+
THREE,
|
|
302
|
+
detail?.fills || [],
|
|
303
|
+
z,
|
|
304
|
+
PcbScene3dCopperFactory.#COPPER_THICKNESS_MIL,
|
|
305
|
+
normalizeBoardPoint,
|
|
306
|
+
mirrorY,
|
|
307
|
+
material,
|
|
308
|
+
occlusionCutouts
|
|
309
|
+
)
|
|
310
|
+
return PcbScene3dMaskCoveredCopperSideGroupBuilder.build(THREE, {
|
|
311
|
+
trackMesh,
|
|
312
|
+
arcMesh,
|
|
313
|
+
fillMesh,
|
|
314
|
+
z,
|
|
315
|
+
mirrorY
|
|
316
|
+
})
|
|
298
317
|
}
|
|
299
318
|
|
|
300
319
|
/**
|
|
@@ -309,7 +328,7 @@ export class PcbScene3dCopperFactory {
|
|
|
309
328
|
/**
|
|
310
329
|
* Builds one widened copper-track mesh for one face.
|
|
311
330
|
* @param {any} THREE
|
|
312
|
-
* @param {
|
|
331
|
+
* @param {any[]} tracks
|
|
313
332
|
* @param {number} z
|
|
314
333
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
315
334
|
* @param {boolean} mirrorY
|
|
@@ -362,7 +381,7 @@ export class PcbScene3dCopperFactory {
|
|
|
362
381
|
/**
|
|
363
382
|
* Builds one widened copper-arc mesh for one face.
|
|
364
383
|
* @param {any} THREE
|
|
365
|
-
* @param {
|
|
384
|
+
* @param {any[]} arcs
|
|
366
385
|
* @param {number} z
|
|
367
386
|
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
368
387
|
* @param {boolean} mirrorY
|
|
@@ -463,7 +482,7 @@ export class PcbScene3dCopperFactory {
|
|
|
463
482
|
* @param {{ x: number, y: number }} end
|
|
464
483
|
* @param {number} width
|
|
465
484
|
* @param {number} z
|
|
466
|
-
* @param {
|
|
485
|
+
* @param {object} [options]
|
|
467
486
|
* @returns {void}
|
|
468
487
|
*/
|
|
469
488
|
static #appendTrackTriangles(
|
|
@@ -705,9 +724,7 @@ export class PcbScene3dCopperFactory {
|
|
|
705
724
|
*/
|
|
706
725
|
static #appendDiscTriangles(positions, center, radius, z) {
|
|
707
726
|
const safeRadius = Math.max(Number(radius || 0), 0)
|
|
708
|
-
if (safeRadius <= 0)
|
|
709
|
-
return
|
|
710
|
-
}
|
|
727
|
+
if (safeRadius <= 0) return
|
|
711
728
|
|
|
712
729
|
for (
|
|
713
730
|
let index = 0;
|
|
@@ -755,9 +772,9 @@ export class PcbScene3dCopperFactory {
|
|
|
755
772
|
* @param {{ x: number, y: number }} center Cap center.
|
|
756
773
|
* @param {number} radius Cap radius.
|
|
757
774
|
* @param {number} z Center Z position.
|
|
758
|
-
* @param {number} outwardX
|
|
759
|
-
* @param {number} outwardY
|
|
760
|
-
* @param {boolean} [includeSideWall]
|
|
775
|
+
* @param {number} outwardX Outward unit X.
|
|
776
|
+
* @param {number} outwardY Outward unit Y.
|
|
777
|
+
* @param {boolean} [includeSideWall] Emits cap perimeter wall.
|
|
761
778
|
* @returns {void}
|
|
762
779
|
*/
|
|
763
780
|
static #appendRoundCapTriangles(
|
|
@@ -771,9 +788,7 @@ export class PcbScene3dCopperFactory {
|
|
|
771
788
|
) {
|
|
772
789
|
const safeRadius = Math.max(Number(radius || 0), 0)
|
|
773
790
|
const outwardLength = Math.hypot(outwardX, outwardY)
|
|
774
|
-
if (safeRadius <= 0)
|
|
775
|
-
return
|
|
776
|
-
}
|
|
791
|
+
if (safeRadius <= 0) return
|
|
777
792
|
if (outwardLength <= 0.001) {
|
|
778
793
|
PcbScene3dCopperFactory.#appendDiscTriangles(
|
|
779
794
|
positions,
|
|
@@ -916,7 +931,6 @@ export class PcbScene3dCopperFactory {
|
|
|
916
931
|
const halfThickness = PcbScene3dCopperFactory.#COPPER_THICKNESS_MIL / 2
|
|
917
932
|
const topZ = z + halfThickness
|
|
918
933
|
const bottomZ = z - halfThickness
|
|
919
|
-
|
|
920
934
|
positions.push(
|
|
921
935
|
a.x,
|
|
922
936
|
a.y,
|