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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pcb-scene3d-viewer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.49",
|
|
4
4
|
"description": "Reusable Three.js PCB 3D scene viewer for normalized ECAD and CircuitJSON scene descriptions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pcb",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"circuitjson-toolkit": "^1.0.10",
|
|
56
56
|
"earcut": "^3.0.2",
|
|
57
57
|
"fflate": "^0.8.2",
|
|
58
|
+
"polygon-clipping": "^0.15.7",
|
|
58
59
|
"three": "^0.183.2"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
@@ -19,9 +19,10 @@ export class PcbScene3dBoardAssemblyPresentation {
|
|
|
19
19
|
* PCB-derived substrate, copper, and silkscreen remain the visual source.
|
|
20
20
|
* @param {any} modelGroup Loaded board assembly group.
|
|
21
21
|
* @param {{ widthMil?: number, heightMil?: number, thicknessMil?: number } | null | undefined} board Board dimensions.
|
|
22
|
+
* @param {{ sourceFormat?: string }} [options] Presentation options.
|
|
22
23
|
* @returns {void}
|
|
23
24
|
*/
|
|
24
|
-
static apply(modelGroup, board) {
|
|
25
|
+
static apply(modelGroup, board, options = {}) {
|
|
25
26
|
const meshRecords =
|
|
26
27
|
PcbScene3dBoardAssemblyPresentation.#collectMeshRecords(modelGroup)
|
|
27
28
|
const boardBounds =
|
|
@@ -30,7 +31,10 @@ export class PcbScene3dBoardAssemblyPresentation {
|
|
|
30
31
|
board
|
|
31
32
|
)
|
|
32
33
|
const surfaceColor =
|
|
33
|
-
PcbScene3dBoardAssemblyPresentation.#resolveSurfaceColor(
|
|
34
|
+
PcbScene3dBoardAssemblyPresentation.#resolveSurfaceColor(
|
|
35
|
+
board,
|
|
36
|
+
options
|
|
37
|
+
)
|
|
34
38
|
const edgeColor =
|
|
35
39
|
PcbScene3dBoardAssemblyPresentation.#resolveEdgeColor(board)
|
|
36
40
|
const importedSurfaceColor =
|
|
@@ -412,11 +416,13 @@ export class PcbScene3dBoardAssemblyPresentation {
|
|
|
412
416
|
/**
|
|
413
417
|
* Resolves the app-level substrate color for board assembly meshes.
|
|
414
418
|
* @param {{ surfaceColor?: number } | null | undefined} board Board dimensions.
|
|
419
|
+
* @param {{ sourceFormat?: string }} options Presentation options.
|
|
415
420
|
* @returns {number}
|
|
416
421
|
*/
|
|
417
|
-
static #resolveSurfaceColor(board) {
|
|
422
|
+
static #resolveSurfaceColor(board, options) {
|
|
418
423
|
return PcbScene3dBoardMaterialPalette.resolveSurfaceColor(board, {
|
|
419
|
-
hasBoardAssemblyModel: true
|
|
424
|
+
hasBoardAssemblyModel: true,
|
|
425
|
+
sourceFormat: options?.sourceFormat
|
|
420
426
|
})
|
|
421
427
|
}
|
|
422
428
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds normalized Three.js paths for explicit board cutouts.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dBoardCutoutPathFactory {
|
|
5
|
+
/**
|
|
6
|
+
* Resolves explicit board cutout paths.
|
|
7
|
+
* @param {any} THREE Three.js namespace.
|
|
8
|
+
* @param {{ cutouts?: any[] }} board Board metadata.
|
|
9
|
+
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
10
|
+
* @returns {{ path: any, points: { x: number, y: number }[] }[]}
|
|
11
|
+
*/
|
|
12
|
+
static resolve(THREE, board, normalizeBoardPoint) {
|
|
13
|
+
return (Array.isArray(board?.cutouts) ? board.cutouts : [])
|
|
14
|
+
.map((cutout) =>
|
|
15
|
+
PcbScene3dBoardCutoutPathFactory.#buildPath(
|
|
16
|
+
THREE,
|
|
17
|
+
cutout,
|
|
18
|
+
normalizeBoardPoint
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Builds one explicit board cutout path.
|
|
26
|
+
* @param {any} THREE Three.js namespace.
|
|
27
|
+
* @param {any} cutout Source cutout.
|
|
28
|
+
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
29
|
+
* @returns {{ path: any, points: { x: number, y: number }[] } | null}
|
|
30
|
+
*/
|
|
31
|
+
static #buildPath(THREE, cutout, normalizeBoardPoint) {
|
|
32
|
+
const points = PcbScene3dBoardCutoutPathFactory.#sourcePoints(cutout)
|
|
33
|
+
.map((point) =>
|
|
34
|
+
PcbScene3dBoardCutoutPathFactory.#normalizePoint(
|
|
35
|
+
point,
|
|
36
|
+
normalizeBoardPoint
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
|
|
41
|
+
if (points.length < 3) {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const path = new THREE.Path()
|
|
46
|
+
path.moveTo(points[0].x, points[0].y)
|
|
47
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
48
|
+
path.lineTo(points[index].x, points[index].y)
|
|
49
|
+
}
|
|
50
|
+
path.closePath()
|
|
51
|
+
|
|
52
|
+
return { path, points }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolves one cutout's point list.
|
|
57
|
+
* @param {any} cutout Source cutout.
|
|
58
|
+
* @returns {any[]}
|
|
59
|
+
*/
|
|
60
|
+
static #sourcePoints(cutout) {
|
|
61
|
+
if (Array.isArray(cutout?.points)) {
|
|
62
|
+
return cutout.points
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (Array.isArray(cutout?.vertices)) {
|
|
66
|
+
return cutout.vertices
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return Array.isArray(cutout) ? cutout : []
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Normalizes one source point into local board shape coordinates.
|
|
74
|
+
* @param {any} point Source point.
|
|
75
|
+
* @param {(x: number, y: number) => { x: number, y: number }} normalizeBoardPoint
|
|
76
|
+
* @returns {{ x: number, y: number } | null}
|
|
77
|
+
*/
|
|
78
|
+
static #normalizePoint(point, normalizeBoardPoint) {
|
|
79
|
+
const x = Number(point?.x ?? point?.[0])
|
|
80
|
+
const y = Number(point?.y ?? point?.[1])
|
|
81
|
+
return Number.isFinite(x + y) ? normalizeBoardPoint(x, y) : null
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -7,11 +7,16 @@ export class PcbScene3dBoardMaterialPalette {
|
|
|
7
7
|
/**
|
|
8
8
|
* Resolves the solder-mask face color for the generated board shell.
|
|
9
9
|
* @param {{ surfaceColor?: number } | undefined} board Board metadata.
|
|
10
|
-
* @param {{ hasBoardAssemblyModel?: boolean }} [options] Scene options.
|
|
10
|
+
* @param {{ hasBoardAssemblyModel?: boolean, sourceFormat?: string }} [options] Scene options.
|
|
11
11
|
* @returns {number}
|
|
12
12
|
*/
|
|
13
13
|
static resolveSurfaceColor(board, options = {}) {
|
|
14
|
-
if (
|
|
14
|
+
if (
|
|
15
|
+
options.hasBoardAssemblyModel &&
|
|
16
|
+
!PcbScene3dBoardMaterialPalette.#shouldPreserveAuthoredSurfaceColor(
|
|
17
|
+
options
|
|
18
|
+
)
|
|
19
|
+
) {
|
|
15
20
|
return PcbScene3dBoardMaterialPalette.#DEFAULT_SURFACE_COLOR
|
|
16
21
|
}
|
|
17
22
|
|
|
@@ -20,6 +25,16 @@ export class PcbScene3dBoardMaterialPalette {
|
|
|
20
25
|
: PcbScene3dBoardMaterialPalette.#DEFAULT_SURFACE_COLOR
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Returns true when the source format supplies display-stable board colors.
|
|
30
|
+
* @param {{ sourceFormat?: string }} options Scene options.
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
static #shouldPreserveAuthoredSurfaceColor(options) {
|
|
34
|
+
const sourceFormat = String(options?.sourceFormat || '').toLowerCase()
|
|
35
|
+
return sourceFormat === 'altium' || sourceFormat.startsWith('altium-')
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
/**
|
|
24
39
|
* Resolves whether the generated board face should render.
|
|
25
40
|
* @param {{ hasBoardAssemblyModel?: boolean }} [options] Scene options.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PcbScene3dBoardEdgeCutoutBuilder } from './PcbScene3dBoardEdgeCutoutBuilder.mjs'
|
|
2
|
+
import { PcbScene3dBoardCutoutPathFactory } from './PcbScene3dBoardCutoutPathFactory.mjs'
|
|
2
3
|
import { PcbScene3dDrillPathFactory } from './PcbScene3dDrillPathFactory.mjs'
|
|
3
4
|
import { PcbScene3dOutlineBuilder } from './PcbScene3dOutlineBuilder.mjs'
|
|
4
5
|
|
|
@@ -36,6 +37,11 @@ export class PcbScene3dBoardShapeFactory {
|
|
|
36
37
|
)
|
|
37
38
|
const contourPoints =
|
|
38
39
|
PcbScene3dBoardEdgeCutoutBuilder.resolveShapePoints(baseShape)
|
|
40
|
+
const boardCutouts = PcbScene3dBoardCutoutPathFactory.resolve(
|
|
41
|
+
THREE,
|
|
42
|
+
board,
|
|
43
|
+
normalizeBoardPoint
|
|
44
|
+
)
|
|
39
45
|
const drillCutouts = PcbScene3dBoardShapeFactory.#resolveDrillCutouts(
|
|
40
46
|
THREE,
|
|
41
47
|
detail,
|
|
@@ -61,12 +67,20 @@ export class PcbScene3dBoardShapeFactory {
|
|
|
61
67
|
const finalContourPoints = edgeCutouts.length
|
|
62
68
|
? PcbScene3dBoardEdgeCutoutBuilder.resolveShapePoints(shape)
|
|
63
69
|
: contourPoints
|
|
64
|
-
|
|
70
|
+
for (const cutout of boardCutouts) {
|
|
71
|
+
if (
|
|
72
|
+
PcbScene3dBoardEdgeCutoutBuilder.isHoleInsideContour(
|
|
73
|
+
cutout.points,
|
|
74
|
+
finalContourPoints
|
|
75
|
+
)
|
|
76
|
+
) {
|
|
77
|
+
shape.holes.push(cutout.path)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
65
80
|
for (const cutout of drillCutouts) {
|
|
66
81
|
if (edgeCutouts.includes(cutout)) {
|
|
67
82
|
continue
|
|
68
83
|
}
|
|
69
|
-
|
|
70
84
|
if (
|
|
71
85
|
!cutout.isCircular ||
|
|
72
86
|
PcbScene3dBoardEdgeCutoutBuilder.isHoleInsideContour(
|
|
@@ -77,7 +91,6 @@ export class PcbScene3dBoardShapeFactory {
|
|
|
77
91
|
shape.holes.push(cutout.path)
|
|
78
92
|
}
|
|
79
93
|
}
|
|
80
|
-
|
|
81
94
|
return shape
|
|
82
95
|
}
|
|
83
96
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PcbScene3dBoardMaterialPalette } from './PcbScene3dBoardMaterialPalette.mjs'
|
|
2
|
+
import { PcbScene3dBoardCutoutPathFactory } from './PcbScene3dBoardCutoutPathFactory.mjs'
|
|
2
3
|
import { PcbScene3dCutoutGeometryFilter } from './PcbScene3dCutoutGeometryFilter.mjs'
|
|
3
4
|
import { PcbScene3dDrillPathFactory } from './PcbScene3dDrillPathFactory.mjs'
|
|
4
5
|
import { PcbScene3dMaterialFinish } from './PcbScene3dMaterialFinish.mjs'
|
|
@@ -51,12 +52,14 @@ export class PcbScene3dBoardSolderMaskFactory {
|
|
|
51
52
|
const topMaterial = PcbScene3dBoardSolderMaskFactory.#buildMaterial(
|
|
52
53
|
THREE,
|
|
53
54
|
board,
|
|
54
|
-
THREE.FrontSide
|
|
55
|
+
THREE.FrontSide,
|
|
56
|
+
sceneDescription?.sourceFormat
|
|
55
57
|
)
|
|
56
58
|
const bottomMaterial = PcbScene3dBoardSolderMaskFactory.#buildMaterial(
|
|
57
59
|
THREE,
|
|
58
60
|
board,
|
|
59
|
-
THREE.BackSide
|
|
61
|
+
THREE.BackSide,
|
|
62
|
+
sceneDescription?.sourceFormat
|
|
60
63
|
)
|
|
61
64
|
const z = Number(board?.thicknessMil || 0) / 2
|
|
62
65
|
|
|
@@ -148,6 +151,11 @@ export class PcbScene3dBoardSolderMaskFactory {
|
|
|
148
151
|
)
|
|
149
152
|
const contourPoints =
|
|
150
153
|
PcbScene3dBoardSolderMaskFactory.#resolveShapePoints(shape)
|
|
154
|
+
const boardCutouts = PcbScene3dBoardCutoutPathFactory.resolve(
|
|
155
|
+
THREE,
|
|
156
|
+
board,
|
|
157
|
+
normalizeBoardPoint
|
|
158
|
+
)
|
|
151
159
|
const drillCutouts =
|
|
152
160
|
PcbScene3dBoardSolderMaskFactory.#resolveDrillCutouts(
|
|
153
161
|
THREE,
|
|
@@ -155,8 +163,8 @@ export class PcbScene3dBoardSolderMaskFactory {
|
|
|
155
163
|
normalizeBoardPoint
|
|
156
164
|
)
|
|
157
165
|
const { shapeHoles, clippingCutouts } =
|
|
158
|
-
PcbScene3dBoardSolderMaskFactory.#
|
|
159
|
-
drillCutouts,
|
|
166
|
+
PcbScene3dBoardSolderMaskFactory.#partitionCutouts(
|
|
167
|
+
[...boardCutouts, ...drillCutouts],
|
|
160
168
|
contourPoints
|
|
161
169
|
)
|
|
162
170
|
|
|
@@ -395,16 +403,16 @@ export class PcbScene3dBoardSolderMaskFactory {
|
|
|
395
403
|
}
|
|
396
404
|
|
|
397
405
|
/**
|
|
398
|
-
* Splits
|
|
399
|
-
* @param {{ path: any, points: { x: number, y: number }[] }[]}
|
|
406
|
+
* Splits cutouts into safe shape holes and fallback clip polygons.
|
|
407
|
+
* @param {{ path: any, points: { x: number, y: number }[] }[]} cutouts
|
|
400
408
|
* @param {{ x: number, y: number }[]} contourPoints
|
|
401
409
|
* @returns {{ shapeHoles: { path: any, points: { x: number, y: number }[] }[], clippingCutouts: { path: any, points: { x: number, y: number }[] }[] }}
|
|
402
410
|
*/
|
|
403
|
-
static #
|
|
411
|
+
static #partitionCutouts(cutouts, contourPoints) {
|
|
404
412
|
const shapeHoles = []
|
|
405
413
|
const clippingCutouts = []
|
|
406
414
|
|
|
407
|
-
for (const cutout of Array.isArray(
|
|
415
|
+
for (const cutout of Array.isArray(cutouts) ? cutouts : []) {
|
|
408
416
|
if (
|
|
409
417
|
PcbScene3dBoardSolderMaskFactory.#isHoleInsideContour(
|
|
410
418
|
cutout.points,
|
|
@@ -596,12 +604,14 @@ export class PcbScene3dBoardSolderMaskFactory {
|
|
|
596
604
|
* @param {any} THREE
|
|
597
605
|
* @param {{ surfaceColor?: number }} board Board metadata.
|
|
598
606
|
* @param {number} side Rendered material side.
|
|
607
|
+
* @param {string | undefined} sourceFormat Scene source format.
|
|
599
608
|
* @returns {any}
|
|
600
609
|
*/
|
|
601
|
-
static #buildMaterial(THREE, board, side) {
|
|
610
|
+
static #buildMaterial(THREE, board, side, sourceFormat) {
|
|
602
611
|
return new THREE.MeshStandardMaterial({
|
|
603
612
|
color: PcbScene3dBoardMaterialPalette.resolveSurfaceColor(board, {
|
|
604
|
-
hasBoardAssemblyModel: true
|
|
613
|
+
hasBoardAssemblyModel: true,
|
|
614
|
+
sourceFormat
|
|
605
615
|
}),
|
|
606
616
|
...PcbScene3dMaterialFinish.semiMatteSolderMaskProperties(),
|
|
607
617
|
polygonOffset: true,
|
|
@@ -474,6 +474,7 @@ export class PcbScene3dController {
|
|
|
474
474
|
this.#handleRuntimeSelection(selection),
|
|
475
475
|
translate: this.#translate
|
|
476
476
|
})
|
|
477
|
+
this.#applyInitialToggles()
|
|
477
478
|
if (!this.#autoSearchMissingModels) {
|
|
478
479
|
this.#runtime?.setToggle?.('model-search-models', false)
|
|
479
480
|
}
|
|
@@ -562,6 +563,25 @@ export class PcbScene3dController {
|
|
|
562
563
|
})
|
|
563
564
|
}
|
|
564
565
|
|
|
566
|
+
/**
|
|
567
|
+
* Synchronizes runtime visibility with the shell's initial checkbox state.
|
|
568
|
+
* @returns {void}
|
|
569
|
+
*/
|
|
570
|
+
#applyInitialToggles() {
|
|
571
|
+
const toggles =
|
|
572
|
+
this.#rootNode?.querySelectorAll('[data-scene-3d-toggle]') || []
|
|
573
|
+
|
|
574
|
+
toggles.forEach((toggle) => {
|
|
575
|
+
const toggleName =
|
|
576
|
+
toggle?.getAttribute?.('data-scene-3d-toggle') || ''
|
|
577
|
+
if (!toggleName) {
|
|
578
|
+
return
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
this.#runtime?.setToggle?.(toggleName, Boolean(toggle.checked))
|
|
582
|
+
})
|
|
583
|
+
}
|
|
584
|
+
|
|
565
585
|
/**
|
|
566
586
|
* Binds the model archive export action.
|
|
567
587
|
* @returns {void}
|
|
@@ -25,9 +25,14 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
25
25
|
*/
|
|
26
26
|
static build(THREE, sceneDescription, topZ, normalizePoint) {
|
|
27
27
|
const group = new THREE.Group()
|
|
28
|
-
const drillCutouts =
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
const drillCutouts = [
|
|
29
|
+
...PcbScene3dCopperDrillCutoutBuilder.resolve(
|
|
30
|
+
sceneDescription?.detail
|
|
31
|
+
),
|
|
32
|
+
...PcbScene3dCopperDetailGroupBuilder.#resolveBoardCutouts(
|
|
33
|
+
sceneDescription?.board
|
|
34
|
+
)
|
|
35
|
+
]
|
|
31
36
|
const coveredGroup =
|
|
32
37
|
PcbScene3dCopperDetailGroupBuilder.#buildCoveredGroup(
|
|
33
38
|
THREE,
|
|
@@ -91,6 +96,10 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
91
96
|
),
|
|
92
97
|
drillCutouts,
|
|
93
98
|
drillDetail: sceneDescription?.detail,
|
|
99
|
+
unionCoveredLayerPrimitives:
|
|
100
|
+
PcbScene3dCopperDetailGroupBuilder.#usesGerberLayerUnion(
|
|
101
|
+
sceneDescription
|
|
102
|
+
),
|
|
94
103
|
occlusionCutouts:
|
|
95
104
|
PcbScene3dCopperDetailGroupBuilder.#resolveCoveredCopperOcclusions(
|
|
96
105
|
sceneDescription?.detail
|
|
@@ -131,6 +140,18 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
131
140
|
)
|
|
132
141
|
}
|
|
133
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Checks whether the source format represents copper as Gerber polarity union.
|
|
145
|
+
* @param {object | undefined} sceneDescription Scene description.
|
|
146
|
+
* @returns {boolean}
|
|
147
|
+
*/
|
|
148
|
+
static #usesGerberLayerUnion(sceneDescription) {
|
|
149
|
+
return (
|
|
150
|
+
String(sceneDescription?.sourceFormat || '').toLowerCase() ===
|
|
151
|
+
'gerber'
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
134
155
|
/**
|
|
135
156
|
* Builds exposed copper detail.
|
|
136
157
|
* @param {any} THREE Three.js namespace.
|
|
@@ -187,6 +208,43 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
187
208
|
)
|
|
188
209
|
}
|
|
189
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Resolves explicit through-board cutouts that copper detail must not cover.
|
|
213
|
+
* @param {{ cutouts?: any[] } | undefined} board Board metadata.
|
|
214
|
+
* @returns {{ x: number, y: number }[][]}
|
|
215
|
+
*/
|
|
216
|
+
static #resolveBoardCutouts(board) {
|
|
217
|
+
return (Array.isArray(board?.cutouts) ? board.cutouts : [])
|
|
218
|
+
.map((cutout) =>
|
|
219
|
+
PcbScene3dCopperDetailGroupBuilder.#resolveBoardCutoutPoints(
|
|
220
|
+
cutout
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
.filter((points) => points.length >= 3)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Resolves one explicit board cutout loop in source board coordinates.
|
|
228
|
+
* @param {any} cutout Cutout source.
|
|
229
|
+
* @returns {{ x: number, y: number }[]}
|
|
230
|
+
*/
|
|
231
|
+
static #resolveBoardCutoutPoints(cutout) {
|
|
232
|
+
const points = Array.isArray(cutout?.points)
|
|
233
|
+
? cutout.points
|
|
234
|
+
: Array.isArray(cutout?.vertices)
|
|
235
|
+
? cutout.vertices
|
|
236
|
+
: Array.isArray(cutout)
|
|
237
|
+
? cutout
|
|
238
|
+
: []
|
|
239
|
+
|
|
240
|
+
return points
|
|
241
|
+
.map((point) => ({
|
|
242
|
+
x: Number(point?.x ?? point?.[0]),
|
|
243
|
+
y: Number(point?.y ?? point?.[1])
|
|
244
|
+
}))
|
|
245
|
+
.filter((point) => Number.isFinite(point.x + point.y))
|
|
246
|
+
}
|
|
247
|
+
|
|
190
248
|
/**
|
|
191
249
|
* Resolves solder-mask material options for covered copper relief.
|
|
192
250
|
* @param {object} sceneDescription Scene description.
|
|
@@ -199,7 +257,8 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
199
257
|
{
|
|
200
258
|
hasBoardAssemblyModel: Boolean(
|
|
201
259
|
sceneDescription?.boardAssemblyModel
|
|
202
|
-
)
|
|
260
|
+
),
|
|
261
|
+
sourceFormat: sceneDescription?.sourceFormat
|
|
203
262
|
}
|
|
204
263
|
)
|
|
205
264
|
}
|