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.
Files changed (63) hide show
  1. package/README.md +46 -21
  2. package/docs/api.md +109 -8
  3. package/docs/circuitjson.md +143 -29
  4. package/docs/model-format.md +50 -7
  5. package/docs/release-notes-v1.2.0.md +122 -0
  6. package/docs/testing.md +30 -1
  7. package/package.json +6 -3
  8. package/spec/library-scope.md +10 -2
  9. package/src/CircuitJsonCadModelAssetResolver.mjs +697 -83
  10. package/src/PcbAssemblyBoardSubstrateBuilder.mjs +43 -0
  11. package/src/PcbAssemblyGeometryBuilder.mjs +40 -18
  12. package/src/PcbAssemblyModelMeshLoader.mjs +101 -150
  13. package/src/PcbAssemblyPadMeshBuilder.mjs +22 -0
  14. package/src/PcbModelArchiveExporter.mjs +28 -112
  15. package/src/PcbModelArchiveSourceBundle.mjs +326 -0
  16. package/src/PcbScene3dAabbIndex.mjs +464 -0
  17. package/src/PcbScene3dBoardAssemblyPresentation.mjs +1 -1
  18. package/src/PcbScene3dBoardEdgeCutoutBuilder.mjs +9 -5
  19. package/src/PcbScene3dBoardMaterialPalette.mjs +29 -0
  20. package/src/PcbScene3dBoardShapeFactory.mjs +11 -118
  21. package/src/PcbScene3dBoardSolderMaskFactory.mjs +65 -39
  22. package/src/PcbScene3dCircuitJsonAdapter.mjs +151 -48
  23. package/src/PcbScene3dCircuitJsonDrillDetail.mjs +31 -0
  24. package/src/PcbScene3dCircuitJsonGeometry.mjs +184 -33
  25. package/src/PcbScene3dCircuitJsonInput.mjs +132 -0
  26. package/src/PcbScene3dCircuitJsonModelAsset.mjs +40 -0
  27. package/src/PcbScene3dController.mjs +40 -34
  28. package/src/PcbScene3dCopperFactory.mjs +36 -22
  29. package/src/PcbScene3dCopperFillAreaClipper.mjs +133 -235
  30. package/src/PcbScene3dCopperFillCoverageContext.mjs +204 -0
  31. package/src/PcbScene3dCopperFillLoopSetResolver.mjs +192 -0
  32. package/src/PcbScene3dCopperFillMeshBuilder.mjs +77 -295
  33. package/src/PcbScene3dCopperTextFactory.mjs +12 -4
  34. package/src/PcbScene3dCutoutCircleDetector.mjs +34 -17
  35. package/src/PcbScene3dCutoutGeometryFilter.mjs +104 -269
  36. package/src/PcbScene3dCutoutGridIndex.mjs +184 -0
  37. package/src/PcbScene3dDeferredModelFinalizer.mjs +52 -0
  38. package/src/PcbScene3dDescriptorSafeRecord.mjs +38 -0
  39. package/src/PcbScene3dDrillCutoutFilter.mjs +149 -143
  40. package/src/PcbScene3dDrillPathFactory.mjs +86 -16
  41. package/src/PcbScene3dDrillVoidFactory.mjs +35 -10
  42. package/src/PcbScene3dExternalModelGroupLoader.mjs +472 -31
  43. package/src/PcbScene3dExternalModels.mjs +23 -24
  44. package/src/PcbScene3dFacetedModelGroupBuilder.mjs +217 -0
  45. package/src/PcbScene3dGeometryZCompressor.mjs +4 -2
  46. package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +18 -4
  47. package/src/PcbScene3dMaskCoveredCopperSurfaceFilter.mjs +75 -10
  48. package/src/PcbScene3dModelContent.mjs +236 -0
  49. package/src/PcbScene3dModelFetchPolicy.mjs +304 -0
  50. package/src/PcbScene3dModelIdentity.mjs +106 -0
  51. package/src/PcbScene3dPlatedDrillSpecResolver.mjs +141 -0
  52. package/src/PcbScene3dPreparedPolygon.mjs +709 -0
  53. package/src/PcbScene3dPreparedPolygonSet.mjs +70 -0
  54. package/src/PcbScene3dRuntime.mjs +45 -45
  55. package/src/PcbScene3dRuntimeBoardMeshes.mjs +85 -1
  56. package/src/PcbScene3dShapeHoleGeometryCleaner.mjs +4 -2
  57. package/src/PcbScene3dShellRenderer.mjs +75 -6
  58. package/src/PcbScene3dSilkscreenCutoutContext.mjs +255 -0
  59. package/src/PcbScene3dSilkscreenFactory.mjs +87 -127
  60. package/src/PcbScene3dSilkscreenFillSeamBuilder.mjs +11 -5
  61. package/src/PcbScene3dStepLoader.mjs +11 -10
  62. package/src/PcbScene3dText.mjs +1 -1
  63. package/src/PcbScene3dTriangleVertexQueryBounds.mjs +302 -0
@@ -0,0 +1,302 @@
1
+ const FLOAT64_VALUE = new Float64Array(1)
2
+ const FLOAT64_BITS = new BigUint64Array(FLOAT64_VALUE.buffer)
3
+ const UNIT_ROUNDOFF = Number.EPSILON / 2
4
+ // This is the ORIENT2D forward-error coefficient for the unchanged legacy
5
+ // determinant order; every magnitude passed to it is rounded outward first.
6
+ const ORIENTATION_ERROR_FACTOR = (3 + 16 * UNIT_ROUNDOFF) * UNIT_ROUNDOFF
7
+
8
+ /**
9
+ * Resolves candidate-complete cutout-vertex bounds for tolerant triangles.
10
+ */
11
+ export class PcbScene3dTriangleVertexQueryBounds {
12
+ /**
13
+ * Expands triangle bounds for the legacy signed-area tolerance predicate.
14
+ * Returns null when finite indexed bounds cannot be proven conservative.
15
+ * @param {{ x: number, y: number }[]} triangle Triangle vertices.
16
+ * @param {{ minX: number, maxX: number, minY: number, maxY: number }} triangleBounds Triangle bounds.
17
+ * @param {{ minX: number, maxX: number, minY: number, maxY: number }} cutoutBounds Cutout bounds.
18
+ * @param {number} epsilon Signed-area tolerance.
19
+ * @returns {{ minX: number, maxX: number, minY: number, maxY: number } | null}
20
+ */
21
+ static resolve(triangle, triangleBounds, cutoutBounds, epsilon) {
22
+ if (
23
+ !Array.isArray(triangle) ||
24
+ triangle.length !== 3 ||
25
+ !PcbScene3dTriangleVertexQueryBounds.#hasFiniteBounds(
26
+ triangleBounds
27
+ ) ||
28
+ !PcbScene3dTriangleVertexQueryBounds.#hasFiniteBounds(
29
+ cutoutBounds
30
+ ) ||
31
+ !Number.isFinite(epsilon) ||
32
+ epsilon <= 0
33
+ ) {
34
+ return null
35
+ }
36
+
37
+ const doubledArea = Math.abs(
38
+ PcbScene3dTriangleVertexQueryBounds.#cross(
39
+ triangle[0],
40
+ triangle[1],
41
+ triangle[2]
42
+ )
43
+ )
44
+ const areaError =
45
+ PcbScene3dTriangleVertexQueryBounds.#resolveCrossErrorBound(
46
+ triangle[0],
47
+ triangle[1],
48
+ triangle[2]
49
+ )
50
+ const minimumArea = PcbScene3dTriangleVertexQueryBounds.#nextDown(
51
+ doubledArea - areaError
52
+ )
53
+ const pointCrossError =
54
+ PcbScene3dTriangleVertexQueryBounds.#resolvePointCrossErrorBound(
55
+ triangle,
56
+ cutoutBounds
57
+ )
58
+
59
+ if (
60
+ !Number.isFinite(minimumArea) ||
61
+ minimumArea <= 0 ||
62
+ !Number.isFinite(pointCrossError) ||
63
+ pointCrossError >= epsilon
64
+ ) {
65
+ return null
66
+ }
67
+
68
+ const effectiveTolerance = PcbScene3dTriangleVertexQueryBounds.#nextUp(
69
+ epsilon + pointCrossError
70
+ )
71
+ const width = PcbScene3dTriangleVertexQueryBounds.#nextUp(
72
+ triangleBounds.maxX - triangleBounds.minX
73
+ )
74
+ const height = PcbScene3dTriangleVertexQueryBounds.#nextUp(
75
+ triangleBounds.maxY - triangleBounds.minY
76
+ )
77
+ const expansionScale = PcbScene3dTriangleVertexQueryBounds.#nextUp(
78
+ (2 * effectiveTolerance) / minimumArea
79
+ )
80
+ const expansionX = PcbScene3dTriangleVertexQueryBounds.#nextUp(
81
+ width * expansionScale
82
+ )
83
+ const expansionY = PcbScene3dTriangleVertexQueryBounds.#nextUp(
84
+ height * expansionScale
85
+ )
86
+ const result = {
87
+ minX: PcbScene3dTriangleVertexQueryBounds.#nextDown(
88
+ triangleBounds.minX - expansionX
89
+ ),
90
+ maxX: PcbScene3dTriangleVertexQueryBounds.#nextUp(
91
+ triangleBounds.maxX + expansionX
92
+ ),
93
+ minY: PcbScene3dTriangleVertexQueryBounds.#nextDown(
94
+ triangleBounds.minY - expansionY
95
+ ),
96
+ maxY: PcbScene3dTriangleVertexQueryBounds.#nextUp(
97
+ triangleBounds.maxY + expansionY
98
+ )
99
+ }
100
+
101
+ return PcbScene3dTriangleVertexQueryBounds.#hasFiniteBounds(result)
102
+ ? result
103
+ : null
104
+ }
105
+
106
+ /**
107
+ * Resolves the maximum point-first determinant error over cutout bounds.
108
+ * @param {{ x: number, y: number }[]} triangle Triangle vertices.
109
+ * @param {{ minX: number, maxX: number, minY: number, maxY: number }} bounds Cutout bounds.
110
+ * @returns {number}
111
+ */
112
+ static #resolvePointCrossErrorBound(triangle, bounds) {
113
+ let maximumError = 0
114
+
115
+ for (let index = 0; index < triangle.length; index += 1) {
116
+ const start = triangle[index]
117
+ const end = triangle[(index + 1) % triangle.length]
118
+ const firstX =
119
+ PcbScene3dTriangleVertexQueryBounds.#resolveRangeDifferenceBound(
120
+ start.x,
121
+ bounds.minX,
122
+ bounds.maxX
123
+ )
124
+ const firstY =
125
+ PcbScene3dTriangleVertexQueryBounds.#resolveRangeDifferenceBound(
126
+ start.y,
127
+ bounds.minY,
128
+ bounds.maxY
129
+ )
130
+ const secondX =
131
+ PcbScene3dTriangleVertexQueryBounds.#resolveRangeDifferenceBound(
132
+ end.x,
133
+ bounds.minX,
134
+ bounds.maxX
135
+ )
136
+ const secondY =
137
+ PcbScene3dTriangleVertexQueryBounds.#resolveRangeDifferenceBound(
138
+ end.y,
139
+ bounds.minY,
140
+ bounds.maxY
141
+ )
142
+ const error =
143
+ PcbScene3dTriangleVertexQueryBounds.#resolveDeterminantErrorBound(
144
+ firstX,
145
+ secondY,
146
+ firstY,
147
+ secondX
148
+ )
149
+
150
+ maximumError = Math.max(maximumError, error)
151
+ }
152
+
153
+ return maximumError
154
+ }
155
+
156
+ /**
157
+ * Resolves one point-first determinant error for fixed coordinates.
158
+ * @param {{ x: number, y: number }} point First cross argument.
159
+ * @param {{ x: number, y: number }} start Second cross argument.
160
+ * @param {{ x: number, y: number }} end Third cross argument.
161
+ * @returns {number}
162
+ */
163
+ static #resolveCrossErrorBound(point, start, end) {
164
+ return PcbScene3dTriangleVertexQueryBounds.#resolveDeterminantErrorBound(
165
+ PcbScene3dTriangleVertexQueryBounds.#resolveDifferenceBound(
166
+ start.x,
167
+ point.x
168
+ ),
169
+ PcbScene3dTriangleVertexQueryBounds.#resolveDifferenceBound(
170
+ end.y,
171
+ point.y
172
+ ),
173
+ PcbScene3dTriangleVertexQueryBounds.#resolveDifferenceBound(
174
+ start.y,
175
+ point.y
176
+ ),
177
+ PcbScene3dTriangleVertexQueryBounds.#resolveDifferenceBound(
178
+ end.x,
179
+ point.x
180
+ )
181
+ )
182
+ }
183
+
184
+ /**
185
+ * Resolves a conservative ORIENT2D forward-error bound.
186
+ * @param {number} firstLeft First product's left magnitude bound.
187
+ * @param {number} firstRight First product's right magnitude bound.
188
+ * @param {number} secondLeft Second product's left magnitude bound.
189
+ * @param {number} secondRight Second product's right magnitude bound.
190
+ * @returns {number}
191
+ */
192
+ static #resolveDeterminantErrorBound(
193
+ firstLeft,
194
+ firstRight,
195
+ secondLeft,
196
+ secondRight
197
+ ) {
198
+ const firstProduct = PcbScene3dTriangleVertexQueryBounds.#nextUp(
199
+ firstLeft * firstRight
200
+ )
201
+ const secondProduct = PcbScene3dTriangleVertexQueryBounds.#nextUp(
202
+ secondLeft * secondRight
203
+ )
204
+ const determinantSum = PcbScene3dTriangleVertexQueryBounds.#nextUp(
205
+ firstProduct + secondProduct
206
+ )
207
+
208
+ return PcbScene3dTriangleVertexQueryBounds.#nextUp(
209
+ ORIENTATION_ERROR_FACTOR * determinantSum
210
+ )
211
+ }
212
+
213
+ /**
214
+ * Resolves an outward-rounded difference magnitude over one range.
215
+ * @param {number} value Fixed coordinate.
216
+ * @param {number} minimum Range minimum.
217
+ * @param {number} maximum Range maximum.
218
+ * @returns {number}
219
+ */
220
+ static #resolveRangeDifferenceBound(value, minimum, maximum) {
221
+ return Math.max(
222
+ PcbScene3dTriangleVertexQueryBounds.#resolveDifferenceBound(
223
+ value,
224
+ minimum
225
+ ),
226
+ PcbScene3dTriangleVertexQueryBounds.#resolveDifferenceBound(
227
+ value,
228
+ maximum
229
+ )
230
+ )
231
+ }
232
+
233
+ /**
234
+ * Resolves an outward-rounded difference magnitude.
235
+ * @param {number} first First coordinate.
236
+ * @param {number} second Second coordinate.
237
+ * @returns {number}
238
+ */
239
+ static #resolveDifferenceBound(first, second) {
240
+ return PcbScene3dTriangleVertexQueryBounds.#nextUp(
241
+ Math.abs(first - second)
242
+ )
243
+ }
244
+
245
+ /**
246
+ * Returns true for finite, ordered two-dimensional bounds.
247
+ * @param {{ minX: number, maxX: number, minY: number, maxY: number }} bounds Bounds to inspect.
248
+ * @returns {boolean}
249
+ */
250
+ static #hasFiniteBounds(bounds) {
251
+ return (
252
+ Number.isFinite(bounds?.minX) &&
253
+ Number.isFinite(bounds?.maxX) &&
254
+ Number.isFinite(bounds?.minY) &&
255
+ Number.isFinite(bounds?.maxY) &&
256
+ bounds.minX <= bounds.maxX &&
257
+ bounds.minY <= bounds.maxY
258
+ )
259
+ }
260
+
261
+ /**
262
+ * Resolves the unchanged legacy signed-area expression.
263
+ * @param {{ x: number, y: number }} first First point.
264
+ * @param {{ x: number, y: number }} second Second point.
265
+ * @param {{ x: number, y: number }} third Third point.
266
+ * @returns {number}
267
+ */
268
+ static #cross(first, second, third) {
269
+ return (
270
+ (second.x - first.x) * (third.y - first.y) -
271
+ (second.y - first.y) * (third.x - first.x)
272
+ )
273
+ }
274
+
275
+ /**
276
+ * Returns the next representable number toward positive infinity.
277
+ * @param {number} value Source value.
278
+ * @returns {number}
279
+ */
280
+ static #nextUp(value) {
281
+ if (Number.isNaN(value) || value === Infinity) return value
282
+ if (value === 0) return Number.MIN_VALUE
283
+
284
+ FLOAT64_VALUE[0] = value
285
+ FLOAT64_BITS[0] += value > 0 ? 1n : -1n
286
+ return FLOAT64_VALUE[0]
287
+ }
288
+
289
+ /**
290
+ * Returns the next representable number toward negative infinity.
291
+ * @param {number} value Source value.
292
+ * @returns {number}
293
+ */
294
+ static #nextDown(value) {
295
+ if (Number.isNaN(value) || value === -Infinity) return value
296
+ if (value === 0) return -Number.MIN_VALUE
297
+
298
+ FLOAT64_VALUE[0] = value
299
+ FLOAT64_BITS[0] += value > 0 ? -1n : 1n
300
+ return FLOAT64_VALUE[0]
301
+ }
302
+ }