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.
- package/README.md +46 -21
- package/docs/api.md +109 -8
- package/docs/circuitjson.md +143 -29
- package/docs/model-format.md +50 -7
- package/docs/release-notes-v1.2.0.md +122 -0
- package/docs/testing.md +30 -1
- package/package.json +6 -3
- package/spec/library-scope.md +10 -2
- package/src/CircuitJsonCadModelAssetResolver.mjs +697 -83
- package/src/PcbAssemblyBoardSubstrateBuilder.mjs +43 -0
- package/src/PcbAssemblyGeometryBuilder.mjs +40 -18
- package/src/PcbAssemblyModelMeshLoader.mjs +101 -150
- package/src/PcbAssemblyPadMeshBuilder.mjs +22 -0
- package/src/PcbModelArchiveExporter.mjs +28 -112
- package/src/PcbModelArchiveSourceBundle.mjs +326 -0
- package/src/PcbScene3dAabbIndex.mjs +464 -0
- package/src/PcbScene3dBoardAssemblyPresentation.mjs +1 -1
- package/src/PcbScene3dBoardEdgeCutoutBuilder.mjs +9 -5
- package/src/PcbScene3dBoardMaterialPalette.mjs +29 -0
- package/src/PcbScene3dBoardShapeFactory.mjs +11 -118
- package/src/PcbScene3dBoardSolderMaskFactory.mjs +65 -39
- package/src/PcbScene3dCircuitJsonAdapter.mjs +151 -48
- package/src/PcbScene3dCircuitJsonDrillDetail.mjs +31 -0
- package/src/PcbScene3dCircuitJsonGeometry.mjs +184 -33
- package/src/PcbScene3dCircuitJsonInput.mjs +132 -0
- package/src/PcbScene3dCircuitJsonModelAsset.mjs +40 -0
- package/src/PcbScene3dController.mjs +40 -34
- package/src/PcbScene3dCopperFactory.mjs +36 -22
- package/src/PcbScene3dCopperFillAreaClipper.mjs +133 -235
- package/src/PcbScene3dCopperFillCoverageContext.mjs +204 -0
- package/src/PcbScene3dCopperFillLoopSetResolver.mjs +192 -0
- package/src/PcbScene3dCopperFillMeshBuilder.mjs +77 -295
- package/src/PcbScene3dCopperTextFactory.mjs +12 -4
- package/src/PcbScene3dCutoutCircleDetector.mjs +34 -17
- package/src/PcbScene3dCutoutGeometryFilter.mjs +104 -269
- package/src/PcbScene3dCutoutGridIndex.mjs +184 -0
- package/src/PcbScene3dDeferredModelFinalizer.mjs +52 -0
- package/src/PcbScene3dDescriptorSafeRecord.mjs +38 -0
- package/src/PcbScene3dDrillCutoutFilter.mjs +149 -143
- package/src/PcbScene3dDrillPathFactory.mjs +86 -16
- package/src/PcbScene3dDrillVoidFactory.mjs +35 -10
- package/src/PcbScene3dExternalModelGroupLoader.mjs +472 -31
- package/src/PcbScene3dExternalModels.mjs +23 -24
- package/src/PcbScene3dFacetedModelGroupBuilder.mjs +217 -0
- package/src/PcbScene3dGeometryZCompressor.mjs +4 -2
- package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +18 -4
- package/src/PcbScene3dMaskCoveredCopperSurfaceFilter.mjs +75 -10
- package/src/PcbScene3dModelContent.mjs +236 -0
- package/src/PcbScene3dModelFetchPolicy.mjs +304 -0
- package/src/PcbScene3dModelIdentity.mjs +106 -0
- package/src/PcbScene3dPlatedDrillSpecResolver.mjs +141 -0
- package/src/PcbScene3dPreparedPolygon.mjs +709 -0
- package/src/PcbScene3dPreparedPolygonSet.mjs +70 -0
- package/src/PcbScene3dRuntime.mjs +45 -45
- package/src/PcbScene3dRuntimeBoardMeshes.mjs +85 -1
- package/src/PcbScene3dShapeHoleGeometryCleaner.mjs +4 -2
- package/src/PcbScene3dShellRenderer.mjs +75 -6
- package/src/PcbScene3dSilkscreenCutoutContext.mjs +255 -0
- package/src/PcbScene3dSilkscreenFactory.mjs +87 -127
- package/src/PcbScene3dSilkscreenFillSeamBuilder.mjs +11 -5
- package/src/PcbScene3dStepLoader.mjs +11 -10
- package/src/PcbScene3dText.mjs +1 -1
- 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
|
+
}
|