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
|
@@ -272,6 +272,30 @@ export class PcbAssemblyBoardSubstrateBuilder {
|
|
|
272
272
|
return null
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
if (Number(pad?.holeShape) === 1) {
|
|
276
|
+
const width = PcbAssemblyBoardSubstrateBuilder.#firstPositive([
|
|
277
|
+
pad?.holeWidth,
|
|
278
|
+
pad?.holeGeometry?.width,
|
|
279
|
+
diameter
|
|
280
|
+
])
|
|
281
|
+
const height = PcbAssemblyBoardSubstrateBuilder.#firstPositive([
|
|
282
|
+
pad?.holeHeight,
|
|
283
|
+
pad?.holeGeometry?.height,
|
|
284
|
+
diameter
|
|
285
|
+
])
|
|
286
|
+
return PcbAssemblyBoardSubstrateBuilder.#rotatedPoints(
|
|
287
|
+
PcbAssemblyBoardSubstrateBuilder.#rectanglePoints(
|
|
288
|
+
x,
|
|
289
|
+
y,
|
|
290
|
+
width,
|
|
291
|
+
height
|
|
292
|
+
),
|
|
293
|
+
x,
|
|
294
|
+
y,
|
|
295
|
+
Number(pad?.holeRotation ?? pad?.rotation ?? 0)
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
275
299
|
const slotLength = PcbAssemblyBoardSubstrateBuilder.#firstPositive([
|
|
276
300
|
pad?.holeSlotLength,
|
|
277
301
|
pad?.slotLength,
|
|
@@ -296,6 +320,25 @@ export class PcbAssemblyBoardSubstrateBuilder {
|
|
|
296
320
|
return PcbAssemblyMeshUtils.circlePoints(x, y, diameter / 2, pointCount)
|
|
297
321
|
}
|
|
298
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Builds axis-aligned rectangular aperture points.
|
|
325
|
+
* @param {number} x Center X.
|
|
326
|
+
* @param {number} y Center Y.
|
|
327
|
+
* @param {number} width Width.
|
|
328
|
+
* @param {number} height Height.
|
|
329
|
+
* @returns {number[][]} Rectangle points.
|
|
330
|
+
*/
|
|
331
|
+
static #rectanglePoints(x, y, width, height) {
|
|
332
|
+
const halfWidth = width / 2
|
|
333
|
+
const halfHeight = height / 2
|
|
334
|
+
return [
|
|
335
|
+
[x - halfWidth, y - halfHeight],
|
|
336
|
+
[x + halfWidth, y - halfHeight],
|
|
337
|
+
[x + halfWidth, y + halfHeight],
|
|
338
|
+
[x - halfWidth, y + halfHeight]
|
|
339
|
+
]
|
|
340
|
+
}
|
|
341
|
+
|
|
299
342
|
/**
|
|
300
343
|
* Builds a fallback via drill loop.
|
|
301
344
|
* @param {object} via Via primitive.
|
|
@@ -82,30 +82,52 @@ export class PcbAssemblyGeometryBuilder {
|
|
|
82
82
|
*/
|
|
83
83
|
static #buildBoardMeshes(sceneDescription) {
|
|
84
84
|
const board = sceneDescription?.board || {}
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
85
|
+
const contours = PcbAssemblyGeometryBuilder.#boardContours(board)
|
|
86
|
+
return contours.map((contour, index) => {
|
|
87
|
+
const width = Math.max(Number(contour.widthMil || 0), 1)
|
|
88
|
+
const height = Math.max(Number(contour.heightMil || 0), 1)
|
|
89
|
+
const thickness = Math.max(
|
|
90
|
+
Number(contour.thicknessMil || board.thicknessMil || 63),
|
|
91
|
+
1
|
|
92
|
+
)
|
|
93
|
+
const name = contours.length === 1 ? 'board' : `board-${index + 1}`
|
|
94
|
+
const outlinePoints =
|
|
95
|
+
PcbAssemblyGeometryBuilder.#boardOutlinePoints(contour)
|
|
96
|
+
const outlineMesh = PcbAssemblyBoardSubstrateBuilder.build(
|
|
97
|
+
name,
|
|
98
|
+
outlinePoints,
|
|
99
|
+
{ ...sceneDescription, board: contour },
|
|
100
|
+
thickness,
|
|
101
|
+
BOARD_COLOR
|
|
102
|
+
)
|
|
103
|
+
if (outlineMesh) return outlineMesh
|
|
100
104
|
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
return PcbAssemblyMeshUtils.box(name, {
|
|
106
|
+
x: Number(contour.minX || 0) + width / 2,
|
|
107
|
+
y: Number(contour.minY || 0) + height / 2,
|
|
103
108
|
width,
|
|
104
109
|
depth: height,
|
|
105
110
|
height: thickness,
|
|
106
111
|
color: BOARD_COLOR
|
|
107
112
|
})
|
|
108
|
-
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolves independently exportable board contours.
|
|
118
|
+
* @param {object} board Aggregate board metadata.
|
|
119
|
+
* @returns {object[]}
|
|
120
|
+
*/
|
|
121
|
+
static #boardContours(board) {
|
|
122
|
+
const contours = Array.isArray(board?.contours)
|
|
123
|
+
? board.contours.filter(Boolean)
|
|
124
|
+
: []
|
|
125
|
+
if (!contours.length) return [board]
|
|
126
|
+
return contours.map((contour) => ({
|
|
127
|
+
...board,
|
|
128
|
+
...contour,
|
|
129
|
+
contours: []
|
|
130
|
+
}))
|
|
109
131
|
}
|
|
110
132
|
|
|
111
133
|
/**
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { PcbAssemblyGltfModelMeshParser } from './PcbAssemblyGltfModelMeshParser.mjs'
|
|
2
2
|
import { PcbAssemblyTextModelMeshParser } from './PcbAssemblyTextModelMeshParser.mjs'
|
|
3
|
+
import { PcbScene3dDescriptorSafeRecord } from './PcbScene3dDescriptorSafeRecord.mjs'
|
|
4
|
+
import { PcbScene3dModelContent } from './PcbScene3dModelContent.mjs'
|
|
3
5
|
import { PcbScene3dStepLoader } from './PcbScene3dStepLoader.mjs'
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -10,39 +12,24 @@ export class PcbAssemblyModelMeshLoader {
|
|
|
10
12
|
/** @type {PcbScene3dStepLoader} */
|
|
11
13
|
#stepLoader
|
|
12
14
|
|
|
13
|
-
/** @type {
|
|
14
|
-
#
|
|
15
|
+
/** @type {boolean} */
|
|
16
|
+
#canFetch
|
|
15
17
|
|
|
16
|
-
/** @type {
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
/** @type {number} */
|
|
20
|
-
#fetchTimeoutMs
|
|
21
|
-
|
|
22
|
-
/** @type {Map<string, Promise<Uint8Array>>} */
|
|
23
|
-
#modelCache
|
|
18
|
+
/** @type {object} */
|
|
19
|
+
#modelOptions
|
|
24
20
|
|
|
25
21
|
/**
|
|
26
22
|
* @param {{ stepLoader?: PcbScene3dStepLoader, fetch?: (url: string, options: object) => Promise<any>, allowNetworkModelFetch?: boolean, authHeaders?: Record<string, string>, fetchTimeoutMs?: number, modelCache?: Map<string, Promise<Uint8Array>> }} [options] Loader options.
|
|
27
23
|
*/
|
|
28
24
|
constructor(options = {}) {
|
|
29
25
|
this.#stepLoader = options.stepLoader || new PcbScene3dStepLoader()
|
|
30
|
-
|
|
31
|
-
typeof options.fetch === 'function'
|
|
32
|
-
? options.fetch
|
|
33
|
-
: options.allowNetworkModelFetch === true &&
|
|
34
|
-
typeof globalThis.fetch === 'function'
|
|
35
|
-
? globalThis.fetch.bind(globalThis)
|
|
36
|
-
: null
|
|
37
|
-
this.#authHeaders = PcbAssemblyModelMeshLoader.#headers(
|
|
38
|
-
options.authHeaders
|
|
39
|
-
)
|
|
40
|
-
this.#fetchTimeoutMs = Math.max(
|
|
41
|
-
Number(options.fetchTimeoutMs || 30_000),
|
|
42
|
-
1
|
|
43
|
-
)
|
|
44
|
-
this.#modelCache =
|
|
26
|
+
const modelCache =
|
|
45
27
|
options.modelCache instanceof Map ? options.modelCache : new Map()
|
|
28
|
+
this.#modelOptions = PcbScene3dModelContent.createFetchScope({
|
|
29
|
+
...PcbScene3dDescriptorSafeRecord.copy(options),
|
|
30
|
+
modelCache
|
|
31
|
+
})
|
|
32
|
+
this.#canFetch = PcbScene3dModelContent.canFetch(this.#modelOptions)
|
|
46
33
|
}
|
|
47
34
|
|
|
48
35
|
/**
|
|
@@ -51,9 +38,10 @@ export class PcbAssemblyModelMeshLoader {
|
|
|
51
38
|
* @returns {Promise<object[]>}
|
|
52
39
|
*/
|
|
53
40
|
async loadPlacement(placement) {
|
|
54
|
-
const
|
|
41
|
+
const suppliedModel = PcbAssemblyModelMeshLoader.#withCanonicalTextData(
|
|
55
42
|
placement?.externalModel || null
|
|
56
43
|
)
|
|
44
|
+
const model = await this.#modelWithFetchedContent(suppliedModel)
|
|
57
45
|
const format = String(model?.format || '').toLowerCase()
|
|
58
46
|
if (format === 'step' || format === 'stp') {
|
|
59
47
|
return this.#loadStepMeshes(model)
|
|
@@ -88,12 +76,15 @@ export class PcbAssemblyModelMeshLoader {
|
|
|
88
76
|
* @returns {Promise<object | null>}
|
|
89
77
|
*/
|
|
90
78
|
async #modelWithFetchedContent(model) {
|
|
91
|
-
if (!model
|
|
92
|
-
return model
|
|
93
|
-
}
|
|
94
|
-
|
|
79
|
+
if (!model) return model
|
|
95
80
|
const url = String(model.resolvedUrl || model.sourceUrl || '').trim()
|
|
96
|
-
|
|
81
|
+
const format = String(model?.format || '').toLowerCase()
|
|
82
|
+
if (PcbAssemblyModelMeshLoader.#hasModelContent(model)) {
|
|
83
|
+
return format === 'gltf' && url && this.#canFetch
|
|
84
|
+
? await this.#modelWithFetchedGltfBuffers(model, url)
|
|
85
|
+
: model
|
|
86
|
+
}
|
|
87
|
+
if (!url || !this.#canFetch) {
|
|
97
88
|
return model
|
|
98
89
|
}
|
|
99
90
|
|
|
@@ -102,7 +93,7 @@ export class PcbAssemblyModelMeshLoader {
|
|
|
102
93
|
...model,
|
|
103
94
|
payloadBytes: bytes
|
|
104
95
|
}
|
|
105
|
-
if (
|
|
96
|
+
if (format === 'gltf') {
|
|
106
97
|
return await this.#modelWithFetchedGltfBuffers(fetchedModel, url)
|
|
107
98
|
}
|
|
108
99
|
|
|
@@ -114,21 +105,16 @@ export class PcbAssemblyModelMeshLoader {
|
|
|
114
105
|
* @param {string} url Resolved model URL.
|
|
115
106
|
* @returns {Promise<Uint8Array>}
|
|
116
107
|
*/
|
|
117
|
-
async #fetchModelBytes(url) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return await pending
|
|
128
|
-
} catch (error) {
|
|
129
|
-
this.#modelCache.delete(cacheKey)
|
|
130
|
-
throw error
|
|
131
|
-
}
|
|
108
|
+
async #fetchModelBytes(url, mainUrl = url) {
|
|
109
|
+
return PcbScene3dModelContent.bytes(
|
|
110
|
+
{
|
|
111
|
+
format: 'model-resource',
|
|
112
|
+
resolvedUrl: url,
|
|
113
|
+
mainModelUrl: mainUrl
|
|
114
|
+
},
|
|
115
|
+
this.#modelOptions,
|
|
116
|
+
'Model'
|
|
117
|
+
)
|
|
132
118
|
}
|
|
133
119
|
|
|
134
120
|
/**
|
|
@@ -138,7 +124,19 @@ export class PcbAssemblyModelMeshLoader {
|
|
|
138
124
|
* @returns {Promise<object>}
|
|
139
125
|
*/
|
|
140
126
|
async #modelWithFetchedGltfBuffers(model, modelUrl) {
|
|
141
|
-
const
|
|
127
|
+
const existingBuffers = PcbAssemblyModelMeshLoader.#resourceArray(
|
|
128
|
+
model?.externalBuffers
|
|
129
|
+
)
|
|
130
|
+
const existingUris = new Set(
|
|
131
|
+
existingBuffers
|
|
132
|
+
.map((resource) =>
|
|
133
|
+
String(resource?.uri || resource?.name || '').trim()
|
|
134
|
+
)
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
)
|
|
137
|
+
const uris = PcbAssemblyModelMeshLoader.#externalGltfBufferUris(
|
|
138
|
+
model
|
|
139
|
+
).filter((uri) => !existingUris.has(uri))
|
|
142
140
|
if (!uris.length) {
|
|
143
141
|
return model
|
|
144
142
|
}
|
|
@@ -153,18 +151,13 @@ export class PcbAssemblyModelMeshLoader {
|
|
|
153
151
|
uri,
|
|
154
152
|
name: uri,
|
|
155
153
|
sourceUrl,
|
|
156
|
-
payloadBytes: await this.#fetchModelBytes(sourceUrl)
|
|
154
|
+
payloadBytes: await this.#fetchModelBytes(sourceUrl, modelUrl)
|
|
157
155
|
})
|
|
158
156
|
}
|
|
159
157
|
|
|
160
158
|
return {
|
|
161
159
|
...model,
|
|
162
|
-
externalBuffers: [
|
|
163
|
-
...PcbAssemblyModelMeshLoader.#resourceArray(
|
|
164
|
-
model?.externalBuffers
|
|
165
|
-
),
|
|
166
|
-
...fetchedBuffers
|
|
167
|
-
]
|
|
160
|
+
externalBuffers: [...existingBuffers, ...fetchedBuffers]
|
|
168
161
|
}
|
|
169
162
|
}
|
|
170
163
|
|
|
@@ -215,47 +208,32 @@ export class PcbAssemblyModelMeshLoader {
|
|
|
215
208
|
* @returns {string}
|
|
216
209
|
*/
|
|
217
210
|
static #resolveSidecarUrl(uri, modelUrl) {
|
|
218
|
-
|
|
219
|
-
return new URL(uri, modelUrl).toString()
|
|
220
|
-
} catch (_error) {
|
|
221
|
-
return uri
|
|
222
|
-
}
|
|
211
|
+
return PcbScene3dModelContent.resolveRelativeUrl(uri, modelUrl)
|
|
223
212
|
}
|
|
224
213
|
|
|
225
214
|
/**
|
|
226
|
-
*
|
|
227
|
-
* @param {
|
|
228
|
-
* @returns {
|
|
215
|
+
* Maps canonical string `text` or `data` to the text parser contract.
|
|
216
|
+
* @param {object | null} model External model metadata.
|
|
217
|
+
* @returns {object | null}
|
|
229
218
|
*/
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const timeout = controller
|
|
234
|
-
? setTimeout(() => controller.abort(), this.#fetchTimeoutMs)
|
|
235
|
-
: null
|
|
236
|
-
|
|
219
|
+
static #withCanonicalTextData(model) {
|
|
220
|
+
if (!model || typeof model !== 'object') return model
|
|
221
|
+
let descriptors
|
|
237
222
|
try {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
throw error
|
|
255
|
-
} finally {
|
|
256
|
-
if (timeout) {
|
|
257
|
-
clearTimeout(timeout)
|
|
258
|
-
}
|
|
223
|
+
descriptors = Object.getOwnPropertyDescriptors(model)
|
|
224
|
+
} catch {
|
|
225
|
+
return model
|
|
226
|
+
}
|
|
227
|
+
if (typeof descriptors.payloadText?.value === 'string') {
|
|
228
|
+
return model
|
|
229
|
+
}
|
|
230
|
+
const text = ['text', 'data']
|
|
231
|
+
.map((key) => descriptors[key]?.value)
|
|
232
|
+
.find((value) => typeof value === 'string')
|
|
233
|
+
if (typeof text !== 'string') return model
|
|
234
|
+
return {
|
|
235
|
+
...PcbScene3dDescriptorSafeRecord.copy(model),
|
|
236
|
+
payloadText: text
|
|
259
237
|
}
|
|
260
238
|
}
|
|
261
239
|
|
|
@@ -342,22 +320,43 @@ export class PcbAssemblyModelMeshLoader {
|
|
|
342
320
|
if (typeof model?.payloadText === 'string') {
|
|
343
321
|
return model.payloadText
|
|
344
322
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
323
|
+
for (const value of [
|
|
324
|
+
model?.payloadBytes,
|
|
325
|
+
model?.bytes,
|
|
326
|
+
model?.data,
|
|
327
|
+
model?.file
|
|
328
|
+
]) {
|
|
329
|
+
if (typeof value?.text === 'function') {
|
|
330
|
+
return await value.text()
|
|
331
|
+
}
|
|
332
|
+
const bytes =
|
|
333
|
+
await PcbAssemblyModelMeshLoader.#bytesFromValue(value)
|
|
334
|
+
if (bytes) return new TextDecoder().decode(bytes)
|
|
348
335
|
}
|
|
349
336
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
337
|
+
throw new Error('WRL model content is not available.')
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Reads one local model payload as bytes.
|
|
342
|
+
* @param {unknown} value Byte or blob candidate.
|
|
343
|
+
* @returns {Promise<Uint8Array | null>}
|
|
344
|
+
*/
|
|
345
|
+
static async #bytesFromValue(value) {
|
|
346
|
+
if (!value) return null
|
|
347
|
+
if (value instanceof Uint8Array) return value
|
|
348
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value)
|
|
349
|
+
if (ArrayBuffer.isView(value)) {
|
|
350
|
+
return new Uint8Array(
|
|
351
|
+
value.buffer,
|
|
352
|
+
value.byteOffset,
|
|
353
|
+
value.byteLength
|
|
353
354
|
)
|
|
354
355
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
return new TextDecoder().decode(model.file)
|
|
356
|
+
if (typeof value.arrayBuffer === 'function') {
|
|
357
|
+
return new Uint8Array(await value.arrayBuffer())
|
|
358
358
|
}
|
|
359
|
-
|
|
360
|
-
throw new Error('WRL model content is not available.')
|
|
359
|
+
return null
|
|
361
360
|
}
|
|
362
361
|
|
|
363
362
|
/**
|
|
@@ -413,54 +412,6 @@ export class PcbAssemblyModelMeshLoader {
|
|
|
413
412
|
)
|
|
414
413
|
}
|
|
415
414
|
|
|
416
|
-
/**
|
|
417
|
-
* Converts a fetch response or byte-like result into bytes.
|
|
418
|
-
* @param {any} response Fetch response or direct byte-like value.
|
|
419
|
-
* @returns {Promise<Uint8Array>}
|
|
420
|
-
*/
|
|
421
|
-
static async #bytesFromFetchResponse(response) {
|
|
422
|
-
if (response?.ok === false) {
|
|
423
|
-
throw new Error(
|
|
424
|
-
'Model fetch failed with HTTP status ' +
|
|
425
|
-
String(response.status || 'unknown') +
|
|
426
|
-
'.'
|
|
427
|
-
)
|
|
428
|
-
}
|
|
429
|
-
if (response instanceof Uint8Array) {
|
|
430
|
-
return response
|
|
431
|
-
}
|
|
432
|
-
if (response instanceof ArrayBuffer) {
|
|
433
|
-
return new Uint8Array(response)
|
|
434
|
-
}
|
|
435
|
-
if (ArrayBuffer.isView(response) && !(response instanceof DataView)) {
|
|
436
|
-
return new Uint8Array(
|
|
437
|
-
response.buffer,
|
|
438
|
-
response.byteOffset,
|
|
439
|
-
response.byteLength
|
|
440
|
-
)
|
|
441
|
-
}
|
|
442
|
-
if (typeof response?.arrayBuffer === 'function') {
|
|
443
|
-
return new Uint8Array(await response.arrayBuffer())
|
|
444
|
-
}
|
|
445
|
-
if (typeof response?.text === 'function') {
|
|
446
|
-
return new TextEncoder().encode(await response.text())
|
|
447
|
-
}
|
|
448
|
-
throw new Error('Fetched model content is not readable.')
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Normalizes string headers.
|
|
453
|
-
* @param {object | undefined} headers Candidate headers.
|
|
454
|
-
* @returns {Record<string, string>}
|
|
455
|
-
*/
|
|
456
|
-
static #headers(headers) {
|
|
457
|
-
return Object.fromEntries(
|
|
458
|
-
Object.entries(headers || {})
|
|
459
|
-
.map(([key, value]) => [String(key), String(value)])
|
|
460
|
-
.filter(([key, value]) => key && value)
|
|
461
|
-
)
|
|
462
|
-
}
|
|
463
|
-
|
|
464
415
|
/**
|
|
465
416
|
* Normalizes resource metadata into an array.
|
|
466
417
|
* @param {unknown} value Candidate resource collection.
|
|
@@ -180,6 +180,28 @@ export class PcbAssemblyPadMeshBuilder {
|
|
|
180
180
|
|
|
181
181
|
const holeX = x + Number(pad?.holeOffsetX || 0)
|
|
182
182
|
const holeY = y + Number(pad?.holeOffsetY || 0)
|
|
183
|
+
if (Number(pad?.holeShape) === 1) {
|
|
184
|
+
const width = PcbAssemblyPadMeshBuilder.#firstPositive([
|
|
185
|
+
pad?.holeWidth,
|
|
186
|
+
pad?.holeGeometry?.width,
|
|
187
|
+
diameter
|
|
188
|
+
])
|
|
189
|
+
const height = PcbAssemblyPadMeshBuilder.#firstPositive([
|
|
190
|
+
pad?.holeHeight,
|
|
191
|
+
pad?.holeGeometry?.height,
|
|
192
|
+
diameter
|
|
193
|
+
])
|
|
194
|
+
return PcbAssemblyPadMeshBuilder.#rotatedPoints(
|
|
195
|
+
PcbAssemblyPadMeshBuilder.#rectanglePoints(holeX, holeY, {
|
|
196
|
+
width,
|
|
197
|
+
height
|
|
198
|
+
}),
|
|
199
|
+
holeX,
|
|
200
|
+
holeY,
|
|
201
|
+
Number(pad?.holeRotation ?? pad?.rotation ?? 0) -
|
|
202
|
+
Number(pad?.rotation || 0)
|
|
203
|
+
)
|
|
204
|
+
}
|
|
183
205
|
const slotLength = PcbAssemblyPadMeshBuilder.#firstPositive([
|
|
184
206
|
pad?.holeSlotLength,
|
|
185
207
|
pad?.slotLength,
|