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
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { PcbScene3dBoardEdgeCutoutBuilder } from './PcbScene3dBoardEdgeCutoutBuilder.mjs'
|
|
2
1
|
import { PcbScene3dCopperTextFactory } from './PcbScene3dCopperTextFactory.mjs'
|
|
3
|
-
import { PcbScene3dCutoutCircleDetector } from './PcbScene3dCutoutCircleDetector.mjs'
|
|
4
2
|
import { PcbScene3dCutoutGeometryFilter } from './PcbScene3dCutoutGeometryFilter.mjs'
|
|
5
3
|
import { PcbScene3dDrillCutoutFilter } from './PcbScene3dDrillCutoutFilter.mjs'
|
|
6
4
|
import { PcbScene3dMaterialFinish } from './PcbScene3dMaterialFinish.mjs'
|
|
@@ -8,6 +6,7 @@ import { PcbScene3dPolygonOverlap } from './PcbScene3dPolygonOverlap.mjs'
|
|
|
8
6
|
import { PcbScene3dShapeHoleMerger } from './PcbScene3dShapeHoleMerger.mjs'
|
|
9
7
|
import { PcbScene3dShapePathFactory } from './PcbScene3dShapePathFactory.mjs'
|
|
10
8
|
import { PcbScene3dShapeHoleGeometryCleaner } from './PcbScene3dShapeHoleGeometryCleaner.mjs'
|
|
9
|
+
import { PcbScene3dSilkscreenCutoutContext } from './PcbScene3dSilkscreenCutoutContext.mjs'
|
|
11
10
|
import { PcbScene3dSilkscreenFillSeamBuilder } from './PcbScene3dSilkscreenFillSeamBuilder.mjs'
|
|
12
11
|
import { PcbScene3dSilkscreenStrokeWidthResolver } from './PcbScene3dSilkscreenStrokeWidthResolver.mjs'
|
|
13
12
|
import { PcbScene3dStrokeGeometryBuilder } from './PcbScene3dStrokeGeometryBuilder.mjs'
|
|
@@ -19,7 +18,6 @@ import { PcbScene3dTrueTypeTextFactory } from './PcbScene3dTrueTypeTextFactory.m
|
|
|
19
18
|
export class PcbScene3dSilkscreenFactory {
|
|
20
19
|
static #DEFAULT_SILKSCREEN_COLOR = 0xf8f6ef
|
|
21
20
|
static #FILL_THICKNESS_MIL = 0.8
|
|
22
|
-
static #GEOMETRY_EPSILON = 0.001
|
|
23
21
|
static #FULL_CIRCLE_EPSILON = 0.001
|
|
24
22
|
static #MIN_STROKE_WIDTH_MIL = 0.04
|
|
25
23
|
static #STROKE_MESH_POSITION_CHUNK_SIZE = 24000
|
|
@@ -102,6 +100,7 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
102
100
|
mirrorY
|
|
103
101
|
)
|
|
104
102
|
const surfaceCutouts = drillCutouts.concat(copperCutouts)
|
|
103
|
+
const cutoutContext = new PcbScene3dSilkscreenCutoutContext()
|
|
105
104
|
const strokeMaterial = PcbScene3dSilkscreenFactory.#buildMaterial(
|
|
106
105
|
THREE,
|
|
107
106
|
strokeColor
|
|
@@ -117,7 +116,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
117
116
|
normalizeBoardPoint,
|
|
118
117
|
mirrorY,
|
|
119
118
|
strokeMaterial,
|
|
120
|
-
surfaceCutouts
|
|
119
|
+
surfaceCutouts,
|
|
120
|
+
cutoutContext
|
|
121
121
|
)
|
|
122
122
|
const arcMesh = PcbScene3dSilkscreenFactory.#buildArcMesh(
|
|
123
123
|
THREE,
|
|
@@ -126,7 +126,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
126
126
|
normalizeBoardPoint,
|
|
127
127
|
mirrorY,
|
|
128
128
|
strokeMaterial,
|
|
129
|
-
surfaceCutouts
|
|
129
|
+
surfaceCutouts,
|
|
130
|
+
cutoutContext
|
|
130
131
|
)
|
|
131
132
|
const fillMeshes = PcbScene3dSilkscreenFactory.#buildFillMeshes(
|
|
132
133
|
THREE,
|
|
@@ -135,7 +136,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
135
136
|
normalizeBoardPoint,
|
|
136
137
|
mirrorY,
|
|
137
138
|
fillMaterial,
|
|
138
|
-
surfaceCutouts
|
|
139
|
+
surfaceCutouts,
|
|
140
|
+
cutoutContext
|
|
139
141
|
)
|
|
140
142
|
const fillSeamMeshes = PcbScene3dSilkscreenFillSeamBuilder.buildMeshes(
|
|
141
143
|
THREE,
|
|
@@ -144,7 +146,10 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
144
146
|
normalizeBoardPoint,
|
|
145
147
|
mirrorY,
|
|
146
148
|
fillMaterial,
|
|
147
|
-
surfaceCutouts
|
|
149
|
+
surfaceCutouts,
|
|
150
|
+
{
|
|
151
|
+
preparedPolygonCache: cutoutContext.preparedPolygonCache
|
|
152
|
+
}
|
|
148
153
|
)
|
|
149
154
|
const texts = Array.isArray(silkscreen?.texts) ? silkscreen.texts : []
|
|
150
155
|
const renderableTexts = texts.filter(
|
|
@@ -174,6 +179,7 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
174
179
|
fog: false
|
|
175
180
|
},
|
|
176
181
|
mirrorY,
|
|
182
|
+
preparedPolygonCache: cutoutContext.preparedPolygonCache,
|
|
177
183
|
side: mirrorY ? 'bottom' : 'top'
|
|
178
184
|
}
|
|
179
185
|
)
|
|
@@ -237,6 +243,7 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
237
243
|
* @param {boolean} mirrorY
|
|
238
244
|
* @param {any} material
|
|
239
245
|
* @param {{ x: number, y: number }[][]} drillCutouts
|
|
246
|
+
* @param {PcbScene3dSilkscreenCutoutContext} cutoutContext
|
|
240
247
|
* @returns {any[]}
|
|
241
248
|
*/
|
|
242
249
|
static #buildTrackMeshes(
|
|
@@ -246,7 +253,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
246
253
|
normalizeBoardPoint,
|
|
247
254
|
mirrorY,
|
|
248
255
|
material,
|
|
249
|
-
drillCutouts
|
|
256
|
+
drillCutouts,
|
|
257
|
+
cutoutContext
|
|
250
258
|
) {
|
|
251
259
|
const meshes = []
|
|
252
260
|
let positions = []
|
|
@@ -289,7 +297,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
289
297
|
THREE,
|
|
290
298
|
positions,
|
|
291
299
|
material,
|
|
292
|
-
drillCutouts
|
|
300
|
+
drillCutouts,
|
|
301
|
+
cutoutContext
|
|
293
302
|
)
|
|
294
303
|
positions = []
|
|
295
304
|
}
|
|
@@ -300,7 +309,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
300
309
|
THREE,
|
|
301
310
|
positions,
|
|
302
311
|
material,
|
|
303
|
-
drillCutouts
|
|
312
|
+
drillCutouts,
|
|
313
|
+
cutoutContext
|
|
304
314
|
)
|
|
305
315
|
|
|
306
316
|
return meshes
|
|
@@ -315,6 +325,7 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
315
325
|
* @param {boolean} mirrorY
|
|
316
326
|
* @param {any} material
|
|
317
327
|
* @param {{ x: number, y: number }[][]} drillCutouts
|
|
328
|
+
* @param {PcbScene3dSilkscreenCutoutContext} cutoutContext
|
|
318
329
|
* @returns {any | null}
|
|
319
330
|
*/
|
|
320
331
|
static #buildArcMesh(
|
|
@@ -324,7 +335,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
324
335
|
normalizeBoardPoint,
|
|
325
336
|
mirrorY,
|
|
326
337
|
material,
|
|
327
|
-
drillCutouts
|
|
338
|
+
drillCutouts,
|
|
339
|
+
cutoutContext
|
|
328
340
|
) {
|
|
329
341
|
const positions = []
|
|
330
342
|
|
|
@@ -353,7 +365,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
353
365
|
THREE,
|
|
354
366
|
positions,
|
|
355
367
|
material,
|
|
356
|
-
drillCutouts
|
|
368
|
+
drillCutouts,
|
|
369
|
+
cutoutContext
|
|
357
370
|
)
|
|
358
371
|
}
|
|
359
372
|
|
|
@@ -366,6 +379,7 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
366
379
|
* @param {boolean} mirrorY
|
|
367
380
|
* @param {any} material
|
|
368
381
|
* @param {{ x: number, y: number }[][]} drillCutouts
|
|
382
|
+
* @param {PcbScene3dSilkscreenCutoutContext} cutoutContext
|
|
369
383
|
* @returns {any[]}
|
|
370
384
|
*/
|
|
371
385
|
static #buildFillMeshes(
|
|
@@ -375,7 +389,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
375
389
|
normalizeBoardPoint,
|
|
376
390
|
mirrorY,
|
|
377
391
|
material,
|
|
378
|
-
drillCutouts
|
|
392
|
+
drillCutouts,
|
|
393
|
+
cutoutContext
|
|
379
394
|
) {
|
|
380
395
|
return fills.map((fill) => {
|
|
381
396
|
const points = PcbScene3dSilkscreenFactory.#normalizeFillPoints(
|
|
@@ -402,7 +417,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
402
417
|
fillHoles,
|
|
403
418
|
drillCutouts,
|
|
404
419
|
z,
|
|
405
|
-
material
|
|
420
|
+
material,
|
|
421
|
+
cutoutContext
|
|
406
422
|
)
|
|
407
423
|
}
|
|
408
424
|
|
|
@@ -425,6 +441,7 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
425
441
|
* @param {{ x: number, y: number }[][]} drillCutouts Normalized drill cutouts.
|
|
426
442
|
* @param {number} z
|
|
427
443
|
* @param {any} material
|
|
444
|
+
* @param {PcbScene3dSilkscreenCutoutContext} cutoutContext
|
|
428
445
|
* @returns {any}
|
|
429
446
|
*/
|
|
430
447
|
static #buildShapeFillMesh(
|
|
@@ -433,15 +450,17 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
433
450
|
fillHoles,
|
|
434
451
|
drillCutouts,
|
|
435
452
|
z,
|
|
436
|
-
material
|
|
453
|
+
material,
|
|
454
|
+
cutoutContext
|
|
437
455
|
) {
|
|
438
456
|
const { authoredHoles, drillHoles, uncoveredCutouts } =
|
|
439
457
|
PcbScene3dDrillCutoutFilter.partitionFillHoles(
|
|
440
458
|
drillCutouts,
|
|
441
|
-
fillHoles
|
|
459
|
+
fillHoles,
|
|
460
|
+
{ preparedPolygonCache: cutoutContext.preparedPolygonCache }
|
|
442
461
|
)
|
|
443
462
|
const { points: contourPoints, appliedCutouts: edgeCutouts } =
|
|
444
|
-
|
|
463
|
+
cutoutContext.applyCircularEdgeCutouts(
|
|
445
464
|
points,
|
|
446
465
|
drillHoles.concat(uncoveredCutouts)
|
|
447
466
|
)
|
|
@@ -461,33 +480,41 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
461
480
|
clippingHoles: copiedClippingHoles
|
|
462
481
|
} = PcbScene3dSilkscreenFactory.#partitionDrillCutouts(
|
|
463
482
|
remainingDrillHoles,
|
|
464
|
-
contourPoints
|
|
483
|
+
contourPoints,
|
|
484
|
+
cutoutContext
|
|
465
485
|
)
|
|
466
486
|
const { shapeHoles, clippingHoles } =
|
|
467
487
|
PcbScene3dSilkscreenFactory.#partitionDrillCutouts(
|
|
468
488
|
remainingUncoveredCutouts,
|
|
469
|
-
contourPoints
|
|
489
|
+
contourPoints,
|
|
490
|
+
cutoutContext
|
|
470
491
|
)
|
|
471
492
|
const physicalShapeCutoutHoles =
|
|
472
493
|
PcbScene3dShapeHoleMerger.mergeOverlapping(
|
|
473
494
|
PcbScene3dDrillCutoutFilter.removeNestedCutouts(
|
|
474
|
-
copiedShapeHoles.concat(shapeHoles)
|
|
495
|
+
copiedShapeHoles.concat(shapeHoles),
|
|
496
|
+
{
|
|
497
|
+
preparedPolygonCache: cutoutContext.preparedPolygonCache
|
|
498
|
+
}
|
|
475
499
|
)
|
|
476
500
|
)
|
|
477
501
|
const shapeCutoutHoles = PcbScene3dShapeHoleMerger.mergeOverlapping(
|
|
478
502
|
PcbScene3dDrillCutoutFilter.removeNestedCutouts(
|
|
479
|
-
authoredHoles.concat(copiedShapeHoles, shapeHoles)
|
|
503
|
+
authoredHoles.concat(copiedShapeHoles, shapeHoles),
|
|
504
|
+
{ preparedPolygonCache: cutoutContext.preparedPolygonCache }
|
|
480
505
|
)
|
|
481
506
|
)
|
|
482
507
|
const fallbackClippingHoles =
|
|
483
508
|
PcbScene3dDrillCutoutFilter.removeNestedCutouts(
|
|
484
|
-
copiedClippingHoles.concat(clippingHoles)
|
|
509
|
+
copiedClippingHoles.concat(clippingHoles),
|
|
510
|
+
{ preparedPolygonCache: cutoutContext.preparedPolygonCache }
|
|
485
511
|
)
|
|
486
512
|
PcbScene3dSilkscreenFactory.#appendShapeHoles(
|
|
487
513
|
THREE,
|
|
488
514
|
shape,
|
|
489
515
|
shapeCutoutHoles,
|
|
490
|
-
contourPoints
|
|
516
|
+
contourPoints,
|
|
517
|
+
cutoutContext
|
|
491
518
|
)
|
|
492
519
|
|
|
493
520
|
const shapeFilterHoles =
|
|
@@ -496,7 +523,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
496
523
|
PcbScene3dShapeHoleGeometryCleaner.removeCoveredHoleCenters(
|
|
497
524
|
THREE,
|
|
498
525
|
new THREE.ShapeGeometry(shape),
|
|
499
|
-
physicalShapeCutoutHoles
|
|
526
|
+
physicalShapeCutoutHoles,
|
|
527
|
+
{ preparedPolygonCache: cutoutContext.preparedPolygonCache }
|
|
500
528
|
)
|
|
501
529
|
const geometry = PcbScene3dCutoutGeometryFilter.filter(
|
|
502
530
|
THREE,
|
|
@@ -505,7 +533,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
505
533
|
{
|
|
506
534
|
maxDepth: 12,
|
|
507
535
|
maxEdgeLength:
|
|
508
|
-
PcbScene3dSilkscreenFactory.#CUTOUT_MAX_EDGE_LENGTH
|
|
536
|
+
PcbScene3dSilkscreenFactory.#CUTOUT_MAX_EDGE_LENGTH,
|
|
537
|
+
preparedPolygonCache: cutoutContext.preparedPolygonCache
|
|
509
538
|
}
|
|
510
539
|
)
|
|
511
540
|
const mesh = new THREE.Mesh(geometry, material)
|
|
@@ -513,94 +542,19 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
513
542
|
return mesh
|
|
514
543
|
}
|
|
515
544
|
|
|
516
|
-
/**
|
|
517
|
-
* Converts circular edge cutouts into the fill contour before triangulation.
|
|
518
|
-
* @param {{ x: number, y: number }[]} contourPoints Fill contour points.
|
|
519
|
-
* @param {{ x: number, y: number }[][]} drillCutouts Candidate cutouts.
|
|
520
|
-
* @returns {{ points: { x: number, y: number }[], appliedCutouts: { x: number, y: number }[][] }}
|
|
521
|
-
*/
|
|
522
|
-
static #applyCircularEdgeCutouts(contourPoints, drillCutouts) {
|
|
523
|
-
let points = contourPoints
|
|
524
|
-
const appliedCutouts = []
|
|
525
|
-
const candidates = PcbScene3dDrillCutoutFilter.removeNestedCutouts(
|
|
526
|
-
(Array.isArray(drillCutouts) ? drillCutouts : []).filter(
|
|
527
|
-
(cutout) =>
|
|
528
|
-
PcbScene3dCutoutCircleDetector.resolve(cutout) &&
|
|
529
|
-
!PcbScene3dSilkscreenFactory.#isHoleInsideContour(
|
|
530
|
-
cutout,
|
|
531
|
-
contourPoints
|
|
532
|
-
)
|
|
533
|
-
)
|
|
534
|
-
)
|
|
535
|
-
|
|
536
|
-
for (const cutout of candidates) {
|
|
537
|
-
const circularCutout =
|
|
538
|
-
PcbScene3dCutoutCircleDetector.resolve(cutout)
|
|
539
|
-
if (
|
|
540
|
-
!circularCutout ||
|
|
541
|
-
PcbScene3dSilkscreenFactory.#isHoleInsideContour(cutout, points)
|
|
542
|
-
) {
|
|
543
|
-
continue
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const nextPoints =
|
|
547
|
-
PcbScene3dBoardEdgeCutoutBuilder.applyCircularEdgeCutouts(
|
|
548
|
-
points,
|
|
549
|
-
[circularCutout]
|
|
550
|
-
)
|
|
551
|
-
if (
|
|
552
|
-
PcbScene3dSilkscreenFactory.#samePointList(points, nextPoints)
|
|
553
|
-
) {
|
|
554
|
-
continue
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
points = nextPoints
|
|
558
|
-
appliedCutouts.push(cutout)
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
return { points, appliedCutouts }
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* Returns true when two point lists share identical coordinates.
|
|
566
|
-
* @param {{ x: number, y: number }[]} first First point list.
|
|
567
|
-
* @param {{ x: number, y: number }[]} second Second point list.
|
|
568
|
-
* @returns {boolean}
|
|
569
|
-
*/
|
|
570
|
-
static #samePointList(first, second) {
|
|
571
|
-
return (
|
|
572
|
-
Array.isArray(first) &&
|
|
573
|
-
Array.isArray(second) &&
|
|
574
|
-
first.length === second.length &&
|
|
575
|
-
first.every((point, index) => {
|
|
576
|
-
const otherPoint = second[index]
|
|
577
|
-
|
|
578
|
-
return (
|
|
579
|
-
Math.abs(point.x - otherPoint.x) <=
|
|
580
|
-
PcbScene3dSilkscreenFactory.#GEOMETRY_EPSILON &&
|
|
581
|
-
Math.abs(point.y - otherPoint.y) <=
|
|
582
|
-
PcbScene3dSilkscreenFactory.#GEOMETRY_EPSILON
|
|
583
|
-
)
|
|
584
|
-
})
|
|
585
|
-
)
|
|
586
|
-
}
|
|
587
545
|
/**
|
|
588
546
|
* Splits drill cutouts into safe shape holes and fallback clip polygons.
|
|
589
547
|
* @param {{ x: number, y: number }[][]} drillCutouts
|
|
590
548
|
* @param {{ x: number, y: number }[]} contourPoints
|
|
549
|
+
* @param {PcbScene3dSilkscreenCutoutContext} cutoutContext
|
|
591
550
|
* @returns {{ shapeHoles: { x: number, y: number }[][], clippingHoles: { x: number, y: number }[][] }}
|
|
592
551
|
*/
|
|
593
|
-
static #partitionDrillCutouts(drillCutouts, contourPoints) {
|
|
552
|
+
static #partitionDrillCutouts(drillCutouts, contourPoints, cutoutContext) {
|
|
594
553
|
const shapeHoles = []
|
|
595
554
|
const clippingHoles = []
|
|
596
555
|
|
|
597
556
|
for (const cutout of Array.isArray(drillCutouts) ? drillCutouts : []) {
|
|
598
|
-
if (
|
|
599
|
-
PcbScene3dSilkscreenFactory.#isHoleInsideContour(
|
|
600
|
-
cutout,
|
|
601
|
-
contourPoints
|
|
602
|
-
)
|
|
603
|
-
) {
|
|
557
|
+
if (cutoutContext.isHoleInsideContour(cutout, contourPoints)) {
|
|
604
558
|
shapeHoles.push(cutout)
|
|
605
559
|
continue
|
|
606
560
|
}
|
|
@@ -617,20 +571,22 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
617
571
|
* @param {{ holes: any[] }} shape
|
|
618
572
|
* @param {{ x: number, y: number }[][]} holes
|
|
619
573
|
* @param {{ x: number, y: number }[]} contourPoints
|
|
574
|
+
* @param {PcbScene3dSilkscreenCutoutContext} cutoutContext
|
|
620
575
|
* @returns {void}
|
|
621
576
|
*/
|
|
622
|
-
static #appendShapeHoles(
|
|
577
|
+
static #appendShapeHoles(
|
|
578
|
+
THREE,
|
|
579
|
+
shape,
|
|
580
|
+
holes,
|
|
581
|
+
contourPoints,
|
|
582
|
+
cutoutContext
|
|
583
|
+
) {
|
|
623
584
|
if (!Array.isArray(holes) || !Array.isArray(shape.holes)) {
|
|
624
585
|
return
|
|
625
586
|
}
|
|
626
587
|
|
|
627
588
|
for (const points of holes) {
|
|
628
|
-
if (
|
|
629
|
-
!PcbScene3dSilkscreenFactory.#isHoleInsideContour(
|
|
630
|
-
points,
|
|
631
|
-
contourPoints
|
|
632
|
-
)
|
|
633
|
-
) {
|
|
589
|
+
if (!cutoutContext.isHoleInsideContour(points, contourPoints)) {
|
|
634
590
|
continue
|
|
635
591
|
}
|
|
636
592
|
|
|
@@ -640,19 +596,6 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
640
596
|
}
|
|
641
597
|
}
|
|
642
598
|
|
|
643
|
-
/**
|
|
644
|
-
* Returns true when a cutout can safely be added as a shape hole.
|
|
645
|
-
* @param {{ x: number, y: number }[]} hole
|
|
646
|
-
* @param {{ x: number, y: number }[]} contour
|
|
647
|
-
* @returns {boolean}
|
|
648
|
-
*/
|
|
649
|
-
static #isHoleInsideContour(hole, contour) {
|
|
650
|
-
return PcbScene3dBoardEdgeCutoutBuilder.isHoleInsideContour(
|
|
651
|
-
hole,
|
|
652
|
-
contour
|
|
653
|
-
)
|
|
654
|
-
}
|
|
655
|
-
|
|
656
599
|
/**
|
|
657
600
|
* Builds one rectangular fallback fill mesh.
|
|
658
601
|
* @param {any} THREE
|
|
@@ -851,9 +794,16 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
851
794
|
* @param {number[]} positions
|
|
852
795
|
* @param {any} material
|
|
853
796
|
* @param {{ x: number, y: number }[][]} [drillCutouts]
|
|
797
|
+
* @param {PcbScene3dSilkscreenCutoutContext | null} [cutoutContext]
|
|
854
798
|
* @returns {any | null}
|
|
855
799
|
*/
|
|
856
|
-
static #buildStrokeMesh(
|
|
800
|
+
static #buildStrokeMesh(
|
|
801
|
+
THREE,
|
|
802
|
+
positions,
|
|
803
|
+
material,
|
|
804
|
+
drillCutouts = [],
|
|
805
|
+
cutoutContext = null
|
|
806
|
+
) {
|
|
857
807
|
if (!positions.length) {
|
|
858
808
|
return null
|
|
859
809
|
}
|
|
@@ -872,7 +822,8 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
872
822
|
{
|
|
873
823
|
maxDepth: 12,
|
|
874
824
|
maxEdgeLength:
|
|
875
|
-
PcbScene3dSilkscreenFactory.#CUTOUT_MAX_EDGE_LENGTH
|
|
825
|
+
PcbScene3dSilkscreenFactory.#CUTOUT_MAX_EDGE_LENGTH,
|
|
826
|
+
preparedPolygonCache: cutoutContext?.preparedPolygonCache
|
|
876
827
|
}
|
|
877
828
|
),
|
|
878
829
|
material
|
|
@@ -886,14 +837,23 @@ export class PcbScene3dSilkscreenFactory {
|
|
|
886
837
|
* @param {number[]} positions
|
|
887
838
|
* @param {any} material
|
|
888
839
|
* @param {{ x: number, y: number }[][]} drillCutouts
|
|
840
|
+
* @param {PcbScene3dSilkscreenCutoutContext} cutoutContext
|
|
889
841
|
* @returns {void}
|
|
890
842
|
*/
|
|
891
|
-
static #appendStrokeMesh(
|
|
843
|
+
static #appendStrokeMesh(
|
|
844
|
+
meshes,
|
|
845
|
+
THREE,
|
|
846
|
+
positions,
|
|
847
|
+
material,
|
|
848
|
+
drillCutouts,
|
|
849
|
+
cutoutContext
|
|
850
|
+
) {
|
|
892
851
|
const mesh = PcbScene3dSilkscreenFactory.#buildStrokeMesh(
|
|
893
852
|
THREE,
|
|
894
853
|
positions,
|
|
895
854
|
material,
|
|
896
|
-
drillCutouts
|
|
855
|
+
drillCutouts,
|
|
856
|
+
cutoutContext
|
|
897
857
|
)
|
|
898
858
|
|
|
899
859
|
if (mesh) {
|
|
@@ -22,6 +22,7 @@ export class PcbScene3dSilkscreenFillSeamBuilder {
|
|
|
22
22
|
* @param {boolean} mirrorY Whether the side is mirrored.
|
|
23
23
|
* @param {any} material Shared fill material.
|
|
24
24
|
* @param {{ x: number, y: number }[][]} [cutouts] Cutouts that should remain uncovered.
|
|
25
|
+
* @param {{ preparedPolygonCache?: Map }} [options] Request-scoped options.
|
|
25
26
|
* @returns {any[]}
|
|
26
27
|
*/
|
|
27
28
|
static buildMeshes(
|
|
@@ -31,7 +32,8 @@ export class PcbScene3dSilkscreenFillSeamBuilder {
|
|
|
31
32
|
normalizeBoardPoint,
|
|
32
33
|
mirrorY,
|
|
33
34
|
material,
|
|
34
|
-
cutouts = []
|
|
35
|
+
cutouts = [],
|
|
36
|
+
options = {}
|
|
35
37
|
) {
|
|
36
38
|
const meshes = []
|
|
37
39
|
let positions = []
|
|
@@ -55,7 +57,8 @@ export class PcbScene3dSilkscreenFillSeamBuilder {
|
|
|
55
57
|
THREE,
|
|
56
58
|
positions,
|
|
57
59
|
material,
|
|
58
|
-
cutouts
|
|
60
|
+
cutouts,
|
|
61
|
+
options
|
|
59
62
|
)
|
|
60
63
|
positions = []
|
|
61
64
|
}
|
|
@@ -66,7 +69,8 @@ export class PcbScene3dSilkscreenFillSeamBuilder {
|
|
|
66
69
|
THREE,
|
|
67
70
|
positions,
|
|
68
71
|
material,
|
|
69
|
-
cutouts
|
|
72
|
+
cutouts,
|
|
73
|
+
options
|
|
70
74
|
)
|
|
71
75
|
|
|
72
76
|
return meshes
|
|
@@ -113,9 +117,10 @@ export class PcbScene3dSilkscreenFillSeamBuilder {
|
|
|
113
117
|
* @param {number[]} positions Position buffer.
|
|
114
118
|
* @param {any} material Shared material.
|
|
115
119
|
* @param {{ x: number, y: number }[][]} cutouts Cutout polygons.
|
|
120
|
+
* @param {{ preparedPolygonCache?: Map }} options Request-scoped options.
|
|
116
121
|
* @returns {void}
|
|
117
122
|
*/
|
|
118
|
-
static #appendMesh(meshes, THREE, positions, material, cutouts) {
|
|
123
|
+
static #appendMesh(meshes, THREE, positions, material, cutouts, options) {
|
|
119
124
|
if (
|
|
120
125
|
!positions.length ||
|
|
121
126
|
!THREE?.BufferGeometry ||
|
|
@@ -133,7 +138,8 @@ export class PcbScene3dSilkscreenFillSeamBuilder {
|
|
|
133
138
|
const mesh = new THREE.Mesh(
|
|
134
139
|
PcbScene3dCutoutGeometryFilter.filter(THREE, geometry, cutouts, {
|
|
135
140
|
maxDepth: 12,
|
|
136
|
-
maxEdgeLength: 2
|
|
141
|
+
maxEdgeLength: 2,
|
|
142
|
+
preparedPolygonCache: options?.preparedPolygonCache
|
|
137
143
|
}),
|
|
138
144
|
material
|
|
139
145
|
)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PcbScene3dModelIdentity } from './PcbScene3dModelIdentity.mjs'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Browser-side STEP mesh loader backed by occt-import-js.
|
|
3
5
|
*/
|
|
@@ -76,7 +78,7 @@ export class PcbScene3dStepLoader {
|
|
|
76
78
|
|
|
77
79
|
/**
|
|
78
80
|
* Loads one STEP model and normalizes it into triangle payloads.
|
|
79
|
-
* @param {{ origin?: string, name?: string, sourceStream?: string, relativePath?: string, payloadText?: string, payloadBytes?: any, bytes?: any, data?: any, file?: Blob | File | Uint8Array | ArrayBuffer | null }} model
|
|
81
|
+
* @param {{ origin?: string, name?: string, sourceStream?: string, relativePath?: string, payloadText?: string, text?: string, payloadBytes?: any, bytes?: any, data?: any, file?: Blob | File | Uint8Array | ArrayBuffer | null }} model
|
|
80
82
|
* @returns {Promise<{ meshPayloads: { name: string, color: number[] | null, positions: ArrayLike<number>, normals: ArrayLike<number>, indices: ArrayLike<number>, faceColors: { first: number, last: number, color: number[] | null }[] }[] }>}
|
|
81
83
|
*/
|
|
82
84
|
async loadModel(model) {
|
|
@@ -99,7 +101,7 @@ export class PcbScene3dStepLoader {
|
|
|
99
101
|
|
|
100
102
|
/**
|
|
101
103
|
* Loads one uncached STEP model.
|
|
102
|
-
* @param {{ origin?: string, name?: string, sourceStream?: string, relativePath?: string, payloadText?: string, payloadBytes?: any, bytes?: any, data?: any, file?: Blob | File | Uint8Array | ArrayBuffer | null }} model
|
|
104
|
+
* @param {{ origin?: string, name?: string, sourceStream?: string, relativePath?: string, payloadText?: string, text?: string, payloadBytes?: any, bytes?: any, data?: any, file?: Blob | File | Uint8Array | ArrayBuffer | null }} model
|
|
103
105
|
* @returns {Promise<{ meshPayloads: { name: string, color: number[] | null, positions: ArrayLike<number>, normals: ArrayLike<number>, indices: ArrayLike<number>, faceColors: { first: number, last: number, color: number[] | null }[] }[] }>}
|
|
104
106
|
*/
|
|
105
107
|
async #loadModelUncached(model) {
|
|
@@ -290,12 +292,7 @@ export class PcbScene3dStepLoader {
|
|
|
290
292
|
* @returns {string}
|
|
291
293
|
*/
|
|
292
294
|
static #resolveCacheKey(model) {
|
|
293
|
-
return
|
|
294
|
-
String(model?.origin || 'unknown'),
|
|
295
|
-
String(model?.sourceStream || ''),
|
|
296
|
-
String(model?.relativePath || ''),
|
|
297
|
-
String(model?.name || '')
|
|
298
|
-
].join('|')
|
|
295
|
+
return PcbScene3dModelIdentity.resolve(model)
|
|
299
296
|
}
|
|
300
297
|
|
|
301
298
|
/**
|
|
@@ -311,12 +308,16 @@ export class PcbScene3dStepLoader {
|
|
|
311
308
|
|
|
312
309
|
/**
|
|
313
310
|
* Reads one STEP model into bytes from embedded text, bytes, or a session file.
|
|
314
|
-
* @param {{ payloadText?: string, payloadBytes?: any, bytes?: any, data?: any, file?: Blob | File | Uint8Array | ArrayBuffer | null }} model
|
|
311
|
+
* @param {{ payloadText?: string, text?: string, payloadBytes?: any, bytes?: any, data?: any, file?: Blob | File | Uint8Array | ArrayBuffer | null }} model
|
|
315
312
|
* @returns {Promise<Uint8Array>}
|
|
316
313
|
*/
|
|
317
314
|
static async #readModelContent(model) {
|
|
318
315
|
const payloadText =
|
|
319
|
-
typeof model?.payloadText === 'string'
|
|
316
|
+
typeof model?.payloadText === 'string'
|
|
317
|
+
? model.payloadText
|
|
318
|
+
: typeof model?.text === 'string'
|
|
319
|
+
? model.text
|
|
320
|
+
: ''
|
|
320
321
|
if (payloadText) {
|
|
321
322
|
return new TextEncoder().encode(payloadText)
|
|
322
323
|
}
|
package/src/PcbScene3dText.mjs
CHANGED
|
@@ -34,7 +34,7 @@ const FALLBACK_MESSAGES = {
|
|
|
34
34
|
'scene3d.mountSide': 'Mount side',
|
|
35
35
|
'scene3d.noMetadataFor': 'No metadata is available for',
|
|
36
36
|
'scene3d.noModelsForExport':
|
|
37
|
-
'No
|
|
37
|
+
'No resolved 3D models were available for export.',
|
|
38
38
|
'scene3d.noPcb': '3D preview is available after loading a PCB document.',
|
|
39
39
|
'scene3d.pattern': 'Pattern',
|
|
40
40
|
'scene3d.picked': 'Picked',
|