pcb-scene3d-viewer 1.2.0 → 1.2.2
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/NOTICE.md +8 -5
- package/README.md +17 -1
- package/docs/api.md +16 -0
- package/docs/circuitjson.md +15 -4
- package/docs/release-notes-v1.2.1.md +37 -0
- package/docs/release-notes-v1.2.2.md +33 -0
- package/docs/testing.md +1 -0
- package/package.json +5 -3
- package/src/PcbScene3dCircuitJsonAdapter.mjs +1 -1
- package/src/PcbScene3dCircuitJsonCopperPourBuilder.mjs +6 -1
- package/src/PcbScene3dCircuitJsonTraceRouteBuilder.mjs +50 -10
- package/src/PcbScene3dOcctImporterLoader.mjs +79 -0
- package/src/PcbScene3dStepLoader.mjs +18 -108
package/NOTICE.md
CHANGED
|
@@ -27,10 +27,13 @@ required by the applicable license:
|
|
|
27
27
|
For applications with an "About", "Licenses", or "Legal Notices" screen,
|
|
28
28
|
include a reasonable reference to this project and its original author there.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
In particular,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
Third-party package dependencies retain their own notices and license terms.
|
|
31
|
+
In particular, distributions that include `@sunbox/occt-import-js` and Open
|
|
32
|
+
CASCADE Technology must preserve the notices shipped by that installed package
|
|
33
|
+
in:
|
|
34
|
+
|
|
35
|
+
- `node_modules/@sunbox/occt-import-js/dist/license.occt-import-js.txt`;
|
|
36
|
+
- `node_modules/@sunbox/occt-import-js/dist/license.occt.txt`;
|
|
37
|
+
- `node_modules/@sunbox/occt-import-js/dist/OCCT_LGPL_EXCEPTION.txt`.
|
|
35
38
|
|
|
36
39
|
Package-manager dependencies retain their own licenses.
|
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ browser-based ECAD tools.
|
|
|
24
24
|
|
|
25
25
|
## CircuitJSON 1.1 convergence
|
|
26
26
|
|
|
27
|
-
Version 1.2.
|
|
27
|
+
Version 1.2.2 accepts the common document and prepared-context shapes returned
|
|
28
28
|
by CircuitJSON, Gerber, Altium, and KiCad Toolkit 1.1-compatible APIs. The
|
|
29
29
|
adapter requests the shared `elements` index once and reuses it across repeated
|
|
30
30
|
scene builds. `PcbScene3dCircuitJsonAdapter.prepare()` exposes that proof-aware
|
|
@@ -47,6 +47,13 @@ Legal rectangular and square CircuitJSON drill apertures retain their exact
|
|
|
47
47
|
width, height, and board-space rotation through substrate, pad, and assembly
|
|
48
48
|
export meshes.
|
|
49
49
|
|
|
50
|
+
Canonical documents retain their exact `source.format` as the scene
|
|
51
|
+
`sourceFormat`; raw element arrays continue to use `circuitjson`. Routed traces
|
|
52
|
+
and copper pours with no authored solder-mask coverage value remain covered,
|
|
53
|
+
while `covered_with_solder_mask: false` keeps an explicit opening exposed.
|
|
54
|
+
Standard vias likewise default to tented and honor `is_tented: false` as an
|
|
55
|
+
explicit opening.
|
|
56
|
+
|
|
50
57
|
The live runtime loads STEP/STP, WRL/VRML, STL, OBJ, GLTF/GLB, and 3MF from
|
|
51
58
|
canonical text/bytes or browser files. Referenced GLTF buffers, OBJ material
|
|
52
59
|
libraries, and WRL textures are attached from matching document/session assets
|
|
@@ -57,6 +64,13 @@ stay on the main model origin; `authHeadersForUrl` is the explicit per-URL
|
|
|
57
64
|
override. The model ZIP exporter uses the same policy and writes each raw model
|
|
58
65
|
under its original source basename with safe GLTF, OBJ, and WRL companions.
|
|
59
66
|
|
|
67
|
+
STEP loading uses the installed `@sunbox/occt-import-js` package directly. Its
|
|
68
|
+
package-owned worker is reused for browser imports, while runtimes without Web
|
|
69
|
+
Workers dynamically import the same ESM factory. Hosts only need to serve the
|
|
70
|
+
package `dist/` directory at
|
|
71
|
+
`/node_modules/@sunbox/occt-import-js/dist/`; no copied runtime, global script,
|
|
72
|
+
or host-owned worker is required.
|
|
73
|
+
|
|
60
74
|
## Install
|
|
61
75
|
|
|
62
76
|
```bash
|
|
@@ -116,6 +130,8 @@ const controller = new PcbScene3dController(viewportNode, document)
|
|
|
116
130
|
|
|
117
131
|
- [API](docs/api.md)
|
|
118
132
|
- [CircuitJSON usage](docs/circuitjson.md)
|
|
133
|
+
- [1.2.2 release notes](docs/release-notes-v1.2.2.md)
|
|
134
|
+
- [1.2.1 release notes](docs/release-notes-v1.2.1.md)
|
|
119
135
|
- [1.2.0 release notes](docs/release-notes-v1.2.0.md)
|
|
120
136
|
- [Model format](docs/model-format.md)
|
|
121
137
|
- [Testing](docs/testing.md)
|
package/docs/api.md
CHANGED
|
@@ -108,6 +108,14 @@ Methods:
|
|
|
108
108
|
and courtyard artwork as silkscreen detail. `showPcbPaste: true` renders
|
|
109
109
|
direct solder-paste artwork as a separate top/bottom overlay.
|
|
110
110
|
|
|
111
|
+
For canonical document and prepared-context inputs, the returned scene's
|
|
112
|
+
`sourceFormat` is the exact canonical `source.format`. Dense element arrays,
|
|
113
|
+
which do not carry source metadata, retain the `circuitjson` fallback. Routed
|
|
114
|
+
traces and copper pours default to covered when their coverage property is
|
|
115
|
+
omitted; an explicit `covered_with_solder_mask: false` remains an exposed
|
|
116
|
+
opening. Standard vias default to tented and honor `is_tented: false` as an
|
|
117
|
+
explicit opening.
|
|
118
|
+
|
|
111
119
|
`PcbScene3dController` and `PcbScene3dRuntime` call this adapter automatically
|
|
112
120
|
when they receive direct CircuitJSON input. See
|
|
113
121
|
[CircuitJSON usage](circuitjson.md) for supported elements, units, and examples.
|
|
@@ -163,6 +171,14 @@ the model, preventing implicit texture networking. STL, OBJ, GLTF, and GLB use
|
|
|
163
171
|
the shared faceted mesh pipeline so runtime and assembly export preserve the
|
|
164
172
|
same units, material color, opacity, and vertex-color behavior.
|
|
165
173
|
|
|
174
|
+
STEP imports resolve `occt-import-js.js`, `occt-import-js.wasm`, and
|
|
175
|
+
`occt-import-js-worker.js` from the installed `@sunbox/occt-import-js` package.
|
|
176
|
+
The package worker is persistent and serialized per loader. When Web Workers
|
|
177
|
+
are unavailable, the viewer dynamically imports the ESM factory directly; it
|
|
178
|
+
does not inject a classic script or depend on a global factory. Worker transfer
|
|
179
|
+
uses a loader-owned byte snapshot, and rejected ESM initialization is evicted
|
|
180
|
+
so callers retain their input and can retry transient failures.
|
|
181
|
+
|
|
166
182
|
Static `authHeaders` are sent only to the main model origin. A host that
|
|
167
183
|
intentionally authorizes another origin can return headers from
|
|
168
184
|
`authHeadersForUrl(url, { mainUrl, sameOrigin, label })`. Each fetch scope
|
package/docs/circuitjson.md
CHANGED
|
@@ -14,6 +14,10 @@ KiCad, Gerber, or other format-specific scene builder.
|
|
|
14
14
|
Prepared contexts are the fastest repeated-render path because validation and
|
|
15
15
|
the adapter's `elements` index are built at most once.
|
|
16
16
|
|
|
17
|
+
Canonical document envelopes and prepared contexts retain `source.format` as
|
|
18
|
+
the normalized scene's `sourceFormat`. Dense element arrays have no canonical
|
|
19
|
+
source metadata and therefore use `circuitjson`.
|
|
20
|
+
|
|
17
21
|
## Direct Controller Input
|
|
18
22
|
|
|
19
23
|
Pass any accepted CircuitJSON shape as the `documentModel`. The controller
|
|
@@ -403,13 +407,15 @@ expected capsule outline:
|
|
|
403
407
|
|
|
404
408
|
SMT pads, plated holes, and vias honor `is_covered_with_solder_mask` and
|
|
405
409
|
`covered_with_solder_mask` when present. `true` keeps the copper under solder
|
|
406
|
-
mask, while `false` exposes the copper on the applicable board side.
|
|
407
|
-
|
|
410
|
+
mask, while `false` exposes the copper on the applicable board side. Standard
|
|
411
|
+
vias also honor `is_tented`; omitted via tenting defaults to covered on both
|
|
412
|
+
sides, while `is_tented: false` retains an explicit opening.
|
|
408
413
|
|
|
409
414
|
Copper pours can use rectangular, polygon, or B-Rep geometry. Rectangular pours
|
|
410
415
|
support `rotation` or `ccw_rotation`; polygon and B-Rep point coordinates are
|
|
411
|
-
converted from millimeters to mils.
|
|
412
|
-
zone under solder mask, while
|
|
416
|
+
converted from millimeters to mils. Omitted or true
|
|
417
|
+
`covered_with_solder_mask` renders the zone under solder mask, while an
|
|
418
|
+
explicit false value exposes the copper:
|
|
413
419
|
|
|
414
420
|
```js
|
|
415
421
|
{
|
|
@@ -450,6 +456,11 @@ one track segment:
|
|
|
450
456
|
}
|
|
451
457
|
```
|
|
452
458
|
|
|
459
|
+
Trace-level or route-entry `covered_with_solder_mask` values control the
|
|
460
|
+
rendered opening. Omitted values default to covered; an explicit false value
|
|
461
|
+
remains exposed. A route-entry value takes precedence over its trace-level
|
|
462
|
+
value.
|
|
463
|
+
|
|
453
464
|
Route entries with `route_type: 'via'` produce via primitives when their
|
|
454
465
|
`from_layer`, `to_layer`, or `layer` touches the top or bottom surface. Adjacent
|
|
455
466
|
surface wire segments remain visible through those vias. Inner-only vias and
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 André Fiedler
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# PCB Scene3D Viewer 1.2.1
|
|
7
|
+
|
|
8
|
+
Version 1.2.1 removes the host-specific OCCT integration layer and consumes the
|
|
9
|
+
installed importer package directly.
|
|
10
|
+
|
|
11
|
+
## Compatibility changes
|
|
12
|
+
|
|
13
|
+
- Requires `@sunbox/occt-import-js ^0.0.28`.
|
|
14
|
+
- STEP workers now load from the scoped package path
|
|
15
|
+
`/node_modules/@sunbox/occt-import-js/dist/occt-import-js-worker.js`.
|
|
16
|
+
- The no-worker path dynamically imports the package ESM factory and resolves
|
|
17
|
+
its WASM through the same package directory.
|
|
18
|
+
- Classic script injection, global `occtimportjs` lookup, unscoped package
|
|
19
|
+
aliases, and app-vendored importer paths are no longer used.
|
|
20
|
+
- The package worker stays persistent across imports and failed model or worker
|
|
21
|
+
requests remain retryable through the existing cache/reset behavior.
|
|
22
|
+
- Byte-backed model inputs are copied into loader ownership before worker
|
|
23
|
+
transfer, so the caller's typed arrays and buffers are never detached.
|
|
24
|
+
- Rejected no-worker ESM initialization attempts are evicted, allowing a later
|
|
25
|
+
load to recover from transient module or WASM delivery failures.
|
|
26
|
+
|
|
27
|
+
Hosts serving browser dependencies must expose the installed package `dist/`
|
|
28
|
+
directory byte-for-byte at
|
|
29
|
+
`/node_modules/@sunbox/occt-import-js/dist/`. No copied JavaScript, WASM, or
|
|
30
|
+
custom worker is required.
|
|
31
|
+
|
|
32
|
+
## Performance
|
|
33
|
+
|
|
34
|
+
The viewer consumes the optimized 0.0.28 importer build and continues to retain
|
|
35
|
+
typed mesh arrays, compact face-color runs, persistent worker reuse, and parsed
|
|
36
|
+
model caching. This avoids script duplication and keeps large STEP payloads off
|
|
37
|
+
the main browser thread when workers are available.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 André Fiedler
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# PCB Scene3D Viewer 1.2.2
|
|
7
|
+
|
|
8
|
+
Version 1.2.2 preserves canonical source identity and aligns solder-mask
|
|
9
|
+
coverage with the converged CircuitJSON contract.
|
|
10
|
+
|
|
11
|
+
## API and behavior changes
|
|
12
|
+
|
|
13
|
+
- Canonical document and prepared-context inputs now retain their exact
|
|
14
|
+
`source.format` as the rendered scene's `sourceFormat`. Dense CircuitJSON
|
|
15
|
+
arrays continue to use the `circuitjson` fallback because they carry no
|
|
16
|
+
canonical source metadata.
|
|
17
|
+
- Routed traces and copper pours now default omitted
|
|
18
|
+
`covered_with_solder_mask` values to covered. Explicit false values remain
|
|
19
|
+
exposed through the solder mask.
|
|
20
|
+
- Standard vias now honor canonical `is_tented` metadata. Omitted values
|
|
21
|
+
default to tented, while `is_tented: false` remains exposed.
|
|
22
|
+
- Covered canonical copper follows the existing solder-mask material palette,
|
|
23
|
+
including tracks, pours, and via annuli.
|
|
24
|
+
|
|
25
|
+
Consumers that used the generic `circuitjson` marker for canonical documents
|
|
26
|
+
must now handle the retained source identity such as `gerber`, `altium`, or
|
|
27
|
+
`kicad`.
|
|
28
|
+
|
|
29
|
+
## Dependencies and validation
|
|
30
|
+
|
|
31
|
+
- Requires `circuitjson-toolkit ^1.1.2` and Node.js 20 or newer.
|
|
32
|
+
- The full test suite and both owned performance benchmarks cover the canonical
|
|
33
|
+
source, coverage, material, exact-geometry, and prepared-context paths.
|
package/docs/testing.md
CHANGED
|
@@ -12,6 +12,7 @@ The test suite covers:
|
|
|
12
12
|
copper, and silkscreen;
|
|
13
13
|
- runtime camera, preset, resizing, selection, and visibility behavior;
|
|
14
14
|
- external STEP/STP, WRL/VRML, STL, OBJ, GLTF/GLB, and 3MF live loading;
|
|
15
|
+
- direct ESM and package-owned worker loading for the installed OCCT importer;
|
|
15
16
|
- model ZIP archive export;
|
|
16
17
|
- optional shell renderer and CSS contract;
|
|
17
18
|
- worker-client request routing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pcb-scene3d-viewer",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Reusable Three.js PCB 3D scene viewer for normalized ECAD and CircuitJSON scene descriptions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pcb",
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
"docs/api.md",
|
|
35
35
|
"docs/circuitjson.md",
|
|
36
36
|
"docs/release-notes-v1.2.0.md",
|
|
37
|
+
"docs/release-notes-v1.2.1.md",
|
|
38
|
+
"docs/release-notes-v1.2.2.md",
|
|
37
39
|
"docs/model-format.md",
|
|
38
40
|
"docs/testing.md",
|
|
39
41
|
"spec",
|
|
@@ -54,8 +56,8 @@
|
|
|
54
56
|
"check:format": "prettier --check ."
|
|
55
57
|
},
|
|
56
58
|
"dependencies": {
|
|
57
|
-
"@sunbox/occt-import-js": "^0.0.
|
|
58
|
-
"circuitjson-toolkit": "^1.1.
|
|
59
|
+
"@sunbox/occt-import-js": "^0.0.28",
|
|
60
|
+
"circuitjson-toolkit": "^1.1.2",
|
|
59
61
|
"earcut": "3.0.2",
|
|
60
62
|
"fflate": "^0.8.2",
|
|
61
63
|
"polygon-clipping": "^0.15.7",
|
|
@@ -257,7 +257,12 @@ export class PcbScene3dCircuitJsonCopperPourBuilder {
|
|
|
257
257
|
*/
|
|
258
258
|
static #isCoveredWithMask(pour) {
|
|
259
259
|
const value = pour?.covered_with_solder_mask
|
|
260
|
-
|
|
260
|
+
if (typeof value === 'boolean') return value
|
|
261
|
+
if (value === undefined || value === null || value === '') return true
|
|
262
|
+
|
|
263
|
+
const text = String(value).trim().toLowerCase()
|
|
264
|
+
if (text === 'false') return false
|
|
265
|
+
return true
|
|
261
266
|
}
|
|
262
267
|
|
|
263
268
|
/**
|
|
@@ -85,7 +85,11 @@ export class PcbScene3dCircuitJsonTraceRouteBuilder {
|
|
|
85
85
|
0.1524
|
|
86
86
|
),
|
|
87
87
|
layerId: PcbScene3dCircuitJsonLayer.layerId(side),
|
|
88
|
-
solderMaskOpening:
|
|
88
|
+
solderMaskOpening:
|
|
89
|
+
PcbScene3dCircuitJsonTraceRouteBuilder.#solderMaskOpening(
|
|
90
|
+
entry,
|
|
91
|
+
trace
|
|
92
|
+
)
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
|
|
@@ -199,7 +203,12 @@ export class PcbScene3dCircuitJsonTraceRouteBuilder {
|
|
|
199
203
|
0.1524
|
|
200
204
|
),
|
|
201
205
|
layerId: PcbScene3dCircuitJsonLayer.layerId(side),
|
|
202
|
-
solderMaskOpening:
|
|
206
|
+
solderMaskOpening:
|
|
207
|
+
PcbScene3dCircuitJsonTraceRouteBuilder.#solderMaskOpening(
|
|
208
|
+
start,
|
|
209
|
+
end,
|
|
210
|
+
trace
|
|
211
|
+
)
|
|
203
212
|
}
|
|
204
213
|
}
|
|
205
214
|
|
|
@@ -303,19 +312,50 @@ export class PcbScene3dCircuitJsonTraceRouteBuilder {
|
|
|
303
312
|
* @returns {boolean}
|
|
304
313
|
*/
|
|
305
314
|
static #isSolderMaskCovered(via) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (typeof value === 'boolean') {
|
|
309
|
-
return value
|
|
315
|
+
if (typeof via?.is_tented === 'boolean') {
|
|
316
|
+
return via.is_tented
|
|
310
317
|
}
|
|
311
|
-
|
|
312
318
|
return (
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
319
|
+
PcbScene3dCircuitJsonTraceRouteBuilder.#solderMaskCoveredValue(
|
|
320
|
+
via
|
|
321
|
+
) ?? true
|
|
316
322
|
)
|
|
317
323
|
}
|
|
318
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Resolves whether trace-like copper has an explicitly authored opening.
|
|
327
|
+
* @param {...object} elements Route entries followed by their trace.
|
|
328
|
+
* @returns {boolean}
|
|
329
|
+
*/
|
|
330
|
+
static #solderMaskOpening(...elements) {
|
|
331
|
+
for (const element of elements) {
|
|
332
|
+
const covered =
|
|
333
|
+
PcbScene3dCircuitJsonTraceRouteBuilder.#solderMaskCoveredValue(
|
|
334
|
+
element
|
|
335
|
+
)
|
|
336
|
+
if (covered !== null) return !covered
|
|
337
|
+
}
|
|
338
|
+
return false
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Reads one explicit solder-mask coverage value.
|
|
343
|
+
* @param {object} element CircuitJSON copper element.
|
|
344
|
+
* @returns {boolean | null}
|
|
345
|
+
*/
|
|
346
|
+
static #solderMaskCoveredValue(element) {
|
|
347
|
+
const value =
|
|
348
|
+
element?.is_covered_with_solder_mask ??
|
|
349
|
+
element?.covered_with_solder_mask
|
|
350
|
+
if (typeof value === 'boolean') return value
|
|
351
|
+
if (value === undefined || value === null || value === '') return null
|
|
352
|
+
|
|
353
|
+
const text = String(value).trim().toLowerCase()
|
|
354
|
+
if (text === 'true') return true
|
|
355
|
+
if (text === 'false') return false
|
|
356
|
+
return null
|
|
357
|
+
}
|
|
358
|
+
|
|
319
359
|
/**
|
|
320
360
|
* Converts a route point from millimeters to mils.
|
|
321
361
|
* @param {object | undefined} point Source point.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads the ESM-shaped OCCT browser package without relying on global scripts.
|
|
3
|
+
*/
|
|
4
|
+
export class PcbScene3dOcctImporterLoader {
|
|
5
|
+
/** @type {Map<string, Promise<Record<string, any>>>} */
|
|
6
|
+
static #cachedImports = new Map()
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Imports and instantiates the OCCT module from its installed package.
|
|
10
|
+
* @param {{ resolveAssetUrl: (fileName: string) => string, loadModule?: (url: string) => Promise<Record<string, any>> }} options Loader options.
|
|
11
|
+
* @returns {Promise<Record<string, any>>} Initialized OCCT importer.
|
|
12
|
+
*/
|
|
13
|
+
static async load(options) {
|
|
14
|
+
const resolveAssetUrl = options?.resolveAssetUrl
|
|
15
|
+
if (typeof resolveAssetUrl !== 'function') {
|
|
16
|
+
throw new TypeError('OCCT asset URL resolver is required.')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const loadModule =
|
|
20
|
+
options?.loadModule ||
|
|
21
|
+
((url) => PcbScene3dOcctImporterLoader.#importModule(url))
|
|
22
|
+
const module = await loadModule(resolveAssetUrl('occt-import-js.js'))
|
|
23
|
+
const factory = module?.default || module?.occtimportjs
|
|
24
|
+
if (typeof factory !== 'function') {
|
|
25
|
+
throw new Error('occt-import-js did not export a factory.')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return await factory({
|
|
29
|
+
locateFile: (fileName) => resolveAssetUrl(fileName)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Loads one importer per resolved module URL and evicts rejected attempts.
|
|
35
|
+
* @param {{ resolveAssetUrl: (fileName: string) => string, loadModule?: (url: string) => Promise<Record<string, any>> }} options Loader options.
|
|
36
|
+
* @returns {Promise<Record<string, any>>} Cached initialized OCCT importer.
|
|
37
|
+
*/
|
|
38
|
+
static async loadCached(options) {
|
|
39
|
+
const resolveAssetUrl = options?.resolveAssetUrl
|
|
40
|
+
if (typeof resolveAssetUrl !== 'function') {
|
|
41
|
+
throw new TypeError('OCCT asset URL resolver is required.')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const moduleUrl = resolveAssetUrl('occt-import-js.js')
|
|
45
|
+
let pendingImport =
|
|
46
|
+
PcbScene3dOcctImporterLoader.#cachedImports.get(moduleUrl)
|
|
47
|
+
if (!pendingImport) {
|
|
48
|
+
pendingImport = PcbScene3dOcctImporterLoader.load(options)
|
|
49
|
+
PcbScene3dOcctImporterLoader.#cachedImports.set(
|
|
50
|
+
moduleUrl,
|
|
51
|
+
pendingImport
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
return await pendingImport
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (
|
|
59
|
+
PcbScene3dOcctImporterLoader.#cachedImports.get(moduleUrl) ===
|
|
60
|
+
pendingImport
|
|
61
|
+
) {
|
|
62
|
+
PcbScene3dOcctImporterLoader.#cachedImports.delete(moduleUrl)
|
|
63
|
+
}
|
|
64
|
+
throw error
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Dynamically imports one browser module URL.
|
|
70
|
+
* @param {string} url Module URL.
|
|
71
|
+
* @returns {Promise<Record<string, any>>} Imported namespace.
|
|
72
|
+
*/
|
|
73
|
+
static async #importModule(url) {
|
|
74
|
+
return await import(url)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Object.freeze(PcbScene3dOcctImporterLoader.prototype)
|
|
79
|
+
Object.freeze(PcbScene3dOcctImporterLoader)
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import { PcbScene3dModelIdentity } from './PcbScene3dModelIdentity.mjs'
|
|
2
|
+
import { PcbScene3dOcctImporterLoader } from './PcbScene3dOcctImporterLoader.mjs'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Browser-side STEP mesh loader backed by occt-import-js.
|
|
5
6
|
*/
|
|
6
7
|
export class PcbScene3dStepLoader {
|
|
7
|
-
/** @type {(() => Promise<{ ReadStepFile?: (content: Uint8Array, params: Record<string, any> | null) => any }>) | null} */
|
|
8
|
-
static #browserImporterLoader
|
|
9
|
-
|
|
10
|
-
/** @type {Promise<{ ReadStepFile?: (content: Uint8Array, params: Record<string, any> | null) => any }> | null} */
|
|
11
|
-
static #browserImporterPromise
|
|
12
|
-
|
|
13
|
-
/** @type {Promise<void> | null} */
|
|
14
|
-
static #browserScriptPromise
|
|
15
|
-
|
|
16
8
|
/** @type {() => Promise<{ ReadStepFile?: (content: Uint8Array, params: Record<string, any> | null) => any }>} */
|
|
17
9
|
#importerLoader
|
|
18
10
|
|
|
@@ -182,7 +174,7 @@ export class PcbScene3dStepLoader {
|
|
|
182
174
|
|
|
183
175
|
/**
|
|
184
176
|
* Reads one importer result through the loader-owned persistent worker.
|
|
185
|
-
* Requests are serialized because the
|
|
177
|
+
* Requests are serialized because the package worker does not echo a
|
|
186
178
|
* request id back in its response payload.
|
|
187
179
|
* @param {Uint8Array} content
|
|
188
180
|
* @returns {Promise<any>}
|
|
@@ -347,20 +339,20 @@ export class PcbScene3dStepLoader {
|
|
|
347
339
|
return null
|
|
348
340
|
}
|
|
349
341
|
if (value instanceof Uint8Array) {
|
|
350
|
-
return value
|
|
342
|
+
return new Uint8Array(value)
|
|
351
343
|
}
|
|
352
344
|
if (value instanceof ArrayBuffer) {
|
|
353
|
-
return new Uint8Array(value)
|
|
345
|
+
return new Uint8Array(value.slice(0))
|
|
354
346
|
}
|
|
355
347
|
if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
|
|
356
348
|
return new Uint8Array(
|
|
357
349
|
value.buffer,
|
|
358
350
|
value.byteOffset,
|
|
359
351
|
value.byteLength
|
|
360
|
-
)
|
|
352
|
+
).slice()
|
|
361
353
|
}
|
|
362
354
|
if (typeof value.arrayBuffer === 'function') {
|
|
363
|
-
return new Uint8Array(await value.arrayBuffer())
|
|
355
|
+
return new Uint8Array(await value.arrayBuffer()).slice()
|
|
364
356
|
}
|
|
365
357
|
return null
|
|
366
358
|
}
|
|
@@ -599,39 +591,10 @@ export class PcbScene3dStepLoader {
|
|
|
599
591
|
* @returns {Promise<{ ReadStepFile?: (content: Uint8Array, params: Record<string, any> | null) => any }>}
|
|
600
592
|
*/
|
|
601
593
|
static async #loadBrowserImporter() {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
typeof document === 'undefined'
|
|
607
|
-
) {
|
|
608
|
-
throw new Error(
|
|
609
|
-
'Browser STEP importer requires window and document.'
|
|
610
|
-
)
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
await PcbScene3dStepLoader.#ensureBrowserScript()
|
|
614
|
-
|
|
615
|
-
const factory = globalThis.occtimportjs
|
|
616
|
-
if (typeof factory !== 'function') {
|
|
617
|
-
throw new Error(
|
|
618
|
-
'occt-import-js did not register a browser factory.'
|
|
619
|
-
)
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
return await factory({
|
|
623
|
-
locateFile: (fileName) =>
|
|
624
|
-
PcbScene3dStepLoader.#resolveVendorAssetUrl(fileName)
|
|
625
|
-
})
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
if (!PcbScene3dStepLoader.#browserImporterPromise) {
|
|
630
|
-
PcbScene3dStepLoader.#browserImporterPromise =
|
|
631
|
-
PcbScene3dStepLoader.#browserImporterLoader()
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
return await PcbScene3dStepLoader.#browserImporterPromise
|
|
594
|
+
return await PcbScene3dOcctImporterLoader.loadCached({
|
|
595
|
+
resolveAssetUrl: (fileName) =>
|
|
596
|
+
PcbScene3dStepLoader.#resolveImporterAssetUrl(fileName)
|
|
597
|
+
})
|
|
635
598
|
}
|
|
636
599
|
|
|
637
600
|
/**
|
|
@@ -645,77 +608,24 @@ export class PcbScene3dStepLoader {
|
|
|
645
608
|
|
|
646
609
|
return () =>
|
|
647
610
|
new globalThis.Worker(
|
|
648
|
-
PcbScene3dStepLoader.#
|
|
611
|
+
PcbScene3dStepLoader.#resolveImporterAssetUrl(
|
|
649
612
|
'occt-import-js-worker.js'
|
|
650
613
|
)
|
|
651
614
|
)
|
|
652
615
|
}
|
|
653
616
|
|
|
654
617
|
/**
|
|
655
|
-
*
|
|
656
|
-
* @returns {Promise<void>}
|
|
657
|
-
*/
|
|
658
|
-
static async #ensureBrowserScript() {
|
|
659
|
-
if (PcbScene3dStepLoader.#browserScriptPromise) {
|
|
660
|
-
return await PcbScene3dStepLoader.#browserScriptPromise
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
PcbScene3dStepLoader.#browserScriptPromise = new Promise(
|
|
664
|
-
(resolve, reject) => {
|
|
665
|
-
const existingScript = document.querySelector(
|
|
666
|
-
'script[data-occt-import-js]'
|
|
667
|
-
)
|
|
668
|
-
if (existingScript) {
|
|
669
|
-
existingScript.addEventListener('load', () => resolve(), {
|
|
670
|
-
once: true
|
|
671
|
-
})
|
|
672
|
-
existingScript.addEventListener(
|
|
673
|
-
'error',
|
|
674
|
-
() =>
|
|
675
|
-
reject(
|
|
676
|
-
new Error(
|
|
677
|
-
'STEP importer script failed to load.'
|
|
678
|
-
)
|
|
679
|
-
),
|
|
680
|
-
{ once: true }
|
|
681
|
-
)
|
|
682
|
-
if (typeof globalThis.occtimportjs === 'function') {
|
|
683
|
-
resolve()
|
|
684
|
-
}
|
|
685
|
-
return
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const script = document.createElement('script')
|
|
689
|
-
script.async = true
|
|
690
|
-
script.dataset.occtImportJs = 'true'
|
|
691
|
-
script.src =
|
|
692
|
-
PcbScene3dStepLoader.#resolveVendorAssetUrl(
|
|
693
|
-
'occt-import-js.js'
|
|
694
|
-
)
|
|
695
|
-
script.addEventListener('load', () => resolve(), { once: true })
|
|
696
|
-
script.addEventListener(
|
|
697
|
-
'error',
|
|
698
|
-
() =>
|
|
699
|
-
reject(
|
|
700
|
-
new Error('STEP importer script failed to load.')
|
|
701
|
-
),
|
|
702
|
-
{ once: true }
|
|
703
|
-
)
|
|
704
|
-
document.head.append(script)
|
|
705
|
-
}
|
|
706
|
-
)
|
|
707
|
-
|
|
708
|
-
return await PcbScene3dStepLoader.#browserScriptPromise
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* Resolves one vendored importer asset URL with the current app version.
|
|
618
|
+
* Resolves one installed importer asset URL with the current app version.
|
|
713
619
|
* @param {string} fileName
|
|
714
620
|
* @returns {string}
|
|
715
621
|
*/
|
|
716
|
-
static #
|
|
622
|
+
static #resolveImporterAssetUrl(fileName) {
|
|
717
623
|
const versionKey = new URL(import.meta.url).searchParams.get('v') || ''
|
|
718
624
|
const suffix = versionKey ? '?v=' + encodeURIComponent(versionKey) : ''
|
|
719
|
-
return
|
|
625
|
+
return (
|
|
626
|
+
'/node_modules/@sunbox/occt-import-js/dist/' +
|
|
627
|
+
String(fileName || '') +
|
|
628
|
+
suffix
|
|
629
|
+
)
|
|
720
630
|
}
|
|
721
631
|
}
|