pcb-scene3d-viewer 1.1.2 → 1.1.10

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pcb-scene3d-viewer",
3
- "version": "1.1.2",
3
+ "version": "1.1.10",
4
4
  "description": "Reusable Three.js PCB 3D scene viewer for normalized ECAD and CircuitJSON scene descriptions",
5
5
  "keywords": [
6
6
  "pcb",
@@ -4,6 +4,24 @@
4
4
  export class PcbScene3dArcUtils {
5
5
  static #FULL_CIRCLE_EPSILON = 0.001
6
6
 
7
+ /**
8
+ * Resolves one arc record's sweep, preserving explicit long-arc sweeps.
9
+ * @param {{ startAngle?: number, endAngle?: number, sweepAngle?: number }} arc
10
+ * Arc record.
11
+ * @returns {number}
12
+ */
13
+ static resolveArcSweepDelta(arc) {
14
+ const sweepAngle = Number(arc?.sweepAngle)
15
+ if (Number.isFinite(sweepAngle)) {
16
+ return sweepAngle
17
+ }
18
+
19
+ return PcbScene3dArcUtils.resolveSweepDelta(
20
+ Number(arc?.startAngle || 0),
21
+ Number(arc?.endAngle || 0)
22
+ )
23
+ }
24
+
7
25
  /**
8
26
  * Normalizes one PCB arc delta to the intended short wrapped sweep.
9
27
  * @param {number} startAngle Start angle in degrees.
@@ -1,4 +1,5 @@
1
1
  import { PcbScene3dBoardMaterialPalette } from './PcbScene3dBoardMaterialPalette.mjs'
2
+ import { PcbScene3dMaterialFinish } from './PcbScene3dMaterialFinish.mjs'
2
3
 
3
4
  /**
4
5
  * Applies display-only material adjustments to full-board assembly models.
@@ -155,6 +156,7 @@ export class PcbScene3dBoardAssemblyPresentation {
155
156
  object?.material,
156
157
  surfaceColor
157
158
  )
159
+ PcbScene3dMaterialFinish.applySemiMatteSolderMask(object?.material)
158
160
  }
159
161
 
160
162
  /**
@@ -192,6 +194,7 @@ export class PcbScene3dBoardAssemblyPresentation {
192
194
  surfaceMaterial,
193
195
  surfaceColor
194
196
  )
197
+ PcbScene3dMaterialFinish.applySemiMatteSolderMask(surfaceMaterial)
195
198
  PcbScene3dBoardAssemblyPresentation.#applyMaterialColor(
196
199
  edgeMaterial,
197
200
  edgeColor
@@ -1,6 +1,7 @@
1
1
  import { PcbScene3dBoardMaterialPalette } from './PcbScene3dBoardMaterialPalette.mjs'
2
2
  import { PcbScene3dCutoutGeometryFilter } from './PcbScene3dCutoutGeometryFilter.mjs'
3
3
  import { PcbScene3dDrillPathFactory } from './PcbScene3dDrillPathFactory.mjs'
4
+ import { PcbScene3dMaterialFinish } from './PcbScene3dMaterialFinish.mjs'
4
5
  import { PcbScene3dOutlineBuilder } from './PcbScene3dOutlineBuilder.mjs'
5
6
 
6
7
  /**
@@ -602,8 +603,7 @@ export class PcbScene3dBoardSolderMaskFactory {
602
603
  color: PcbScene3dBoardMaterialPalette.resolveSurfaceColor(board, {
603
604
  hasBoardAssemblyModel: true
604
605
  }),
605
- roughness: 0.68,
606
- metalness: 0.08,
606
+ ...PcbScene3dMaterialFinish.semiMatteSolderMaskProperties(),
607
607
  polygonOffset: true,
608
608
  polygonOffsetFactor: -1,
609
609
  polygonOffsetUnits: -1,
@@ -48,7 +48,7 @@ export class PcbScene3dCameraRig {
48
48
  return {
49
49
  radius,
50
50
  target,
51
- up: { x: 0, y: -1, z: 0 },
51
+ up: PcbScene3dCameraRig.#bottomUpVector(sceneDescription),
52
52
  position: {
53
53
  x: target.x,
54
54
  y: target.y,
@@ -165,4 +165,19 @@ export class PcbScene3dCameraRig {
165
165
  z: Number(controls?.target?.z || 0)
166
166
  }
167
167
  }
168
+
169
+ /**
170
+ * Resolves screen-up for bottom views from the scene coordinate contract.
171
+ * @param {{ coordinateSystem?: string }} sceneDescription Scene metadata.
172
+ * @returns {{ x: number, y: number, z: number }}
173
+ */
174
+ static #bottomUpVector(sceneDescription) {
175
+ const coordinateSystem = String(
176
+ sceneDescription?.coordinateSystem || ''
177
+ ).toLowerCase()
178
+
179
+ return coordinateSystem === 'kicad-3d-y-up'
180
+ ? { x: 0, y: 1, z: 0 }
181
+ : { x: 0, y: -1, z: 0 }
182
+ }
168
183
  }
@@ -39,6 +39,41 @@ export class PcbScene3dCopperDetailFilter {
39
39
  }
40
40
  }
41
41
 
42
+ /**
43
+ * Resolves trace-like copper that should be visible through solder mask.
44
+ * @param {object} sceneDescription 3D scene description.
45
+ * @returns {{ tracks: any[], arcs: any[], copperTexts: any[], vias: any[] }}
46
+ */
47
+ static resolveCoveredByMask(sceneDescription) {
48
+ const detail = sceneDescription?.detail || {}
49
+
50
+ if (
51
+ !PcbScene3dCopperDetailFilter.#usesRealisticMasking(
52
+ sceneDescription
53
+ )
54
+ ) {
55
+ return { tracks: [], arcs: [], copperTexts: [], vias: [] }
56
+ }
57
+
58
+ const defaultCovered =
59
+ PcbScene3dCopperDetailFilter.#usesDefaultCoveredCopper(
60
+ sceneDescription
61
+ )
62
+
63
+ return {
64
+ tracks: PcbScene3dCopperDetailFilter.#filterMaskCoveredPrimitives(
65
+ detail.tracks,
66
+ defaultCovered
67
+ ),
68
+ arcs: PcbScene3dCopperDetailFilter.#filterMaskCoveredPrimitives(
69
+ detail.arcs,
70
+ defaultCovered
71
+ ),
72
+ copperTexts: [],
73
+ vias: []
74
+ }
75
+ }
76
+
42
77
  /**
43
78
  * Checks whether standalone via annuli should be rendered.
44
79
  * @param {object} sceneDescription 3D scene description.
@@ -101,6 +136,24 @@ export class PcbScene3dCopperDetailFilter {
101
136
  )
102
137
  }
103
138
 
139
+ /**
140
+ * Checks whether tracks are considered mask-covered without per-primitive
141
+ * mask metadata.
142
+ * @param {object} sceneDescription 3D scene description.
143
+ * @returns {boolean}
144
+ */
145
+ static #usesDefaultCoveredCopper(sceneDescription) {
146
+ const sourceFormat = String(sceneDescription?.sourceFormat || '')
147
+ .trim()
148
+ .toLowerCase()
149
+
150
+ return (
151
+ sourceFormat === 'altium' ||
152
+ sourceFormat === 'kicad' ||
153
+ sceneDescription?.coordinateSystem === 'kicad-3d-y-up'
154
+ )
155
+ }
156
+
104
157
  /**
105
158
  * Checks whether parsed copper detail carries explicit solder-mask data.
106
159
  * @param {object | undefined} detail Scene detail.
@@ -132,6 +185,25 @@ export class PcbScene3dCopperDetailFilter {
132
185
  )
133
186
  }
134
187
 
188
+ /**
189
+ * Keeps trace-like copper primitives that are covered by solder mask.
190
+ * @param {any[] | undefined} primitives Copper primitive list.
191
+ * @param {boolean} defaultCovered Whether missing metadata means covered.
192
+ * @returns {any[]}
193
+ */
194
+ static #filterMaskCoveredPrimitives(primitives, defaultCovered) {
195
+ return (primitives || []).filter((primitive) => {
196
+ if (PcbScene3dCopperDetailFilter.#hasMaskOpening(primitive)) {
197
+ return false
198
+ }
199
+
200
+ return (
201
+ defaultCovered ||
202
+ PcbScene3dCopperDetailFilter.#hasMaskMetadata(primitive)
203
+ )
204
+ })
205
+ }
206
+
135
207
  /**
136
208
  * Keeps vias only when they are explicitly not tented.
137
209
  * @param {any[] | undefined} vias Via list.
@@ -243,7 +315,7 @@ export class PcbScene3dCopperDetailFilter {
243
315
  * @returns {boolean}
244
316
  */
245
317
  static #hasMaskOpening(primitive, maskMatcher = null) {
246
- if (primitive?.hasSolderMask === true) {
318
+ if (primitive?.hasSolderMask === false) {
247
319
  return true
248
320
  }
249
321
 
@@ -0,0 +1,120 @@
1
+ import { PcbScene3dBoardMaterialPalette } from './PcbScene3dBoardMaterialPalette.mjs'
2
+ import { PcbScene3dCopperDetailFilter } from './PcbScene3dCopperDetailFilter.mjs'
3
+ import { PcbScene3dCopperFactory } from './PcbScene3dCopperFactory.mjs'
4
+ import { PcbScene3dViaFactory } from './PcbScene3dViaFactory.mjs'
5
+
6
+ /**
7
+ * Builds deferred copper-detail groups for the 3D runtime.
8
+ */
9
+ export class PcbScene3dCopperDetailGroupBuilder {
10
+ /**
11
+ * Builds visible exposed and mask-covered copper detail.
12
+ * @param {any} THREE Three.js namespace.
13
+ * @param {object} sceneDescription Scene description.
14
+ * @param {number} topZ Top copper center Z.
15
+ * @param {(x: number, y: number) => { x: number, y: number }} normalizePoint
16
+ * @returns {any}
17
+ */
18
+ static build(THREE, sceneDescription, topZ, normalizePoint) {
19
+ const group = new THREE.Group()
20
+ const coveredGroup =
21
+ PcbScene3dCopperDetailGroupBuilder.#buildCoveredGroup(
22
+ THREE,
23
+ sceneDescription,
24
+ topZ,
25
+ normalizePoint
26
+ )
27
+ const exposedGroup =
28
+ PcbScene3dCopperDetailGroupBuilder.#buildExposedGroup(
29
+ THREE,
30
+ sceneDescription,
31
+ topZ,
32
+ normalizePoint
33
+ )
34
+ const viaGroup = PcbScene3dCopperDetailGroupBuilder.#buildViaGroup(
35
+ THREE,
36
+ sceneDescription,
37
+ normalizePoint
38
+ )
39
+
40
+ ;[coveredGroup, exposedGroup, viaGroup]
41
+ .filter((child) => child.children.length)
42
+ .forEach((child) => group.add(child))
43
+
44
+ return group
45
+ }
46
+
47
+ /**
48
+ * Builds traces covered by solder mask.
49
+ * @param {any} THREE Three.js namespace.
50
+ * @param {object} sceneDescription Scene description.
51
+ * @param {number} topZ Top copper center Z.
52
+ * @param {(x: number, y: number) => { x: number, y: number }} normalizePoint
53
+ * @returns {any}
54
+ */
55
+ static #buildCoveredGroup(THREE, sceneDescription, topZ, normalizePoint) {
56
+ return PcbScene3dCopperFactory.buildMaskCoveredGroup(
57
+ THREE,
58
+ PcbScene3dCopperDetailFilter.resolveCoveredByMask(sceneDescription),
59
+ topZ,
60
+ -topZ,
61
+ normalizePoint,
62
+ {
63
+ solderMaskColor:
64
+ PcbScene3dBoardMaterialPalette.resolveSurfaceColor(
65
+ sceneDescription?.board,
66
+ {
67
+ hasBoardAssemblyModel: Boolean(
68
+ sceneDescription?.boardAssemblyModel
69
+ )
70
+ }
71
+ )
72
+ }
73
+ )
74
+ }
75
+
76
+ /**
77
+ * Builds exposed copper detail.
78
+ * @param {any} THREE Three.js namespace.
79
+ * @param {object} sceneDescription Scene description.
80
+ * @param {number} topZ Top copper center Z.
81
+ * @param {(x: number, y: number) => { x: number, y: number }} normalizePoint
82
+ * @returns {any}
83
+ */
84
+ static #buildExposedGroup(THREE, sceneDescription, topZ, normalizePoint) {
85
+ return PcbScene3dCopperFactory.buildGroup(
86
+ THREE,
87
+ PcbScene3dCopperDetailFilter.resolve(sceneDescription),
88
+ topZ,
89
+ -topZ,
90
+ normalizePoint,
91
+ { coordinateSystem: sceneDescription?.coordinateSystem }
92
+ )
93
+ }
94
+
95
+ /**
96
+ * Builds exposed via and through-hole barrel detail.
97
+ * @param {any} THREE Three.js namespace.
98
+ * @param {object} sceneDescription Scene description.
99
+ * @param {(x: number, y: number) => { x: number, y: number }} normalizePoint
100
+ * @returns {any}
101
+ */
102
+ static #buildViaGroup(THREE, sceneDescription, normalizePoint) {
103
+ if (
104
+ !PcbScene3dCopperDetailFilter.shouldRenderStandaloneVias(
105
+ sceneDescription
106
+ )
107
+ ) {
108
+ return new THREE.Group()
109
+ }
110
+
111
+ return PcbScene3dViaFactory.buildGroup(
112
+ THREE,
113
+ PcbScene3dCopperDetailFilter.resolveStandaloneVias(
114
+ sceneDescription
115
+ ),
116
+ sceneDescription?.board?.thicknessMil,
117
+ normalizePoint
118
+ )
119
+ }
120
+ }