pcb-scene3d-viewer 1.1.44 → 1.1.48
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/AGENTS.md +15 -0
- package/package.json +1 -1
- package/src/PcbAssemblyComponentMeshBuilder.mjs +6 -3
- package/src/PcbAssemblyGeometryBuilder.mjs +13 -7
- package/src/PcbAssemblyGltfImageDataUri.mjs +67 -0
- package/src/PcbAssemblyGltfSceneCamera.mjs +450 -0
- package/src/PcbAssemblyGltfWriter.mjs +34 -33
- 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/PcbScene3dCompanionBasePlacementAdjuster.mjs +8 -0
- package/src/PcbScene3dController.mjs +29 -5
- package/src/PcbScene3dCopperDetailGroupBuilder.mjs +47 -4
- package/src/PcbScene3dDrillCutoutFilter.mjs +136 -116
- package/src/PcbScene3dDrillVoidFactory.mjs +15 -71
- package/src/PcbScene3dExternalCompanionFallback.mjs +48 -3
- package/src/PcbScene3dExternalModelCenteringPolicy.mjs +5 -4
- package/src/PcbScene3dExternalModelDisplayOutlineTarget.mjs +297 -0
- package/src/PcbScene3dExternalModelExactOwnerAnchor.mjs +60 -0
- package/src/PcbScene3dExternalModelGroupLoader.mjs +288 -0
- package/src/PcbScene3dExternalModelOpacity.mjs +6 -0
- package/src/PcbScene3dExternalModelOwnerAnchoredConnectorContactRowRepair.mjs +663 -0
- package/src/PcbScene3dExternalModelPadAnchoredBodyCandidate.mjs +273 -0
- package/src/PcbScene3dExternalModelPadFallbackCenterRepair.mjs +208 -0
- package/src/PcbScene3dExternalModelPlacementRepair.mjs +16 -79
- package/src/PcbScene3dExternalModelRepeatedBoundsCenterRepair.mjs +866 -0
- package/src/PcbScene3dExternalModelRepeatedOwnerPackageCenterRepair.mjs +553 -0
- package/src/PcbScene3dExternalModels.mjs +35 -274
- package/src/PcbScene3dExternalPlacementDefaults.mjs +241 -0
- package/src/PcbScene3dGeometryZCompressor.mjs +5 -2
- package/src/PcbScene3dMaskCoveredCopperMaterial.mjs +1 -1
- package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +172 -5
- package/src/PcbScene3dModelSeatingPolicy.mjs +400 -23
- package/src/PcbScene3dOutlineBuilder.mjs +54 -0
- package/src/PcbScene3dRuntime.mjs +9 -7
- package/src/PcbScene3dRuntimeBoardMeshes.mjs +10 -3
- package/src/PcbScene3dShellRenderer.mjs +38 -4
- package/src/PcbScene3dStaticBodyFactory.mjs +8 -0
- package/src/PcbScene3dTransparentMountFaceCuller.mjs +255 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { PcbAssemblyExportCoordinateFrame } from './PcbAssemblyExportCoordinateFrame.mjs'
|
|
2
|
+
import { PcbAssemblyGltfImageDataUri } from './PcbAssemblyGltfImageDataUri.mjs'
|
|
3
|
+
import { PcbAssemblyGltfSceneCamera } from './PcbAssemblyGltfSceneCamera.mjs'
|
|
2
4
|
import { PcbAssemblyPolygonTriangulator } from './PcbAssemblyPolygonTriangulator.mjs'
|
|
3
5
|
|
|
4
6
|
const FLOAT_COMPONENT = 5126
|
|
@@ -17,7 +19,7 @@ const BIN_CHUNK_TYPE = 0x004e4942
|
|
|
17
19
|
export class PcbAssemblyGltfWriter {
|
|
18
20
|
/**
|
|
19
21
|
* Writes a GLTF or GLB assembly document.
|
|
20
|
-
* @param {{ name?: string, meshes?: object[], format?: string, binary?: boolean, includeSceneMetadata?: boolean }} assembly Assembly data.
|
|
22
|
+
* @param {{ name?: string, meshes?: object[], format?: string, binary?: boolean, includeSceneMetadata?: boolean, sceneCameraPreset?: string, sceneCameraAspectRatio?: number, sceneCameraFovDegrees?: number }} assembly Assembly data.
|
|
21
23
|
* @returns {object | Uint8Array}
|
|
22
24
|
*/
|
|
23
25
|
static write(assembly = {}) {
|
|
@@ -32,7 +34,7 @@ export class PcbAssemblyGltfWriter {
|
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Builds the GLTF JSON tree and shared binary buffer.
|
|
35
|
-
* @param {{ name?: string, meshes?: object[], includeSceneMetadata?: boolean }} assembly Assembly data.
|
|
37
|
+
* @param {{ name?: string, meshes?: object[], includeSceneMetadata?: boolean, sceneCameraPreset?: string, sceneCameraAspectRatio?: number, sceneCameraFovDegrees?: number }} assembly Assembly data.
|
|
36
38
|
* @param {boolean} binary Whether the document will be embedded in GLB.
|
|
37
39
|
* @returns {{ gltf: object, buffer: Uint8Array }}
|
|
38
40
|
*/
|
|
@@ -46,7 +48,22 @@ export class PcbAssemblyGltfWriter {
|
|
|
46
48
|
PcbAssemblyGltfWriter.#appendMesh(state, mesh)
|
|
47
49
|
})
|
|
48
50
|
if (assembly?.includeSceneMetadata === true) {
|
|
49
|
-
PcbAssemblyGltfWriter.#appendSceneMetadata(
|
|
51
|
+
PcbAssemblyGltfWriter.#appendSceneMetadata(
|
|
52
|
+
state,
|
|
53
|
+
sourceMeshes,
|
|
54
|
+
assembly
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
if (binary) {
|
|
58
|
+
state.images = PcbAssemblyGltfImageDataUri.pack(
|
|
59
|
+
state.images,
|
|
60
|
+
(bytes) =>
|
|
61
|
+
PcbAssemblyGltfWriter.#appendBufferView(
|
|
62
|
+
state,
|
|
63
|
+
bytes,
|
|
64
|
+
undefined
|
|
65
|
+
)
|
|
66
|
+
)
|
|
50
67
|
}
|
|
51
68
|
|
|
52
69
|
const buffer = PcbAssemblyGltfWriter.#concatBuffer(state.bufferParts)
|
|
@@ -136,41 +153,23 @@ export class PcbAssemblyGltfWriter {
|
|
|
136
153
|
* Appends default camera and light nodes from scene bounds.
|
|
137
154
|
* @param {object} state Writer state.
|
|
138
155
|
* @param {object[]} meshes Source meshes.
|
|
156
|
+
* @param {{ sceneCameraPreset?: string, sceneCameraAspectRatio?: number, sceneCameraFovDegrees?: number }} options Scene metadata options.
|
|
139
157
|
* @returns {void}
|
|
140
158
|
*/
|
|
141
|
-
static #appendSceneMetadata(state, meshes) {
|
|
159
|
+
static #appendSceneMetadata(state, meshes, options) {
|
|
142
160
|
const bounds = PcbAssemblyGltfWriter.#sceneBounds(meshes)
|
|
143
|
-
const
|
|
144
|
-
(bounds.min[0] + bounds.max[0]) / 2,
|
|
145
|
-
(bounds.min[1] + bounds.max[1]) / 2,
|
|
146
|
-
(bounds.min[2] + bounds.max[2]) / 2
|
|
147
|
-
]
|
|
148
|
-
const span = Math.max(
|
|
149
|
-
bounds.max[0] - bounds.min[0],
|
|
150
|
-
bounds.max[1] - bounds.min[1],
|
|
151
|
-
bounds.max[2] - bounds.min[2],
|
|
152
|
-
1
|
|
153
|
-
)
|
|
154
|
-
const distance = span * 2.2
|
|
161
|
+
const camera = PcbAssemblyGltfSceneCamera.resolve(bounds, options)
|
|
155
162
|
const cameraIndex = state.cameras.length
|
|
156
163
|
state.cameras.push({
|
|
157
164
|
name: 'Default Camera',
|
|
158
165
|
type: 'perspective',
|
|
159
|
-
perspective:
|
|
160
|
-
yfov: 0.7,
|
|
161
|
-
znear: Math.max(distance / 1000, 0.01),
|
|
162
|
-
zfar: distance * 10
|
|
163
|
-
}
|
|
166
|
+
perspective: camera.perspective
|
|
164
167
|
})
|
|
165
168
|
state.nodes.push({
|
|
166
169
|
name: 'Default Camera',
|
|
167
170
|
camera: cameraIndex,
|
|
168
|
-
translation:
|
|
169
|
-
|
|
170
|
-
center[1] + distance * 0.45,
|
|
171
|
-
center[2] + distance
|
|
172
|
-
],
|
|
173
|
-
rotation: [-0.260009, 0.279848, 0.076342, 0.920364]
|
|
171
|
+
translation: camera.position,
|
|
172
|
+
rotation: camera.rotation
|
|
174
173
|
})
|
|
175
174
|
|
|
176
175
|
const lightIndex = state.lights.length
|
|
@@ -182,9 +181,9 @@ export class PcbAssemblyGltfWriter {
|
|
|
182
181
|
state.nodes.push({
|
|
183
182
|
name: 'Key Light',
|
|
184
183
|
translation: [
|
|
185
|
-
center[0] - distance * 0.35,
|
|
186
|
-
center[1] + distance,
|
|
187
|
-
center[2] + distance
|
|
184
|
+
camera.center[0] - camera.distance * 0.35,
|
|
185
|
+
camera.center[1] + camera.distance,
|
|
186
|
+
camera.center[2] + camera.distance
|
|
188
187
|
],
|
|
189
188
|
extensions: {
|
|
190
189
|
KHR_lights_punctual: {
|
|
@@ -513,7 +512,7 @@ export class PcbAssemblyGltfWriter {
|
|
|
513
512
|
* Adds one aligned buffer view to the document.
|
|
514
513
|
* @param {object} state Writer state.
|
|
515
514
|
* @param {Uint8Array} bytes Binary data.
|
|
516
|
-
* @param {number} target Buffer target.
|
|
515
|
+
* @param {number | undefined} target Buffer target.
|
|
517
516
|
* @returns {number}
|
|
518
517
|
*/
|
|
519
518
|
static #appendBufferView(state, bytes, target) {
|
|
@@ -526,8 +525,10 @@ export class PcbAssemblyGltfWriter {
|
|
|
526
525
|
const bufferView = {
|
|
527
526
|
buffer: 0,
|
|
528
527
|
byteOffset: state.byteLength,
|
|
529
|
-
byteLength: bytes.byteLength
|
|
530
|
-
|
|
528
|
+
byteLength: bytes.byteLength
|
|
529
|
+
}
|
|
530
|
+
if (Number.isInteger(target)) {
|
|
531
|
+
bufferView.target = target
|
|
531
532
|
}
|
|
532
533
|
state.bufferViews.push(bufferView)
|
|
533
534
|
state.bufferParts.push(bytes)
|
|
@@ -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,
|
|
@@ -74,6 +74,14 @@ export class PcbScene3dCompanionBasePlacementAdjuster {
|
|
|
74
74
|
return null
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
if (
|
|
78
|
+
PcbScene3dCompanionBasePlacementAdjuster.#isBottomSide(
|
|
79
|
+
component?.mountSide
|
|
80
|
+
)
|
|
81
|
+
) {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
77
85
|
return {
|
|
78
86
|
designator: String(component?.designator || '').trim(),
|
|
79
87
|
mountSide: String(component?.mountSide || 'top').toLowerCase(),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PcbModelArchiveExporter } from './PcbModelArchiveExporter.mjs'
|
|
2
2
|
import { PcbScene3dAdjustmentControlBinder } from './PcbScene3dAdjustmentControlBinder.mjs'
|
|
3
3
|
import { PcbScene3dCircuitJsonAdapter } from './PcbScene3dCircuitJsonAdapter.mjs'
|
|
4
|
+
import { PcbScene3dExternalPlacementDefaults } from './PcbScene3dExternalPlacementDefaults.mjs'
|
|
4
5
|
import { PcbScene3dInteractionHints } from './PcbScene3dInteractionHints.mjs'
|
|
5
6
|
import { PcbScene3dRuntime } from './PcbScene3dRuntime.mjs'
|
|
6
7
|
import { PcbScene3dSelectionInspectorRenderer } from './PcbScene3dSelectionInspectorRenderer.mjs'
|
|
@@ -473,6 +474,7 @@ export class PcbScene3dController {
|
|
|
473
474
|
this.#handleRuntimeSelection(selection),
|
|
474
475
|
translate: this.#translate
|
|
475
476
|
})
|
|
477
|
+
this.#applyInitialToggles()
|
|
476
478
|
if (!this.#autoSearchMissingModels) {
|
|
477
479
|
this.#runtime?.setToggle?.('model-search-models', false)
|
|
478
480
|
}
|
|
@@ -561,6 +563,25 @@ export class PcbScene3dController {
|
|
|
561
563
|
})
|
|
562
564
|
}
|
|
563
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
|
+
|
|
564
585
|
/**
|
|
565
586
|
* Binds the model archive export action.
|
|
566
587
|
* @returns {void}
|
|
@@ -903,11 +924,14 @@ export class PcbScene3dController {
|
|
|
903
924
|
* @returns {any}
|
|
904
925
|
*/
|
|
905
926
|
static #normalizeSceneDescription(sceneDescription) {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
927
|
+
const normalizedScene =
|
|
928
|
+
PcbScene3dCircuitJsonAdapter.isDirectCircuitJsonModel(
|
|
929
|
+
sceneDescription
|
|
930
|
+
)
|
|
931
|
+
? PcbScene3dCircuitJsonAdapter.build(sceneDescription)
|
|
932
|
+
: sceneDescription
|
|
933
|
+
|
|
934
|
+
return PcbScene3dExternalPlacementDefaults.apply(normalizedScene)
|
|
911
935
|
}
|
|
912
936
|
|
|
913
937
|
/**
|
|
@@ -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,
|
|
@@ -187,6 +192,43 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
187
192
|
)
|
|
188
193
|
}
|
|
189
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Resolves explicit through-board cutouts that copper detail must not cover.
|
|
197
|
+
* @param {{ cutouts?: any[] } | undefined} board Board metadata.
|
|
198
|
+
* @returns {{ x: number, y: number }[][]}
|
|
199
|
+
*/
|
|
200
|
+
static #resolveBoardCutouts(board) {
|
|
201
|
+
return (Array.isArray(board?.cutouts) ? board.cutouts : [])
|
|
202
|
+
.map((cutout) =>
|
|
203
|
+
PcbScene3dCopperDetailGroupBuilder.#resolveBoardCutoutPoints(
|
|
204
|
+
cutout
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
.filter((points) => points.length >= 3)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Resolves one explicit board cutout loop in source board coordinates.
|
|
212
|
+
* @param {any} cutout Cutout source.
|
|
213
|
+
* @returns {{ x: number, y: number }[]}
|
|
214
|
+
*/
|
|
215
|
+
static #resolveBoardCutoutPoints(cutout) {
|
|
216
|
+
const points = Array.isArray(cutout?.points)
|
|
217
|
+
? cutout.points
|
|
218
|
+
: Array.isArray(cutout?.vertices)
|
|
219
|
+
? cutout.vertices
|
|
220
|
+
: Array.isArray(cutout)
|
|
221
|
+
? cutout
|
|
222
|
+
: []
|
|
223
|
+
|
|
224
|
+
return points
|
|
225
|
+
.map((point) => ({
|
|
226
|
+
x: Number(point?.x ?? point?.[0]),
|
|
227
|
+
y: Number(point?.y ?? point?.[1])
|
|
228
|
+
}))
|
|
229
|
+
.filter((point) => Number.isFinite(point.x + point.y))
|
|
230
|
+
}
|
|
231
|
+
|
|
190
232
|
/**
|
|
191
233
|
* Resolves solder-mask material options for covered copper relief.
|
|
192
234
|
* @param {object} sceneDescription Scene description.
|
|
@@ -199,7 +241,8 @@ export class PcbScene3dCopperDetailGroupBuilder {
|
|
|
199
241
|
{
|
|
200
242
|
hasBoardAssemblyModel: Boolean(
|
|
201
243
|
sceneDescription?.boardAssemblyModel
|
|
202
|
-
)
|
|
244
|
+
),
|
|
245
|
+
sourceFormat: sceneDescription?.sourceFormat
|
|
203
246
|
}
|
|
204
247
|
)
|
|
205
248
|
}
|