pcb-scene3d-viewer 1.1.21 → 1.1.31

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.
Files changed (55) hide show
  1. package/README.md +3 -2
  2. package/docs/api.md +27 -1
  3. package/docs/circuitjson.md +63 -18
  4. package/docs/model-format.md +13 -2
  5. package/package.json +1 -1
  6. package/spec/library-scope.md +2 -2
  7. package/src/PcbAssemblyBoardSubstrateBuilder.mjs +25 -5
  8. package/src/PcbAssemblyComponentMeshBuilder.mjs +762 -0
  9. package/src/PcbAssemblyFillGeometryResolver.mjs +579 -0
  10. package/src/PcbAssemblyFillRingNormalizer.mjs +209 -0
  11. package/src/PcbAssemblyGeometryBuilder.mjs +41 -301
  12. package/src/PcbAssemblyGltfModelMeshParser.mjs +912 -0
  13. package/src/PcbAssemblyGltfValidator.mjs +460 -0
  14. package/src/PcbAssemblyGltfWriter.mjs +801 -0
  15. package/src/PcbAssemblyMeshUtils.mjs +117 -0
  16. package/src/PcbAssemblyModelMeshLoader.mjs +18 -0
  17. package/src/PcbAssemblyPadMeshBuilder.mjs +394 -0
  18. package/src/PcbAssemblyStepWriter.mjs +24 -2
  19. package/src/PcbAssemblyTextModelMeshParser.mjs +602 -0
  20. package/src/PcbModelArchiveExporter.mjs +521 -7
  21. package/src/PcbScene3dCircuitJsonAdapter.mjs +409 -7
  22. package/src/PcbScene3dCircuitJsonModelTransform.mjs +232 -0
  23. package/src/PcbScene3dCompanionBasePlacementAdjuster.mjs +242 -0
  24. package/src/PcbScene3dComponentVisibility.mjs +100 -5
  25. package/src/PcbScene3dController.mjs +25 -55
  26. package/src/PcbScene3dCopperDetailFilter.mjs +86 -9
  27. package/src/PcbScene3dCopperDetailGroupBuilder.mjs +186 -15
  28. package/src/PcbScene3dCopperDrillCutoutBuilder.mjs +258 -0
  29. package/src/PcbScene3dCopperFactory.mjs +99 -85
  30. package/src/PcbScene3dCopperFillMeshBuilder.mjs +393 -0
  31. package/src/PcbScene3dCopperLayerFilter.mjs +32 -3
  32. package/src/PcbScene3dCutoutGeometryFilter.mjs +17 -14
  33. package/src/PcbScene3dExternalCompanionFallback.mjs +202 -0
  34. package/src/PcbScene3dExternalModelCenteringPolicy.mjs +21 -0
  35. package/src/PcbScene3dExternalModelOpacity.mjs +51 -0
  36. package/src/PcbScene3dExternalModelPlacementRepair.mjs +5 -2
  37. package/src/PcbScene3dExternalModelSourceOriginPolicy.mjs +41 -0
  38. package/src/PcbScene3dExternalModels.mjs +16 -7
  39. package/src/PcbScene3dFallbackBodyFactory.mjs +105 -0
  40. package/src/PcbScene3dFallbackVisibility.mjs +58 -1
  41. package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +68 -0
  42. package/src/PcbScene3dPadFactory.mjs +35 -2
  43. package/src/PcbScene3dRenderGroupVisibility.mjs +8 -1
  44. package/src/PcbScene3dRuntime.mjs +94 -100
  45. package/src/PcbScene3dRuntimeHelpers.mjs +39 -0
  46. package/src/PcbScene3dSelectionIndexBuilder.mjs +87 -0
  47. package/src/PcbScene3dSelectionInspectorRenderer.mjs +50 -11
  48. package/src/PcbScene3dSelectionMarkerFactory.mjs +333 -0
  49. package/src/PcbScene3dSelectionMarkerOverlay.mjs +70 -0
  50. package/src/PcbScene3dSelectionStyler.mjs +52 -2
  51. package/src/PcbScene3dStaticBodyFactory.mjs +263 -0
  52. package/src/PcbScene3dText.mjs +1 -0
  53. package/src/PcbScene3dTransparentMeshSplitter.mjs +623 -0
  54. package/src/PcbScene3dViaFactory.mjs +27 -7
  55. package/src/scene3d.mjs +3 -0
@@ -0,0 +1,263 @@
1
+ import { PcbScene3dComponentAdjustment } from './PcbScene3dComponentAdjustment.mjs'
2
+ import { PcbScene3dMountRig } from './PcbScene3dMountRig.mjs'
3
+ import { PcbScene3dTransparentMeshSplitter } from './PcbScene3dTransparentMeshSplitter.mjs'
4
+
5
+ /**
6
+ * Builds authored static component-body render roots.
7
+ */
8
+ export class PcbScene3dStaticBodyFactory {
9
+ static #DEFAULT_COLOR = 0x808080
10
+
11
+ /**
12
+ * Builds all renderable static body roots.
13
+ * @param {any} THREE Three.js namespace.
14
+ * @param {object[]} placements Static body placements.
15
+ * @returns {{ rootGroup: any, adjustmentGroup: any, placement: object }[]}
16
+ */
17
+ static buildMany(THREE, placements) {
18
+ return (Array.isArray(placements) ? placements : [])
19
+ .map((placement) =>
20
+ PcbScene3dStaticBodyFactory.build(THREE, placement)
21
+ )
22
+ .filter(Boolean)
23
+ }
24
+
25
+ /**
26
+ * Builds one authored static body root.
27
+ * @param {any} THREE Three.js namespace.
28
+ * @param {{ designator?: string, selectionKey?: string, mountSide?: string, rotationDeg?: number, positionMil?: { x?: number, y?: number, z?: number }, geometry?: object, bodyColor?: object, bodyOpacity?: number }} placement Static body placement.
29
+ * @returns {{ rootGroup: any, adjustmentGroup: any, placement: object } | null}
30
+ */
31
+ static build(THREE, placement) {
32
+ const geometry = PcbScene3dStaticBodyFactory.#buildGeometry(
33
+ THREE,
34
+ placement?.geometry
35
+ )
36
+ if (!geometry) {
37
+ return null
38
+ }
39
+
40
+ const opacity = PcbScene3dStaticBodyFactory.#resolveOpacity(placement)
41
+ const transparent = opacity < 1
42
+ const material = new THREE.MeshStandardMaterial({
43
+ color: PcbScene3dStaticBodyFactory.#resolveColor(placement),
44
+ roughness: 0.7,
45
+ metalness: 0.08,
46
+ transparent,
47
+ opacity,
48
+ depthWrite: !transparent
49
+ })
50
+ const mesh = new THREE.Mesh(geometry, material)
51
+ const renderObject = transparent
52
+ ? PcbScene3dTransparentMeshSplitter.build(THREE, mesh)
53
+ : mesh
54
+ const mountRig = PcbScene3dMountRig.create(THREE, placement)
55
+ const adjustmentGroup =
56
+ PcbScene3dStaticBodyFactory.#buildAdjustmentGroup(THREE, placement)
57
+ const selectionKey =
58
+ PcbScene3dStaticBodyFactory.resolveSelectionKey(placement)
59
+
60
+ mountRig.rootGroup.userData.scene3dSelection = {
61
+ designator: selectionKey,
62
+ displayDesignator: String(
63
+ placement?.designator || selectionKey || 'static-body'
64
+ ),
65
+ sourceType: 'static-body'
66
+ }
67
+ PcbScene3dStaticBodyFactory.#applyVariantMetadata(
68
+ mountRig.rootGroup,
69
+ placement
70
+ )
71
+ adjustmentGroup.add(renderObject)
72
+ mountRig.faceGroup.add(adjustmentGroup)
73
+
74
+ return {
75
+ rootGroup: mountRig.rootGroup,
76
+ adjustmentGroup,
77
+ placement
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Resolves the selectable key for one static body placement.
83
+ * @param {{ designator?: string, selectionKey?: string }} placement Static body placement.
84
+ * @returns {string}
85
+ */
86
+ static resolveSelectionKey(placement) {
87
+ return String(
88
+ placement?.selectionKey || placement?.designator || 'static-body'
89
+ ).trim()
90
+ }
91
+
92
+ /**
93
+ * Builds the geometry for one supported static body.
94
+ * @param {any} THREE Three.js namespace.
95
+ * @param {{ kind?: string, verticesMil?: { x?: number, y?: number }[], heightMil?: number, radiusMil?: number }} geometry Static body geometry.
96
+ * @returns {any | null}
97
+ */
98
+ static #buildGeometry(THREE, geometry) {
99
+ const kind = String(geometry?.kind || '').toLowerCase()
100
+ if (kind === 'extruded-polygon') {
101
+ return PcbScene3dStaticBodyFactory.#buildExtrudedPolygon(
102
+ THREE,
103
+ geometry
104
+ )
105
+ }
106
+ if (kind === 'cylinder') {
107
+ return PcbScene3dStaticBodyFactory.#buildCylinder(THREE, geometry)
108
+ }
109
+ if (kind === 'cone') {
110
+ return PcbScene3dStaticBodyFactory.#buildCone(THREE, geometry)
111
+ }
112
+ if (kind === 'sphere') {
113
+ return PcbScene3dStaticBodyFactory.#buildSphere(THREE, geometry)
114
+ }
115
+
116
+ return null
117
+ }
118
+
119
+ /**
120
+ * Builds an extruded polygon geometry centered on its authored Z midpoint.
121
+ * @param {any} THREE Three.js namespace.
122
+ * @param {{ verticesMil?: { x?: number, y?: number }[], heightMil?: number }} geometry Static body geometry.
123
+ * @returns {any | null}
124
+ */
125
+ static #buildExtrudedPolygon(THREE, geometry) {
126
+ const vertices = Array.isArray(geometry?.verticesMil)
127
+ ? geometry.verticesMil
128
+ : []
129
+ const heightMil = Number(geometry?.heightMil || 0)
130
+ if (vertices.length < 3 || !(heightMil > 0)) {
131
+ return null
132
+ }
133
+
134
+ const shape = new THREE.Shape()
135
+ shape.moveTo(Number(vertices[0].x || 0), Number(vertices[0].y || 0))
136
+ vertices.slice(1).forEach((vertex) => {
137
+ shape.lineTo(Number(vertex.x || 0), Number(vertex.y || 0))
138
+ })
139
+ shape.closePath()
140
+
141
+ const extrudedGeometry = new THREE.ExtrudeGeometry(shape, {
142
+ depth: heightMil,
143
+ bevelEnabled: false
144
+ })
145
+ extrudedGeometry.translate?.(0, 0, -heightMil / 2)
146
+ return extrudedGeometry
147
+ }
148
+
149
+ /**
150
+ * Builds a cylinder body geometry.
151
+ * @param {any} THREE Three.js namespace.
152
+ * @param {{ radiusMil?: number, heightMil?: number }} geometry Static body geometry.
153
+ * @returns {any | null}
154
+ */
155
+ static #buildCylinder(THREE, geometry) {
156
+ const radiusMil = Number(geometry?.radiusMil || 0)
157
+ const heightMil = Number(geometry?.heightMil || 0)
158
+ if (!(radiusMil > 0) || !(heightMil > 0)) {
159
+ return null
160
+ }
161
+
162
+ return new THREE.CylinderGeometry(radiusMil, radiusMil, heightMil, 32)
163
+ }
164
+
165
+ /**
166
+ * Builds a cone body geometry.
167
+ * @param {any} THREE Three.js namespace.
168
+ * @param {{ radiusMil?: number, heightMil?: number }} geometry Static body geometry.
169
+ * @returns {any | null}
170
+ */
171
+ static #buildCone(THREE, geometry) {
172
+ const radiusMil = Number(geometry?.radiusMil || 0)
173
+ const heightMil = Number(geometry?.heightMil || 0)
174
+ if (!(radiusMil > 0) || !(heightMil > 0)) {
175
+ return null
176
+ }
177
+
178
+ if (typeof THREE.ConeGeometry === 'function') {
179
+ return new THREE.ConeGeometry(radiusMil, heightMil, 32)
180
+ }
181
+
182
+ return new THREE.CylinderGeometry(0, radiusMil, heightMil, 32)
183
+ }
184
+
185
+ /**
186
+ * Builds a sphere body geometry.
187
+ * @param {any} THREE Three.js namespace.
188
+ * @param {{ radiusMil?: number }} geometry Static body geometry.
189
+ * @returns {any | null}
190
+ */
191
+ static #buildSphere(THREE, geometry) {
192
+ const radiusMil = Number(geometry?.radiusMil || 0)
193
+ if (!(radiusMil > 0) || typeof THREE.SphereGeometry !== 'function') {
194
+ return null
195
+ }
196
+
197
+ return new THREE.SphereGeometry(radiusMil, 32, 16)
198
+ }
199
+
200
+ /**
201
+ * Builds the transform node used by live component adjustments.
202
+ * @param {any} THREE Three.js namespace.
203
+ * @param {{ designator?: string, selectionKey?: string }} placement Static body placement.
204
+ * @returns {any}
205
+ */
206
+ static #buildAdjustmentGroup(THREE, placement) {
207
+ const adjustmentGroup = new THREE.Group()
208
+ adjustmentGroup.userData.scene3dAdjustmentTarget = true
209
+ adjustmentGroup.userData.scene3dAdjustmentDesignator = String(
210
+ PcbScene3dStaticBodyFactory.resolveSelectionKey(placement)
211
+ )
212
+ adjustmentGroup.userData.scene3dAdjustmentBaseline =
213
+ PcbScene3dComponentAdjustment.neutral()
214
+ return adjustmentGroup
215
+ }
216
+
217
+ /**
218
+ * Copies authored variant grouping metadata onto the selectable root.
219
+ * @param {any} rootGroup Static body root group.
220
+ * @param {{ coLocatedVariantGroupKey?: string }} placement Static body placement.
221
+ * @returns {void}
222
+ */
223
+ static #applyVariantMetadata(rootGroup, placement) {
224
+ const groupKey = String(
225
+ placement?.coLocatedVariantGroupKey || ''
226
+ ).trim()
227
+ if (groupKey) {
228
+ rootGroup.userData.scene3dVariantGroupKey = groupKey
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Resolves static body display color.
234
+ * @param {{ bodyColor?: object, geometry?: object }} placement Static body placement.
235
+ * @returns {number}
236
+ */
237
+ static #resolveColor(placement) {
238
+ const rawColor = Number(
239
+ placement?.bodyColor?.raw ??
240
+ placement?.geometry?.bodyColor?.raw ??
241
+ placement?.geometry?.color
242
+ )
243
+ return Number.isInteger(rawColor)
244
+ ? rawColor
245
+ : PcbScene3dStaticBodyFactory.#DEFAULT_COLOR
246
+ }
247
+
248
+ /**
249
+ * Resolves static body opacity.
250
+ * @param {{ bodyOpacity?: number, geometry?: object }} placement Static body placement.
251
+ * @returns {number}
252
+ */
253
+ static #resolveOpacity(placement) {
254
+ const opacity = Number(
255
+ placement?.bodyOpacity ?? placement?.geometry?.bodyOpacity
256
+ )
257
+ if (!Number.isFinite(opacity)) {
258
+ return 1
259
+ }
260
+
261
+ return Math.min(1, Math.max(0, opacity))
262
+ }
263
+ }
@@ -49,6 +49,7 @@ const FALLBACK_MESSAGES = {
49
49
  'scene3d.skipped': 'Skipped',
50
50
  'scene3d.source': 'Source',
51
51
  'scene3d.startFailed': '3D preview could not start:',
52
+ 'scene3d.staticBody': 'Static body',
52
53
  'scene3d.stillPreparing': '3D scene is still preparing.',
53
54
  'scene3d.title': '3D preview',
54
55
  'scene3d.to': 'to',