pcb-scene3d-viewer 1.1.49 → 1.2.0
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 +46 -21
- package/docs/api.md +109 -8
- package/docs/circuitjson.md +143 -29
- package/docs/model-format.md +50 -7
- package/docs/release-notes-v1.2.0.md +122 -0
- package/docs/testing.md +30 -1
- package/package.json +6 -3
- package/spec/library-scope.md +10 -2
- package/src/CircuitJsonCadModelAssetResolver.mjs +697 -83
- package/src/PcbAssemblyBoardSubstrateBuilder.mjs +43 -0
- package/src/PcbAssemblyGeometryBuilder.mjs +40 -18
- package/src/PcbAssemblyModelMeshLoader.mjs +101 -150
- package/src/PcbAssemblyPadMeshBuilder.mjs +22 -0
- package/src/PcbModelArchiveExporter.mjs +28 -112
- package/src/PcbModelArchiveSourceBundle.mjs +326 -0
- package/src/PcbScene3dAabbIndex.mjs +464 -0
- package/src/PcbScene3dBoardAssemblyPresentation.mjs +1 -1
- package/src/PcbScene3dBoardEdgeCutoutBuilder.mjs +9 -5
- package/src/PcbScene3dBoardMaterialPalette.mjs +29 -0
- package/src/PcbScene3dBoardShapeFactory.mjs +11 -118
- package/src/PcbScene3dBoardSolderMaskFactory.mjs +65 -39
- package/src/PcbScene3dCircuitJsonAdapter.mjs +151 -48
- package/src/PcbScene3dCircuitJsonDrillDetail.mjs +31 -0
- package/src/PcbScene3dCircuitJsonGeometry.mjs +184 -33
- package/src/PcbScene3dCircuitJsonInput.mjs +132 -0
- package/src/PcbScene3dCircuitJsonModelAsset.mjs +40 -0
- package/src/PcbScene3dController.mjs +40 -34
- package/src/PcbScene3dCopperFactory.mjs +36 -22
- package/src/PcbScene3dCopperFillAreaClipper.mjs +133 -235
- package/src/PcbScene3dCopperFillCoverageContext.mjs +204 -0
- package/src/PcbScene3dCopperFillLoopSetResolver.mjs +192 -0
- package/src/PcbScene3dCopperFillMeshBuilder.mjs +77 -295
- package/src/PcbScene3dCopperTextFactory.mjs +12 -4
- package/src/PcbScene3dCutoutCircleDetector.mjs +34 -17
- package/src/PcbScene3dCutoutGeometryFilter.mjs +104 -269
- package/src/PcbScene3dCutoutGridIndex.mjs +184 -0
- package/src/PcbScene3dDeferredModelFinalizer.mjs +52 -0
- package/src/PcbScene3dDescriptorSafeRecord.mjs +38 -0
- package/src/PcbScene3dDrillCutoutFilter.mjs +149 -143
- package/src/PcbScene3dDrillPathFactory.mjs +86 -16
- package/src/PcbScene3dDrillVoidFactory.mjs +35 -10
- package/src/PcbScene3dExternalModelGroupLoader.mjs +472 -31
- package/src/PcbScene3dExternalModels.mjs +23 -24
- package/src/PcbScene3dFacetedModelGroupBuilder.mjs +217 -0
- package/src/PcbScene3dGeometryZCompressor.mjs +4 -2
- package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +18 -4
- package/src/PcbScene3dMaskCoveredCopperSurfaceFilter.mjs +75 -10
- package/src/PcbScene3dModelContent.mjs +236 -0
- package/src/PcbScene3dModelFetchPolicy.mjs +304 -0
- package/src/PcbScene3dModelIdentity.mjs +106 -0
- package/src/PcbScene3dPlatedDrillSpecResolver.mjs +141 -0
- package/src/PcbScene3dPreparedPolygon.mjs +709 -0
- package/src/PcbScene3dPreparedPolygonSet.mjs +70 -0
- package/src/PcbScene3dRuntime.mjs +45 -45
- package/src/PcbScene3dRuntimeBoardMeshes.mjs +85 -1
- package/src/PcbScene3dShapeHoleGeometryCleaner.mjs +4 -2
- package/src/PcbScene3dShellRenderer.mjs +75 -6
- package/src/PcbScene3dSilkscreenCutoutContext.mjs +255 -0
- package/src/PcbScene3dSilkscreenFactory.mjs +87 -127
- package/src/PcbScene3dSilkscreenFillSeamBuilder.mjs +11 -5
- package/src/PcbScene3dStepLoader.mjs +11 -10
- package/src/PcbScene3dText.mjs +1 -1
- package/src/PcbScene3dTriangleVertexQueryBounds.mjs +302 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Builds reusable circular and slotted drill paths for the 3D PCB scene.
|
|
3
3
|
*/
|
|
4
4
|
export class PcbScene3dDrillPathFactory {
|
|
5
|
+
static #PAD_HOLE_SHAPE_RECT = 1
|
|
5
6
|
static #PAD_HOLE_SHAPE_SLOT = 2
|
|
6
7
|
static #ARC_SEGMENTS = 12
|
|
7
8
|
|
|
@@ -34,7 +35,7 @@ export class PcbScene3dDrillPathFactory {
|
|
|
34
35
|
/**
|
|
35
36
|
* Resolves deduped board-space drill specs from pads and vias.
|
|
36
37
|
* @param {{ pads?: any[], vias?: any[] }} detail
|
|
37
|
-
* @returns {{ x: number, y: number, diameter: number, slotLength?: number | null, rotationDeg?: number | null }[]}
|
|
38
|
+
* @returns {{ x: number, y: number, diameter: number, width?: number, height?: number, shape?: 'circle' | 'pill' | 'rect', slotLength?: number | null, rotationDeg?: number | null }[]}
|
|
38
39
|
*/
|
|
39
40
|
static resolveBoardDrillSpecs(detail) {
|
|
40
41
|
const seen = new Set()
|
|
@@ -64,7 +65,7 @@ export class PcbScene3dDrillPathFactory {
|
|
|
64
65
|
/**
|
|
65
66
|
* Builds one local pad drill path centered on the pad origin.
|
|
66
67
|
* @param {any} THREE
|
|
67
|
-
* @param {{ holeDiameter?: number, holeShape?: number | null, holeSlotLength?: number | null, holeRotation?: number | null, rotation?: number | null }} pad
|
|
68
|
+
* @param {{ holeDiameter?: number, holeWidth?: number, holeHeight?: number, holeShape?: number | null, holeSlotLength?: number | null, holeRotation?: number | null, rotation?: number | null }} pad
|
|
68
69
|
* @returns {any | null}
|
|
69
70
|
*/
|
|
70
71
|
static buildPadHolePath(THREE, pad) {
|
|
@@ -73,7 +74,14 @@ export class PcbScene3dDrillPathFactory {
|
|
|
73
74
|
? PcbScene3dDrillPathFactory.buildDrillPath(THREE, {
|
|
74
75
|
...drillSpec,
|
|
75
76
|
x: 0,
|
|
76
|
-
y: 0
|
|
77
|
+
y: 0,
|
|
78
|
+
rotationDeg:
|
|
79
|
+
drillSpec.shape === 'circle'
|
|
80
|
+
? 0
|
|
81
|
+
: PcbScene3dDrillPathFactory.#normalizeAngle(
|
|
82
|
+
Number(drillSpec.rotationDeg || 0) -
|
|
83
|
+
Number(pad?.rotation || 0)
|
|
84
|
+
)
|
|
77
85
|
})
|
|
78
86
|
: null
|
|
79
87
|
}
|
|
@@ -94,6 +102,9 @@ export class PcbScene3dDrillPathFactory {
|
|
|
94
102
|
x: 0,
|
|
95
103
|
y: 0,
|
|
96
104
|
diameter: holeDiameter,
|
|
105
|
+
width: holeDiameter,
|
|
106
|
+
height: holeDiameter,
|
|
107
|
+
shape: 'circle',
|
|
97
108
|
slotLength: null,
|
|
98
109
|
rotationDeg: 0
|
|
99
110
|
})
|
|
@@ -102,7 +113,7 @@ export class PcbScene3dDrillPathFactory {
|
|
|
102
113
|
/**
|
|
103
114
|
* Builds one drill path from a normalized drill descriptor.
|
|
104
115
|
* @param {any} THREE
|
|
105
|
-
* @param {{ x: number, y: number, diameter: number, slotLength?: number | null, rotationDeg?: number | null }} drillSpec
|
|
116
|
+
* @param {{ x: number, y: number, diameter: number, width?: number, height?: number, shape?: 'circle' | 'pill' | 'rect', slotLength?: number | null, rotationDeg?: number | null }} drillSpec
|
|
106
117
|
* @returns {any | null}
|
|
107
118
|
*/
|
|
108
119
|
static buildDrillPath(THREE, drillSpec) {
|
|
@@ -111,6 +122,10 @@ export class PcbScene3dDrillPathFactory {
|
|
|
111
122
|
return null
|
|
112
123
|
}
|
|
113
124
|
|
|
125
|
+
if (drillSpec?.shape === 'rect') {
|
|
126
|
+
return PcbScene3dDrillPathFactory.#buildRectPath(THREE, drillSpec)
|
|
127
|
+
}
|
|
128
|
+
|
|
114
129
|
if (Number(drillSpec?.slotLength || 0) > diameter + 0.001) {
|
|
115
130
|
return PcbScene3dDrillPathFactory.#buildSlotPath(THREE, drillSpec)
|
|
116
131
|
}
|
|
@@ -121,7 +136,7 @@ export class PcbScene3dDrillPathFactory {
|
|
|
121
136
|
/**
|
|
122
137
|
* Resolves board-space via drill specs from normalized via detail.
|
|
123
138
|
* @param {{ x?: number, y?: number, holeDiameter?: number }[]} vias
|
|
124
|
-
* @returns {{ x: number, y: number, diameter: number, slotLength: null, rotationDeg: 0 }[]}
|
|
139
|
+
* @returns {{ x: number, y: number, diameter: number, width: number, height: number, shape: 'circle', slotLength: null, rotationDeg: 0 }[]}
|
|
125
140
|
*/
|
|
126
141
|
static #resolveViaDrillSpecs(vias) {
|
|
127
142
|
return (vias || [])
|
|
@@ -135,6 +150,9 @@ export class PcbScene3dDrillPathFactory {
|
|
|
135
150
|
x: Number(via?.x || 0),
|
|
136
151
|
y: Number(via?.y || 0),
|
|
137
152
|
diameter,
|
|
153
|
+
width: diameter,
|
|
154
|
+
height: diameter,
|
|
155
|
+
shape: 'circle',
|
|
138
156
|
slotLength: null,
|
|
139
157
|
rotationDeg: 0
|
|
140
158
|
}
|
|
@@ -145,7 +163,7 @@ export class PcbScene3dDrillPathFactory {
|
|
|
145
163
|
/**
|
|
146
164
|
* Resolves board-space pad drill specs from normalized pad detail.
|
|
147
165
|
* @param {any[]} pads
|
|
148
|
-
* @returns {{ x: number, y: number, diameter: number, slotLength?: number | null, rotationDeg?: number | null }[]}
|
|
166
|
+
* @returns {{ x: number, y: number, diameter: number, width?: number, height?: number, shape?: 'circle' | 'pill' | 'rect', slotLength?: number | null, rotationDeg?: number | null }[]}
|
|
149
167
|
*/
|
|
150
168
|
static #resolvePadDrillSpecs(pads) {
|
|
151
169
|
return (pads || [])
|
|
@@ -161,11 +179,10 @@ export class PcbScene3dDrillPathFactory {
|
|
|
161
179
|
x: Number(pad?.x || 0),
|
|
162
180
|
y: Number(pad?.y || 0),
|
|
163
181
|
rotationDeg:
|
|
164
|
-
drillSpec.
|
|
182
|
+
drillSpec.shape === 'circle'
|
|
165
183
|
? 0
|
|
166
184
|
: PcbScene3dDrillPathFactory.#normalizeAngle(
|
|
167
|
-
Number(
|
|
168
|
-
Number(drillSpec.rotationDeg || 0)
|
|
185
|
+
Number(drillSpec.rotationDeg || 0)
|
|
169
186
|
)
|
|
170
187
|
}
|
|
171
188
|
})
|
|
@@ -174,8 +191,8 @@ export class PcbScene3dDrillPathFactory {
|
|
|
174
191
|
|
|
175
192
|
/**
|
|
176
193
|
* Resolves one pad-local drill spec when the pad is through-hole.
|
|
177
|
-
* @param {{ holeDiameter?: number, holeShape?: number | null, holeSlotLength?: number | null, holeRotation?: number | null }} pad
|
|
178
|
-
* @returns {{ diameter: number, slotLength?: number | null, rotationDeg?: number | null } | null}
|
|
194
|
+
* @param {{ holeDiameter?: number, holeWidth?: number, holeHeight?: number, holeShape?: number | null, holeSlotLength?: number | null, holeRotation?: number | null }} pad
|
|
195
|
+
* @returns {{ diameter: number, width: number, height: number, shape: 'circle' | 'pill' | 'rect', slotLength?: number | null, rotationDeg?: number | null } | null}
|
|
179
196
|
*/
|
|
180
197
|
static #resolvePadDrillSpec(pad) {
|
|
181
198
|
const diameter = Number(pad?.holeDiameter || 0)
|
|
@@ -183,20 +200,70 @@ export class PcbScene3dDrillPathFactory {
|
|
|
183
200
|
return null
|
|
184
201
|
}
|
|
185
202
|
|
|
203
|
+
const holeShape = Number(pad?.holeShape)
|
|
204
|
+
const shape =
|
|
205
|
+
holeShape === PcbScene3dDrillPathFactory.#PAD_HOLE_SHAPE_RECT
|
|
206
|
+
? 'rect'
|
|
207
|
+
: holeShape === PcbScene3dDrillPathFactory.#PAD_HOLE_SHAPE_SLOT
|
|
208
|
+
? 'pill'
|
|
209
|
+
: 'circle'
|
|
210
|
+
const width = Number(pad?.holeWidth || diameter)
|
|
211
|
+
const height = Number(pad?.holeHeight || diameter)
|
|
186
212
|
const slotLength =
|
|
187
|
-
Number(pad?.
|
|
188
|
-
PcbScene3dDrillPathFactory.#PAD_HOLE_SHAPE_SLOT &&
|
|
189
|
-
Number(pad?.holeSlotLength || 0) > diameter
|
|
213
|
+
shape === 'pill' && Number(pad?.holeSlotLength || 0) > diameter
|
|
190
214
|
? Number(pad?.holeSlotLength || 0)
|
|
191
215
|
: null
|
|
192
216
|
|
|
193
217
|
return {
|
|
194
218
|
diameter,
|
|
219
|
+
width,
|
|
220
|
+
height,
|
|
221
|
+
shape,
|
|
195
222
|
slotLength,
|
|
196
|
-
rotationDeg: Number(pad?.holeRotation
|
|
223
|
+
rotationDeg: Number(pad?.holeRotation ?? pad?.rotation ?? 0)
|
|
197
224
|
}
|
|
198
225
|
}
|
|
199
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Builds one rotated rectangular drill path.
|
|
229
|
+
* @param {any} THREE
|
|
230
|
+
* @param {{ x: number, y: number, diameter: number, width?: number, height?: number, rotationDeg?: number | null }} drillSpec
|
|
231
|
+
* @returns {any}
|
|
232
|
+
*/
|
|
233
|
+
static #buildRectPath(THREE, drillSpec) {
|
|
234
|
+
const halfWidth = Math.max(
|
|
235
|
+
Number(drillSpec?.width || drillSpec?.diameter || 0) / 2,
|
|
236
|
+
0.6
|
|
237
|
+
)
|
|
238
|
+
const halfHeight = Math.max(
|
|
239
|
+
Number(drillSpec?.height || drillSpec?.diameter || 0) / 2,
|
|
240
|
+
0.6
|
|
241
|
+
)
|
|
242
|
+
const rotationRad = (Number(drillSpec.rotationDeg || 0) * Math.PI) / 180
|
|
243
|
+
const centerX = Number(drillSpec.x || 0)
|
|
244
|
+
const centerY = Number(drillSpec.y || 0)
|
|
245
|
+
const points = [
|
|
246
|
+
{ x: -halfWidth, y: -halfHeight },
|
|
247
|
+
{ x: halfWidth, y: -halfHeight },
|
|
248
|
+
{ x: halfWidth, y: halfHeight },
|
|
249
|
+
{ x: -halfWidth, y: halfHeight }
|
|
250
|
+
].map((point) =>
|
|
251
|
+
PcbScene3dDrillPathFactory.#rotateAndTranslatePoint(
|
|
252
|
+
point,
|
|
253
|
+
rotationRad,
|
|
254
|
+
centerX,
|
|
255
|
+
centerY
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
const path = new THREE.Path()
|
|
259
|
+
path.moveTo(points[0].x, points[0].y)
|
|
260
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
261
|
+
path.lineTo(points[index].x, points[index].y)
|
|
262
|
+
}
|
|
263
|
+
path.closePath()
|
|
264
|
+
return path
|
|
265
|
+
}
|
|
266
|
+
|
|
200
267
|
/**
|
|
201
268
|
* Builds one circular drill path.
|
|
202
269
|
* @param {any} THREE
|
|
@@ -219,7 +286,7 @@ export class PcbScene3dDrillPathFactory {
|
|
|
219
286
|
/**
|
|
220
287
|
* Builds one slotted drill path as a rotated rounded rectangle.
|
|
221
288
|
* @param {any} THREE
|
|
222
|
-
* @param {{ x: number, y: number, diameter: number, slotLength?: number | null, rotationDeg?: number | null }} drillSpec
|
|
289
|
+
* @param {{ x: number, y: number, diameter: number, width?: number, height?: number, shape?: string, slotLength?: number | null, rotationDeg?: number | null }} drillSpec
|
|
223
290
|
* @returns {any}
|
|
224
291
|
*/
|
|
225
292
|
static #buildSlotPath(THREE, drillSpec) {
|
|
@@ -345,6 +412,9 @@ export class PcbScene3dDrillPathFactory {
|
|
|
345
412
|
Number(drillSpec.x || 0).toFixed(4),
|
|
346
413
|
Number(drillSpec.y || 0).toFixed(4),
|
|
347
414
|
Number(drillSpec.diameter || 0).toFixed(4),
|
|
415
|
+
String(drillSpec.shape || 'circle'),
|
|
416
|
+
Number(drillSpec.width || drillSpec.diameter || 0).toFixed(4),
|
|
417
|
+
Number(drillSpec.height || drillSpec.diameter || 0).toFixed(4),
|
|
348
418
|
Number(drillSpec.slotLength || 0).toFixed(4),
|
|
349
419
|
Number(drillSpec.rotationDeg || 0).toFixed(4)
|
|
350
420
|
].join(':')
|
|
@@ -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(
|
|
@@ -50,7 +51,7 @@ export class PcbScene3dDrillVoidFactory {
|
|
|
50
51
|
PcbScene3dDrillPathFactory.resolveBoardDrillSpecs(detail).forEach(
|
|
51
52
|
(drillSpec) => {
|
|
52
53
|
if (
|
|
53
|
-
PcbScene3dDrillVoidFactory.#
|
|
54
|
+
PcbScene3dDrillVoidFactory.#isNonCircularDrill(drillSpec) ||
|
|
54
55
|
edgeDrillKeys.has(
|
|
55
56
|
PcbScene3dDrillVoidFactory.#drillKey(drillSpec)
|
|
56
57
|
)
|
|
@@ -113,14 +114,12 @@ 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
|
|
@@ -128,14 +127,40 @@ export class PcbScene3dDrillVoidFactory {
|
|
|
128
127
|
}
|
|
129
128
|
|
|
130
129
|
/**
|
|
131
|
-
*
|
|
132
|
-
* @param {{
|
|
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
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Checks whether one drill needs its non-circular substrate wall directly.
|
|
156
|
+
* @param {{ diameter?: number, shape?: string, slotLength?: number | null }} drillSpec Drill spec.
|
|
133
157
|
* @returns {boolean}
|
|
134
158
|
*/
|
|
135
|
-
static #
|
|
159
|
+
static #isNonCircularDrill(drillSpec) {
|
|
136
160
|
return (
|
|
161
|
+
drillSpec?.shape === 'rect' ||
|
|
137
162
|
Number(drillSpec?.slotLength || 0) >
|
|
138
|
-
|
|
163
|
+
Number(drillSpec?.diameter || 0) + 0.001
|
|
139
164
|
)
|
|
140
165
|
}
|
|
141
166
|
|