@faicad/faijs-viewer 0.29.3
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.i18n.yaml +6 -0
- package/README.md +102 -0
- package/README.zh.md +102 -0
- package/dist/engine.d.ts +50 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +78 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/libs.d.ts +42 -0
- package/dist/libs.d.ts.map +1 -0
- package/dist/libs.js +192 -0
- package/dist/libs.js.map +1 -0
- package/dist/open-fai-zip.d.ts +29 -0
- package/dist/open-fai-zip.d.ts.map +1 -0
- package/dist/open-fai-zip.js +239 -0
- package/dist/open-fai-zip.js.map +1 -0
- package/dist/types.d.ts +141 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/package.json +59 -0
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/faijs-viewer/README.md
|
|
5
|
+
README.md: 3ccc96a92a8c48c3d9fabc6e709166dcada26b73
|
|
6
|
+
README.zh.md: d35e4fd11c3776230282e2e3900e1a56ba923b18
|
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @faicad/faijs-viewer
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
The reference way for **any third-party host** to open and render a project `.fai.zip` 3D document. The viewer reads the container, executes the active (or requested) model through `@faicad/faijs`, and returns host-agnostic tessellated mesh data — no THREE, no DOM, and no GL library required on the return value.
|
|
6
|
+
|
|
7
|
+
This package does **not** depend on `sheetmetal` or `cq-compat`. Its preset libraries — `@faicad/faijs`, `@faicad/faijs-sketch` and `@faicad/faijs-extra` — are family peers the host provides, and third-party faijs libraries (e.g. `@faicad/faijs-gears`) are loaded on demand at execution time (see [Dynamic library loading](#dynamic-library-loading)). It is the reference SDK for "a third party needs only one **viewer** package to view a `.fai.zip`".
|
|
8
|
+
|
|
9
|
+
## v1 API
|
|
10
|
+
|
|
11
|
+
```ts ignore-check
|
|
12
|
+
import { openFaiZip } from '@faicad/faijs-viewer'
|
|
13
|
+
|
|
14
|
+
const result = await openFaiZip(bytes, {
|
|
15
|
+
wasm: {
|
|
16
|
+
occtUrl: 'https://your-cdn/occt-wasm.wasm', // BREP-chain engine
|
|
17
|
+
manifoldUrl: 'https://your-cdn/manifold.wasm', // mesh / CSG engine
|
|
18
|
+
brepkitUrl: 'https://your-cdn/brepkit_wasm_bg.wasm', // secondary BREP engine
|
|
19
|
+
},
|
|
20
|
+
// modelId?: string // pick a model; default is manifest.active, else models[0]
|
|
21
|
+
// mode?: 'auto' | 'brep' | 'mesh' // default 'auto' (static BREP/mesh dispatch)
|
|
22
|
+
// sketch?: { planegcsUrl: '...' } // browser-only: constraint-solver wasm URL (see below)
|
|
23
|
+
// libs?: { allow: ['@faicad/faijs-gears'], versions: { '@faicad/faijs-gears': '0.29.2' } }
|
|
24
|
+
// // dynamic third-party libraries (see below)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// result.meshes — structured mesh data; the host does the actual rendering.
|
|
28
|
+
for (const mesh of result.meshes) {
|
|
29
|
+
// mesh.name display label
|
|
30
|
+
// mesh.positions Float32Array, interleaved x/y/z triangle vertices
|
|
31
|
+
// mesh.indices Uint32Array, groups of 3 triangle indices
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Three outcomes:
|
|
36
|
+
|
|
37
|
+
| case | form |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `wasm` missing or any of the three urls empty | **throws** (`E_WASM_URL`; a caller contract violation, never returned as `error`) |
|
|
40
|
+
| invalid bytes / execution failure / no visible geometry | returns `result.error`, `code ∈ { E_CONTAINER, E_EXECUTION, E_NO_GEOMETRY }` |
|
|
41
|
+
| success | returns structured `meshes` |
|
|
42
|
+
|
|
43
|
+
Real FreeCAD-converted containers call `cad.sketch` and the editor-extension ops (`cad.fai_*`, `cad.group`, `cad.text`, …); the viewer merges the sketch + faijs-extra namespaces into the default `cad` binding and backs sketch with a constraint solver (see below). `@faicad/faijs-draw` is deprecated and deliberately not merged — a `cad.draw` call fails as an unknown callee.
|
|
44
|
+
|
|
45
|
+
## `cad.sketch` and the constraint solver
|
|
46
|
+
|
|
47
|
+
`cad.sketch` (from `@faicad/faijs-sketch`) requires a separate **planegcs** constraint-solver wasm. How it is sourced differs by runner:
|
|
48
|
+
|
|
49
|
+
- **Node / worker** — the solver auto-loads from the installed `@salusoft89/planegcs` package; no `sketch` option is needed.
|
|
50
|
+
- **Browser** — self-host `planegcs.wasm` and pass its URL: `openFaiZip(bytes, { wasm, sketch: { planegcsUrl: 'https://your-cdn/planegcs.wasm' } })`. Without a URL the solver is not installed; a `cad.sketch` op fails with `E_SKETCHC_NO_SOLVER`, reported through `result.error`.
|
|
51
|
+
|
|
52
|
+
Note: the converted sketch input must follow the sketch op's contract (`shapes` or `geoms`). A converter that emits `cad.sketch({ contours: [...] })` is not currently accepted and fails with `E_SKETCHC_NO_GEOMS` — tracked in the faijs family, not this viewer package.
|
|
53
|
+
|
|
54
|
+
## Dynamic library loading
|
|
55
|
+
|
|
56
|
+
A `.fai.zip` model may `import` any third-party faijs library — the set cannot be known in advance. The engine already loads namespace imports lazily at execute time (`autoLoadLibsFromImports` → `HostPorts.libLoader`), and this viewer builds that loader for you:
|
|
57
|
+
|
|
58
|
+
- **browser host** — dynamic `import()` from the jsDelivr CDN (version-pinned `+esm` direct links when `libs.versions` is provided);
|
|
59
|
+
- **Node host** — `import(pkg)` from installed packages, defaulting to `@faicad/`-scoped libraries unless `libs.allow` is given.
|
|
60
|
+
|
|
61
|
+
Configure it through `OpenFaiZipOptions.libs`:
|
|
62
|
+
|
|
63
|
+
```ts ignore-check
|
|
64
|
+
const result = await openFaiZip(bytes, {
|
|
65
|
+
wasm,
|
|
66
|
+
libs: {
|
|
67
|
+
allow: ['@faicad/faijs-gears'], // whitelist (a .fai.zip is untrusted input — production hosts should set this)
|
|
68
|
+
versions: { '@faicad/faijs-gears': '0.29.2' }, // pin to the host engine's version line
|
|
69
|
+
// aliases?: { gears: '@faicad/faijs-gears' }, // script specifier → npm package name
|
|
70
|
+
// cdnBase?: 'https://cdn.jsdelivr.net/npm/', // browser CDN base
|
|
71
|
+
// enabled?: true, // false disables dynamic loading entirely
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Preset libraries (core, sketch, faijs-extra — merged into `cad`) never pass through the loader. Loading or version-verification failures surface as structured `E_EXECUTION` on `result.error`, never a throw. A library whose `contractVersion` does not match the host engine is rejected loudly — keep `libs.versions` pinned to the same version line as `@faicad/faijs` the host runs.
|
|
77
|
+
|
|
78
|
+
## All three wasm urls are required, and they must be self-hosted
|
|
79
|
+
|
|
80
|
+
A `.fai.zip` has **no baked mesh** — models execute at runtime, which requires the engine wasm. v1 does not bundle them; the host provides and self-hosts:
|
|
81
|
+
|
|
82
|
+
- **occt-wasm** — BREP-chain engine (default-required).
|
|
83
|
+
- **manifold** — mesh/CSG engine (also used for the tessellation of BREP output).
|
|
84
|
+
- **brepkit** — secondary BREP engine (v1 accepts + validates it; reserved).
|
|
85
|
+
|
|
86
|
+
Self-host the three files (do not rely on an upstream CDN's uptime) and pass their urls to `openFaiZip`:
|
|
87
|
+
|
|
88
|
+
1. Copy `dist/occt-wasm.wasm` from the `occt-wasm` npm package into your static directory.
|
|
89
|
+
2. Copy `manifold.wasm` (package root) from `manifold-3d` into your static directory.
|
|
90
|
+
3. Copy `lib/brepkit_wasm_bg.wasm` from `brepkit-wasm` into your static directory.
|
|
91
|
+
|
|
92
|
+
For a concrete browser binding, see `packages/demo/main.ts#initOcct`, which registers `OcctKernel.init({ wasm: occtUrl })` on core's `setOcctWasmInitFn`; this viewer package installs the three urls into core's `setOcctWasmInitFn` / `setManifoldWasmUrl` hooks for you in a browser.
|
|
93
|
+
|
|
94
|
+
## Node / tests
|
|
95
|
+
|
|
96
|
+
In a Node/worker runner the three urls are still validated (v1 contract), but the engine falls back to core's locally-bundled occt/manifold auto-load — no network fetch. So a host without public internet can still fully run `openFaiZip` in a test process.
|
|
97
|
+
|
|
98
|
+
## Dependencies
|
|
99
|
+
|
|
100
|
+
- preset peers `@faicad/faijs`, `@faicad/faijs-sketch`, `@faicad/faijs-extra` — merged into the default `cad` binding (a host provides all three family packages).
|
|
101
|
+
- peer `three` — carried by faijs-extra's B-group ops (`cad.text` / `cad.svgExtrude` mesh path); not touched by the return value.
|
|
102
|
+
- peer `occt-wasm` — lazily imported only by the browser path (`bindBrowserWasm`); never loaded in Node tests.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @faicad/faijs-viewer
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
任何第三方宿主打开并渲染 `.fai.zip` 三维文档的参考方式。viewer 读取容器,通过 `@faicad/faijs` 执行 active(或指定的)model,返回与宿主无关的三角化 mesh 数据——返回值不依赖 THREE、DOM 或任何 GL 库。
|
|
6
|
+
|
|
7
|
+
本包**不**依赖 `sheetmetal` 或 `cq-compat`。预置库——`@faicad/faijs`、`@faicad/faijs-sketch` 和 `@faicad/faijs-extra`——是家族 peer 包,由宿主提供;第三方 faijs 库(如 `@faicad/faijs-gears`)在执行期按需装载(见 [动态库加载](#dynamic-library-loading))。它就是"第三方只需要一个 **viewer** 包就能查看 `.fai.zip`"的参考 SDK。
|
|
8
|
+
|
|
9
|
+
## v1 API
|
|
10
|
+
|
|
11
|
+
```ts ignore-check
|
|
12
|
+
import { openFaiZip } from '@faicad/faijs-viewer'
|
|
13
|
+
|
|
14
|
+
const result = await openFaiZip(bytes, {
|
|
15
|
+
wasm: {
|
|
16
|
+
occtUrl: 'https://your-cdn/occt-wasm.wasm', // BREP-chain engine
|
|
17
|
+
manifoldUrl: 'https://your-cdn/manifold.wasm', // mesh / CSG engine
|
|
18
|
+
brepkitUrl: 'https://your-cdn/brepkit_wasm_bg.wasm', // secondary BREP engine
|
|
19
|
+
},
|
|
20
|
+
// modelId?: string // pick a model; default is manifest.active, else models[0]
|
|
21
|
+
// mode?: 'auto' | 'brep' | 'mesh' // default 'auto' (static BREP/mesh dispatch)
|
|
22
|
+
// sketch?: { planegcsUrl: '...' } // browser-only: constraint-solver wasm URL (see below)
|
|
23
|
+
// libs?: { allow: ['@faicad/faijs-gears'], versions: { '@faicad/faijs-gears': '0.29.2' } }
|
|
24
|
+
// // dynamic third-party libraries (see below)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// result.meshes — structured mesh data; the host does the actual rendering.
|
|
28
|
+
for (const mesh of result.meshes) {
|
|
29
|
+
// mesh.name display label
|
|
30
|
+
// mesh.positions Float32Array, interleaved x/y/z triangle vertices
|
|
31
|
+
// mesh.indices Uint32Array, groups of 3 triangle indices
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
三种结果:
|
|
36
|
+
|
|
37
|
+
| 情形 | 形式 |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `wasm` 缺失或三个 url 任一为空 | **抛异常**(`E_WASM_URL`;调用方契约违规,绝不作为 `error` 返回) |
|
|
40
|
+
| 字节无效 / 执行失败 / 无可见几何 | 返回 `result.error`,`code ∈ { E_CONTAINER, E_EXECUTION, E_NO_GEOMETRY }` |
|
|
41
|
+
| 成功 | 返回结构化 `meshes` |
|
|
42
|
+
|
|
43
|
+
真实 FreeCAD 转换产物会调用 `cad.sketch` 和编辑器扩展 op(`cad.fai_*`、`cad.group`、`cad.text`、…);viewer 把 sketch + faijs-extra 命名空间合入默认 `cad` binding,并由约束求解器支撑 sketch(见下)。`@faicad/faijs-draw` 已弃用且刻意不合并——`cad.draw` 调用按未知 callee 失败。
|
|
44
|
+
|
|
45
|
+
## `cad.sketch` 与约束求解器
|
|
46
|
+
|
|
47
|
+
`cad.sketch`(来自 `@faicad/faijs-sketch`)需要单独的 **planegcs** 约束求解器 wasm。获取方式因运行环境而异:
|
|
48
|
+
|
|
49
|
+
- **Node / worker** — 求解器自动从已安装的 `@salusoft89/planegcs` 包加载;不需要 `sketch` 选项。
|
|
50
|
+
- **浏览器** — 自托管 `planegcs.wasm` 并传入 URL:`openFaiZip(bytes, { wasm, sketch: { planegcsUrl: 'https://your-cdn/planegcs.wasm' } })`。不给 URL 则不安装求解器;`cad.sketch` op 以 `E_SKETCHC_NO_SOLVER` 失败,经 `result.error` 上报。
|
|
51
|
+
|
|
52
|
+
注意:转换产物的 sketch 输入必须符合 sketch op 的契约(`shapes` 或 `geoms`)。产出 `cad.sketch({ contours: [...] })` 的转换器当前不被接受,会以 `E_SKETCHC_NO_GEOMS` 失败——该问题在 faijs 家族跟踪,不属本 viewer 包。
|
|
53
|
+
|
|
54
|
+
## <a id="dynamic-library-loading"></a>动态库加载
|
|
55
|
+
|
|
56
|
+
`.fai.zip` 的 model 可能 `import` 任意第三方 faijs 库——这个集合无法预先知道。引擎本来就在执行期按需装载命名空间 import(`autoLoadLibsFromImports` → `HostPorts.libLoader`),本 viewer 替你构建这个 loader:
|
|
57
|
+
|
|
58
|
+
- **浏览器宿主** — 从 jsDelivr CDN 动态 `import()`(给了 `libs.versions` 则走版本 pin 的 `+esm` 直链);
|
|
59
|
+
- **Node 宿主** — 从已安装包 `import(pkg)`,默认限 `@faicad/` scoped 库,除非给了 `libs.allow`。
|
|
60
|
+
|
|
61
|
+
通过 `OpenFaiZipOptions.libs` 配置:
|
|
62
|
+
|
|
63
|
+
```ts ignore-check
|
|
64
|
+
const result = await openFaiZip(bytes, {
|
|
65
|
+
wasm,
|
|
66
|
+
libs: {
|
|
67
|
+
allow: ['@faicad/faijs-gears'], // whitelist (a .fai.zip is untrusted input — production hosts should set this)
|
|
68
|
+
versions: { '@faicad/faijs-gears': '0.29.2' }, // pin to the host engine's version line
|
|
69
|
+
// aliases?: { gears: '@faicad/faijs-gears' }, // script specifier → npm package name
|
|
70
|
+
// cdnBase?: 'https://cdn.jsdelivr.net/npm/', // browser CDN base
|
|
71
|
+
// enabled?: true, // false disables dynamic loading entirely
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
预置库(core、sketch、faijs-extra——合入 `cad`)从不经过 loader。装载或版本校验失败统一结构化返回 `E_EXECUTION` 到 `result.error`,绝不抛穿。`contractVersion` 与宿主引擎不匹配的库会被显式拒绝——请把 `libs.versions` pin 到与宿主运行的 `@faicad/faijs` 相同版本线。
|
|
77
|
+
|
|
78
|
+
## 三个 wasm url 全部必填,且必须自托管
|
|
79
|
+
|
|
80
|
+
`.fai.zip` **不内嵌 mesh** —— model 在运行时执行,这需要引擎 wasm。v1 不打包它们;由宿主提供并自托管:
|
|
81
|
+
|
|
82
|
+
- **occt-wasm** — BREP 链引擎(默认必选)。
|
|
83
|
+
- **manifold** — mesh/CSG 引擎(也用于 BREP 输出的三角化)。
|
|
84
|
+
- **brepkit** — 次级 BREP 引擎(v1 接受并校验;预留)。
|
|
85
|
+
|
|
86
|
+
自托管这三个文件(不要依赖上游 CDN 的可用性),并把 url 传给 `openFaiZip`:
|
|
87
|
+
|
|
88
|
+
1. 把 `occt-wasm` npm 包里的 `dist/occt-wasm.wasm` 拷进你的静态目录。
|
|
89
|
+
2. 把 `manifold-3d` 包根的 `manifold.wasm` 拷进你的静态目录。
|
|
90
|
+
3. 把 `brepkit-wasm` 的 `lib/brepkit_wasm_bg.wasm` 拷进你的静态目录。
|
|
91
|
+
|
|
92
|
+
具体浏览器绑定的示例见 `packages/demo/main.ts#initOcct`——它把 `OcctKernel.init({ wasm: occtUrl })` 注册到 core 的 `setOcctWasmInitFn`;浏览器环境下本 viewer 包会替你把三个 url 装进 core 的 `setOcctWasmInitFn` / `setManifoldWasmUrl` 钩子。
|
|
93
|
+
|
|
94
|
+
## Node / 测试
|
|
95
|
+
|
|
96
|
+
在 Node/worker 环境里,三个 url 仍会被校验(v1 契约),但引擎回退到 core 本地打包的 occt/manifold 自动加载——不发起网络请求。所以无公网的宿主也能在测试进程中完整运行 `openFaiZip`。
|
|
97
|
+
|
|
98
|
+
## 依赖
|
|
99
|
+
|
|
100
|
+
- 预置 peer `@faicad/faijs`、`@faicad/faijs-sketch`、`@faicad/faijs-extra` —— 合入默认 `cad` binding(三个家族包都由宿主提供)。
|
|
101
|
+
- peer `three` —— 由 faijs-extra 的 B 组 op(`cad.text` / `cad.svgExtrude` mesh 路径)携带;返回值不触碰它。
|
|
102
|
+
- peer `occt-wasm` —— 仅浏览器路径(`bindBrowserWasm`)懒加载;Node 测试从不加载。
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine (kernel) wiring for the viewer.
|
|
3
|
+
*
|
|
4
|
+
* `@faicad/faijs` executes a `.fai.zip` model through environment-level kernel
|
|
5
|
+
* singletons: the OCCT kernel (BREP chain), the manifold mesh engine, and the
|
|
6
|
+
* secondary brepkit engine. Those singletons are configured through the
|
|
7
|
+
* `setOcctWasmInitFn` / `setManifoldWasmUrl` / `setBrepkitWasmInitFn` hooks
|
|
8
|
+
* exported by core. This module maps the three wasm URLs from the v1 contract
|
|
9
|
+
* onto those hooks.
|
|
10
|
+
*
|
|
11
|
+
* Environment handling:
|
|
12
|
+
* - In a real browser the three URLs are authoritative: each kernel loads its
|
|
13
|
+
* wasm from the given URL.
|
|
14
|
+
* - In a Node.ts test / local runner the same three URLs are validated (v1
|
|
15
|
+
* requires them) but the engine falls back to core's node_modules auto-load,
|
|
16
|
+
* so tests run against real, local wasm without a network fetch.
|
|
17
|
+
*
|
|
18
|
+
* Dual-OCCT-copy note: `occt-wasm`'s opaque instance type is nominal per
|
|
19
|
+
* package install (a `#private` member), so a pristine cross-copy type cannot
|
|
20
|
+
* be expressed. The demo uses `OcctKernel.init({ wasm })` plus a cast to the
|
|
21
|
+
* faijs-expected return shape; the viewer mirrors that established pattern.
|
|
22
|
+
*/
|
|
23
|
+
/** A wasm URL triplet (same shape as the v1 contract). */
|
|
24
|
+
export interface ViewerWasmUrls {
|
|
25
|
+
occtUrl: string;
|
|
26
|
+
manifoldUrl: string;
|
|
27
|
+
brepkitUrl: string;
|
|
28
|
+
}
|
|
29
|
+
/** A code-pinned capability error surfaced by the viewer. */
|
|
30
|
+
export interface EngineError {
|
|
31
|
+
code: 'E_WASM_URL';
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validate the three v1 wasm urls. Throws a code-bearing `E_WASM_URL` error
|
|
36
|
+
* when any is missing or empty, as required by the v1 contract.
|
|
37
|
+
* @param wasm - The three v1 wasm asset URLs ({ occtUrl, manifoldUrl, brepkitUrl }) to validate. type:ViewerWasmUrls required:true
|
|
38
|
+
*/
|
|
39
|
+
export declare function assertWasmUrls(wasm: Readonly<ViewerWasmUrls> | undefined): void;
|
|
40
|
+
/**
|
|
41
|
+
* Install the engine kernels for an `openFaiZip` view. Safe to call
|
|
42
|
+
* repeatedly — core's kernel singletons are environment-wide and
|
|
43
|
+
* instance-neutral.
|
|
44
|
+
*
|
|
45
|
+
* In a browser the three URLs are registered; in a Node/worker runner the URLs
|
|
46
|
+
* are still validated and the Node engine auto-loads its own local wasm.
|
|
47
|
+
* @param wasm - The three v1 wasm asset URLs; validated here, registered in browsers. type:ViewerWasmUrls required:true
|
|
48
|
+
*/
|
|
49
|
+
export declare function installEngine(wasm: Readonly<ViewerWasmUrls>): Promise<void>;
|
|
50
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,6DAA6D;AAC7D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,YAAY,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;CAChB;AAQD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,SAAS,GAAG,IAAI,CAS/E;AAuBD;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKjF"}
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine (kernel) wiring for the viewer.
|
|
3
|
+
*
|
|
4
|
+
* `@faicad/faijs` executes a `.fai.zip` model through environment-level kernel
|
|
5
|
+
* singletons: the OCCT kernel (BREP chain), the manifold mesh engine, and the
|
|
6
|
+
* secondary brepkit engine. Those singletons are configured through the
|
|
7
|
+
* `setOcctWasmInitFn` / `setManifoldWasmUrl` / `setBrepkitWasmInitFn` hooks
|
|
8
|
+
* exported by core. This module maps the three wasm URLs from the v1 contract
|
|
9
|
+
* onto those hooks.
|
|
10
|
+
*
|
|
11
|
+
* Environment handling:
|
|
12
|
+
* - In a real browser the three URLs are authoritative: each kernel loads its
|
|
13
|
+
* wasm from the given URL.
|
|
14
|
+
* - In a Node.ts test / local runner the same three URLs are validated (v1
|
|
15
|
+
* requires them) but the engine falls back to core's node_modules auto-load,
|
|
16
|
+
* so tests run against real, local wasm without a network fetch.
|
|
17
|
+
*
|
|
18
|
+
* Dual-OCCT-copy note: `occt-wasm`'s opaque instance type is nominal per
|
|
19
|
+
* package install (a `#private` member), so a pristine cross-copy type cannot
|
|
20
|
+
* be expressed. The demo uses `OcctKernel.init({ wasm })` plus a cast to the
|
|
21
|
+
* faijs-expected return shape; the viewer mirrors that established pattern.
|
|
22
|
+
*/
|
|
23
|
+
import { setOcctWasmInitFn, setManifoldWasmUrl } from '@faicad/faijs/browser';
|
|
24
|
+
function throwEngine(message) {
|
|
25
|
+
const err = new Error(message);
|
|
26
|
+
err.code = 'E_WASM_URL';
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validate the three v1 wasm urls. Throws a code-bearing `E_WASM_URL` error
|
|
31
|
+
* when any is missing or empty, as required by the v1 contract.
|
|
32
|
+
* @param wasm - The three v1 wasm asset URLs ({ occtUrl, manifoldUrl, brepkitUrl }) to validate. type:ViewerWasmUrls required:true
|
|
33
|
+
*/
|
|
34
|
+
export function assertWasmUrls(wasm) {
|
|
35
|
+
if (!wasm) {
|
|
36
|
+
throwEngine('openFaiZip requires `wasm` — supply { occtUrl, manifoldUrl, brepkitUrl }.');
|
|
37
|
+
}
|
|
38
|
+
for (const key of ['occtUrl', 'manifoldUrl', 'brepkitUrl']) {
|
|
39
|
+
if (typeof wasm[key] !== 'string' || wasm[key].length === 0) {
|
|
40
|
+
throwEngine(`openFaiZip wasm.${key} must be a non-empty string.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function hasBrowserWindow() {
|
|
45
|
+
return typeof window !== 'undefined' && typeof window.addEventListener === 'function';
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Bind the engine kernels for a real browser, installing the occt and manifold
|
|
49
|
+
* init hooks from the configured URLs. `occt-wasm` is loaded lazily so this
|
|
50
|
+
* path only exists in the browser (never forces the dependency at load time in
|
|
51
|
+
* Node tests). brepkit is a secondary engine whose mesh boolean is not yet
|
|
52
|
+
* wired (v1, plan §1.8), so its URL is accepted but not depended on at
|
|
53
|
+
* execution time.
|
|
54
|
+
*/
|
|
55
|
+
async function bindBrowserWasm(wasm) {
|
|
56
|
+
const { OcctKernel } = await import('occt-wasm');
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
setOcctWasmInitFn((() => OcctKernel.init({ wasm: wasm.occtUrl })));
|
|
59
|
+
setManifoldWasmUrl(wasm.manifoldUrl);
|
|
60
|
+
// brepkit: accepted, not consumed in v1.
|
|
61
|
+
void wasm.brepkitUrl;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Install the engine kernels for an `openFaiZip` view. Safe to call
|
|
65
|
+
* repeatedly — core's kernel singletons are environment-wide and
|
|
66
|
+
* instance-neutral.
|
|
67
|
+
*
|
|
68
|
+
* In a browser the three URLs are registered; in a Node/worker runner the URLs
|
|
69
|
+
* are still validated and the Node engine auto-loads its own local wasm.
|
|
70
|
+
* @param wasm - The three v1 wasm asset URLs; validated here, registered in browsers. type:ViewerWasmUrls required:true
|
|
71
|
+
*/
|
|
72
|
+
export async function installEngine(wasm) {
|
|
73
|
+
assertWasmUrls(wasm);
|
|
74
|
+
if (hasBrowserWindow()) {
|
|
75
|
+
await bindBrowserWasm(wasm);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAe7E,SAAS,WAAW,CAAC,OAAe;IAClC,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,CAC7B;IAAC,GAAgC,CAAC,IAAI,GAAG,YAAY,CAAA;IACtD,MAAM,GAAG,CAAA;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAA0C;IACvE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,WAAW,CAAC,2EAA2E,CAAC,CAAA;IAC1F,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,YAAY,CAAU,EAAE,CAAC;QACpE,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5D,WAAW,CAAC,mBAAmB,GAAG,8BAA8B,CAAC,CAAA;QACnE,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB;IACvB,OAAO,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,gBAAgB,KAAK,UAAU,CAAA;AACvF,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,eAAe,CAAC,IAA8B;IAC3D,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAA;IAChD,8DAA8D;IAC9D,iBAAiB,CAAC,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAQ,CAAC,CAAA;IACzE,kBAAkB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACpC,yCAAyC;IACzC,KAAK,IAAI,CAAC,UAAU,CAAA;AACtB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAA8B;IAChE,cAAc,CAAC,IAAI,CAAC,CAAA;IACpB,IAAI,gBAAgB,EAAE,EAAE,CAAC;QACvB,MAAM,eAAe,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;AACH,CAAC","sourcesContent":["/**\n * Engine (kernel) wiring for the viewer.\n *\n * `@faicad/faijs` executes a `.fai.zip` model through environment-level kernel\n * singletons: the OCCT kernel (BREP chain), the manifold mesh engine, and the\n * secondary brepkit engine. Those singletons are configured through the\n * `setOcctWasmInitFn` / `setManifoldWasmUrl` / `setBrepkitWasmInitFn` hooks\n * exported by core. This module maps the three wasm URLs from the v1 contract\n * onto those hooks.\n *\n * Environment handling:\n * - In a real browser the three URLs are authoritative: each kernel loads its\n * wasm from the given URL.\n * - In a Node.ts test / local runner the same three URLs are validated (v1\n * requires them) but the engine falls back to core's node_modules auto-load,\n * so tests run against real, local wasm without a network fetch.\n *\n * Dual-OCCT-copy note: `occt-wasm`'s opaque instance type is nominal per\n * package install (a `#private` member), so a pristine cross-copy type cannot\n * be expressed. The demo uses `OcctKernel.init({ wasm })` plus a cast to the\n * faijs-expected return shape; the viewer mirrors that established pattern.\n */\n\nimport { setOcctWasmInitFn, setManifoldWasmUrl } from '@faicad/faijs/browser'\n\n/** A wasm URL triplet (same shape as the v1 contract). */\nexport interface ViewerWasmUrls {\n occtUrl: string\n manifoldUrl: string\n brepkitUrl: string\n}\n\n/** A code-pinned capability error surfaced by the viewer. */\nexport interface EngineError {\n code: 'E_WASM_URL'\n message: string\n}\n\nfunction throwEngine(message: string): never {\n const err = new Error(message)\n ;(err as Error & { code: string }).code = 'E_WASM_URL'\n throw err\n}\n\n/**\n * Validate the three v1 wasm urls. Throws a code-bearing `E_WASM_URL` error\n * when any is missing or empty, as required by the v1 contract.\n * @param wasm - The three v1 wasm asset URLs ({ occtUrl, manifoldUrl, brepkitUrl }) to validate. type:ViewerWasmUrls required:true\n */\nexport function assertWasmUrls(wasm: Readonly<ViewerWasmUrls> | undefined): void {\n if (!wasm) {\n throwEngine('openFaiZip requires `wasm` — supply { occtUrl, manifoldUrl, brepkitUrl }.')\n }\n for (const key of ['occtUrl', 'manifoldUrl', 'brepkitUrl'] as const) {\n if (typeof wasm[key] !== 'string' || wasm[key].length === 0) {\n throwEngine(`openFaiZip wasm.${key} must be a non-empty string.`)\n }\n }\n}\n\nfunction hasBrowserWindow(): boolean {\n return typeof window !== 'undefined' && typeof window.addEventListener === 'function'\n}\n\n/**\n * Bind the engine kernels for a real browser, installing the occt and manifold\n * init hooks from the configured URLs. `occt-wasm` is loaded lazily so this\n * path only exists in the browser (never forces the dependency at load time in\n * Node tests). brepkit is a secondary engine whose mesh boolean is not yet\n * wired (v1, plan §1.8), so its URL is accepted but not depended on at\n * execution time.\n */\nasync function bindBrowserWasm(wasm: Readonly<ViewerWasmUrls>): Promise<void> {\n const { OcctKernel } = await import('occt-wasm')\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n setOcctWasmInitFn((() => OcctKernel.init({ wasm: wasm.occtUrl })) as any)\n setManifoldWasmUrl(wasm.manifoldUrl)\n // brepkit: accepted, not consumed in v1.\n void wasm.brepkitUrl\n}\n\n/**\n * Install the engine kernels for an `openFaiZip` view. Safe to call\n * repeatedly — core's kernel singletons are environment-wide and\n * instance-neutral.\n *\n * In a browser the three URLs are registered; in a Node/worker runner the URLs\n * are still validated and the Node engine auto-loads its own local wasm.\n * @param wasm - The three v1 wasm asset URLs; validated here, registered in browsers. type:ViewerWasmUrls required:true\n */\nexport async function installEngine(wasm: Readonly<ViewerWasmUrls>): Promise<void> {\n assertWasmUrls(wasm)\n if (hasBrowserWindow()) {\n await bindBrowserWasm(wasm)\n }\n}"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@faicad/faijs-viewer` — the reference way for any third-party host to
|
|
3
|
+
* open and tessellate a project `.fai.zip` document.
|
|
4
|
+
*
|
|
5
|
+
* v1 API:
|
|
6
|
+
* ```
|
|
7
|
+
* import { openFaiZip } from '@faicad/faijs-viewer'
|
|
8
|
+
* const result = await openFaiZip(bytes, {
|
|
9
|
+
* wasm: { occtUrl, manifoldUrl, brepkitUrl },
|
|
10
|
+
* })
|
|
11
|
+
* // result.meshes === [{ name, positions: Float32Array, indices: Uint32Array }]
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The three wasm URLs are all mandatory: the engine's BREP chain depends on
|
|
15
|
+
* OCCT, its mesh/CSG path depends on manifold, and brepkit is part of the
|
|
16
|
+
* reserved engine surface. In a browser each kernel loads its wasm from the
|
|
17
|
+
* given URL; in a Node/worker runner the same URLs are validated and the local
|
|
18
|
+
* engines auto-load their bundled wasm.
|
|
19
|
+
*/
|
|
20
|
+
export { openFaiZip } from './open-fai-zip.js';
|
|
21
|
+
export type { OpenFaiZipOptions, FaiViewerMesh, FaiViewerError, FaiZipViewerErrorCode, OpenFaiResult, FaiZipViewerWasmOptions, } from './types.js';
|
|
22
|
+
export { assertWasmUrls, installEngine } from './engine.js';
|
|
23
|
+
export type { ViewerWasmUrls } from './engine.js';
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,aAAa,EACb,uBAAuB,GACxB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@faicad/faijs-viewer` — the reference way for any third-party host to
|
|
3
|
+
* open and tessellate a project `.fai.zip` document.
|
|
4
|
+
*
|
|
5
|
+
* v1 API:
|
|
6
|
+
* ```
|
|
7
|
+
* import { openFaiZip } from '@faicad/faijs-viewer'
|
|
8
|
+
* const result = await openFaiZip(bytes, {
|
|
9
|
+
* wasm: { occtUrl, manifoldUrl, brepkitUrl },
|
|
10
|
+
* })
|
|
11
|
+
* // result.meshes === [{ name, positions: Float32Array, indices: Uint32Array }]
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The three wasm URLs are all mandatory: the engine's BREP chain depends on
|
|
15
|
+
* OCCT, its mesh/CSG path depends on manifold, and brepkit is part of the
|
|
16
|
+
* reserved engine surface. In a browser each kernel loads its wasm from the
|
|
17
|
+
* given URL; in a Node/worker runner the same URLs are validated and the local
|
|
18
|
+
* engines auto-load their bundled wasm.
|
|
19
|
+
*/
|
|
20
|
+
export { openFaiZip } from './open-fai-zip.js';
|
|
21
|
+
export { assertWasmUrls, installEngine } from './engine.js';
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAS3C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA","sourcesContent":["/**\n * `@faicad/faijs-viewer` — the reference way for any third-party host to\n * open and tessellate a project `.fai.zip` document.\n *\n * v1 API:\n * ```\n * import { openFaiZip } from '@faicad/faijs-viewer'\n * const result = await openFaiZip(bytes, {\n * wasm: { occtUrl, manifoldUrl, brepkitUrl },\n * })\n * // result.meshes === [{ name, positions: Float32Array, indices: Uint32Array }]\n * ```\n *\n * The three wasm URLs are all mandatory: the engine's BREP chain depends on\n * OCCT, its mesh/CSG path depends on manifold, and brepkit is part of the\n * reserved engine surface. In a browser each kernel loads its wasm from the\n * given URL; in a Node/worker runner the same URLs are validated and the local\n * engines auto-load their bundled wasm.\n */\n\nexport { openFaiZip } from './open-fai-zip'\nexport type {\n OpenFaiZipOptions,\n FaiViewerMesh,\n FaiViewerError,\n FaiZipViewerErrorCode,\n OpenFaiResult,\n FaiZipViewerWasmOptions,\n} from './types'\nexport { assertWasmUrls, installEngine } from './engine'\nexport type { ViewerWasmUrls } from './engine'"]}
|
package/dist/libs.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* libs — the viewer's third-party library loader (the dynamic side of
|
|
3
|
+
* `openFaiZip`).
|
|
4
|
+
*
|
|
5
|
+
* The engine loads a model's namespace imports lazily at execute time
|
|
6
|
+
* (`autoLoadLibsFromImports` → `HostPorts.libLoader.loadLib`). This module
|
|
7
|
+
* builds that loader for the viewer. Preset libraries (core, sketch,
|
|
8
|
+
* faijs-extra — merged into the default `cad` namespace) never pass through it;
|
|
9
|
+
* it serves scripts like `import * as gears from "@faicad/faijs-gears"` only.
|
|
10
|
+
*
|
|
11
|
+
* Dispatch follows the environment:
|
|
12
|
+
* - a browser host gets `createBrowserLibLoader` (jsDelivr CDN dynamic
|
|
13
|
+
* `import()`, version-pinned `+esm` links when `versions` is provided);
|
|
14
|
+
* - a Node host gets an internal whitelisted `import(pkg)` loader mirroring
|
|
15
|
+
* core's CLI loader semantics: per-library `faijs.autoLift` from each
|
|
16
|
+
* package's own `package.json`, plus `loadSource` for determinism scanning.
|
|
17
|
+
*
|
|
18
|
+
* **Access policy (user-decided, 2026-10-04):** every `@faicad/*` package is
|
|
19
|
+
* allowed by default — no whitelist is required. Non-`@faicad/*` packages are
|
|
20
|
+
* rejected (defence against look-alike stranger packages from an untrusted
|
|
21
|
+
* `.fai.zip`). The Node branch enforces the scoped prefix directly; the
|
|
22
|
+
* browser branch wraps core's loader (whose `libs` option, when omitted,
|
|
23
|
+
* would otherwise allow everything) with the same check.
|
|
24
|
+
*
|
|
25
|
+
* The Node branches touch `node:` builtins, so they are reached only through
|
|
26
|
+
* dynamic `import(/* @vite-ignore */)` calls behind an `inNodeEnv()` guard —
|
|
27
|
+
* the same pattern `ensureSketchSolver` uses in `open-fai-zip.ts`. A browser
|
|
28
|
+
* bundler never bundles the `node:` branch into a shipped chunk.
|
|
29
|
+
*/
|
|
30
|
+
import type { LibLoader } from '@faicad/faijs';
|
|
31
|
+
import type { FaiViewerLibsOptions } from './types.js';
|
|
32
|
+
/**
|
|
33
|
+
* Build the viewer's library loader for `openFaiZip`.
|
|
34
|
+
*
|
|
35
|
+
* @param opts - the `libs` config from `OpenFaiZipOptions`; `undefined` means
|
|
36
|
+
* dynamic loading is enabled with the loader defaults.
|
|
37
|
+
* @returns the loader, or `undefined` when dynamic loading is disabled
|
|
38
|
+
* (`libs.enabled === false`) — unregistered library imports then fail as
|
|
39
|
+
* unbound namespaces at execution.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createViewerLibLoader(opts: FaiViewerLibsOptions | undefined): Promise<LibLoader | undefined>;
|
|
42
|
+
//# sourceMappingURL=libs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"libs.d.ts","sourceRoot":"","sources":["../src/libs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAuInD;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,oBAAoB,GAAG,SAAS,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAsBlH"}
|
package/dist/libs.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* libs — the viewer's third-party library loader (the dynamic side of
|
|
3
|
+
* `openFaiZip`).
|
|
4
|
+
*
|
|
5
|
+
* The engine loads a model's namespace imports lazily at execute time
|
|
6
|
+
* (`autoLoadLibsFromImports` → `HostPorts.libLoader.loadLib`). This module
|
|
7
|
+
* builds that loader for the viewer. Preset libraries (core, sketch,
|
|
8
|
+
* faijs-extra — merged into the default `cad` namespace) never pass through it;
|
|
9
|
+
* it serves scripts like `import * as gears from "@faicad/faijs-gears"` only.
|
|
10
|
+
*
|
|
11
|
+
* Dispatch follows the environment:
|
|
12
|
+
* - a browser host gets `createBrowserLibLoader` (jsDelivr CDN dynamic
|
|
13
|
+
* `import()`, version-pinned `+esm` links when `versions` is provided);
|
|
14
|
+
* - a Node host gets an internal whitelisted `import(pkg)` loader mirroring
|
|
15
|
+
* core's CLI loader semantics: per-library `faijs.autoLift` from each
|
|
16
|
+
* package's own `package.json`, plus `loadSource` for determinism scanning.
|
|
17
|
+
*
|
|
18
|
+
* **Access policy (user-decided, 2026-10-04):** every `@faicad/*` package is
|
|
19
|
+
* allowed by default — no whitelist is required. Non-`@faicad/*` packages are
|
|
20
|
+
* rejected (defence against look-alike stranger packages from an untrusted
|
|
21
|
+
* `.fai.zip`). The Node branch enforces the scoped prefix directly; the
|
|
22
|
+
* browser branch wraps core's loader (whose `libs` option, when omitted,
|
|
23
|
+
* would otherwise allow everything) with the same check.
|
|
24
|
+
*
|
|
25
|
+
* The Node branches touch `node:` builtins, so they are reached only through
|
|
26
|
+
* dynamic `import(/* @vite-ignore */)` calls behind an `inNodeEnv()` guard —
|
|
27
|
+
* the same pattern `ensureSketchSolver` uses in `open-fai-zip.ts`. A browser
|
|
28
|
+
* bundler never bundles the `node:` branch into a shipped chunk.
|
|
29
|
+
*/
|
|
30
|
+
import { createBrowserLibLoader } from '@faicad/faijs/browser';
|
|
31
|
+
/** Node runner detection (mirrors core's `hasBrowserWindow`). */
|
|
32
|
+
function inNodeEnv() {
|
|
33
|
+
return typeof window === 'undefined' || typeof window.addEventListener !== 'function';
|
|
34
|
+
}
|
|
35
|
+
/** Allowed scoped prefix: every `@faicad/` package loads without a whitelist. */
|
|
36
|
+
const SCOPED_PREFIX = '@faicad/';
|
|
37
|
+
/**
|
|
38
|
+
* Access policy shared by both branches: every `@faicad/*` package is allowed
|
|
39
|
+
* by default; anything else is rejected. (User-decided 2026-10-04: no
|
|
40
|
+
* whitelist parameter — all `@faicad` packages load without configuration.)
|
|
41
|
+
*/
|
|
42
|
+
function assertScoped(pkg, name) {
|
|
43
|
+
if (!pkg.startsWith(SCOPED_PREFIX)) {
|
|
44
|
+
throw new Error(`package "${name}" is not a scoped @faicad/ library`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build the Node library loader.
|
|
49
|
+
*
|
|
50
|
+
* `import(pkg)` resolves through the host's module graph: the package must be
|
|
51
|
+
* installed (or reachable) from the consumer. An unresolved package throws
|
|
52
|
+
* `ERR_MODULE_NOT_FOUND`, which the runtime surfaces as a structured load
|
|
53
|
+
* failure at the import statement — never a viewer throw.
|
|
54
|
+
*/
|
|
55
|
+
async function createNodeLibLoader(opts) {
|
|
56
|
+
// node:* builtins are imported dynamically behind the Node guard so a
|
|
57
|
+
// browser-targeted bundler never sees them statically.
|
|
58
|
+
const { createRequire } = await import(/* @vite-ignore */ 'node:module');
|
|
59
|
+
const { readFileSync, existsSync } = await import(/* @vite-ignore */ 'node:fs');
|
|
60
|
+
const { dirname, join } = await import(/* @vite-ignore */ 'node:path');
|
|
61
|
+
const requireNode = createRequire(import.meta.url);
|
|
62
|
+
const aliases = { ...(opts.aliases ?? {}) };
|
|
63
|
+
/** specifier → npm package name. */
|
|
64
|
+
const pkgOf = (name) => aliases[name] ?? name;
|
|
65
|
+
/**
|
|
66
|
+
* Locate a package's root directory without tripping on its `exports` map.
|
|
67
|
+
*
|
|
68
|
+
* `require.resolve(pkg/package.json)` fails with ERR_PACKAGE_PATH_NOT_EXPORTED
|
|
69
|
+
* for packages whose `exports` does not list the `package.json` subpath (all
|
|
70
|
+
* @faicad family packages today). Resolving the package's main entry instead
|
|
71
|
+
* (always listed in `exports`), then walking up to the first `package.json`
|
|
72
|
+
* whose `name` matches, yields the root in both symlinked-workspace and
|
|
73
|
+
* installed-from-registry layouts. GOTCHA (2026-10-04): core's own CLI loader
|
|
74
|
+
* (`cli.ts` `loadSource` / `readLibAutoLift`) uses the direct
|
|
75
|
+
* `resolve(pkg/package.json)` form and therefore silently returns `undefined`
|
|
76
|
+
* for every exported library — the viewer must not copy that behaviour.
|
|
77
|
+
*/
|
|
78
|
+
function findPkgDir(pkg) {
|
|
79
|
+
try {
|
|
80
|
+
const entry = requireNode.resolve(pkg);
|
|
81
|
+
let dir = dirname(entry);
|
|
82
|
+
for (let i = 0; i < 6; i++) {
|
|
83
|
+
const pjPath = join(dir, 'package.json');
|
|
84
|
+
if (existsSync(pjPath)) {
|
|
85
|
+
try {
|
|
86
|
+
const pj = JSON.parse(readFileSync(pjPath, 'utf8'));
|
|
87
|
+
if (pj.name === pkg)
|
|
88
|
+
return dir;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Unparseable package.json: keep walking up.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const parent = dirname(dir);
|
|
95
|
+
if (parent === dir)
|
|
96
|
+
break;
|
|
97
|
+
dir = parent;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Unresolvable package: caller handles `undefined`.
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
/** Read `faijs.autoLift` from the library's own `package.json` (D3-autoLift).
|
|
106
|
+
* Absent → `undefined` (runtime falls back to its `!hasDualOp` inference). */
|
|
107
|
+
function readLibAutoLift(pkg) {
|
|
108
|
+
const pkgDir = findPkgDir(pkg);
|
|
109
|
+
if (!pkgDir)
|
|
110
|
+
return undefined;
|
|
111
|
+
try {
|
|
112
|
+
const pj = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8'));
|
|
113
|
+
const v = pj.faijs?.autoLift;
|
|
114
|
+
return typeof v === 'boolean' ? v : undefined;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
loadLib: async (name) => {
|
|
122
|
+
const pkg = pkgOf(name);
|
|
123
|
+
assertScoped(pkg, name);
|
|
124
|
+
return (await import(pkg));
|
|
125
|
+
},
|
|
126
|
+
listLibs: () => [...Object.keys(aliases), '@faicad/'],
|
|
127
|
+
loadSource: async (name) => {
|
|
128
|
+
const pkg = pkgOf(name);
|
|
129
|
+
const pkgDir = findPkgDir(pkg);
|
|
130
|
+
if (!pkgDir)
|
|
131
|
+
return undefined;
|
|
132
|
+
// Compiled JS first: the runtime's determinism scan (`scanLibrarySource`,
|
|
133
|
+
// strict mode) rejects TS sources outright ("libraries must ship compiled
|
|
134
|
+
// JS"), and it runs against every @faicad/* import before loadLib.
|
|
135
|
+
for (const p of [
|
|
136
|
+
join(pkgDir, 'dist', 'index.js'),
|
|
137
|
+
join(pkgDir, 'src', 'index.js'),
|
|
138
|
+
join(pkgDir, 'src', 'index.ts'),
|
|
139
|
+
]) {
|
|
140
|
+
if (existsSync(p))
|
|
141
|
+
return readFileSync(p, 'utf8');
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
},
|
|
145
|
+
options: {
|
|
146
|
+
// Lifting defaults to the runtime's `!hasDualOp(ns)` inference: bare
|
|
147
|
+
// function libraries (gears, fasteners, sheetmetal) lift to the
|
|
148
|
+
// script-facing shape automatically, dual-op libraries stay unlifted.
|
|
149
|
+
// Each library overrides through its own `faijs.autoLift` declaration
|
|
150
|
+
// (`autoLiftFor`), mirroring core's per-library D3 convention. Note this
|
|
151
|
+
// deliberately differs from core's CLI loader, which defaults
|
|
152
|
+
// `autoLift: false` and leaves undeclared bare-function libraries
|
|
153
|
+
// un-lifted — a CLI-side limitation the viewer does not copy.
|
|
154
|
+
autoLiftFor: (name) => readLibAutoLift(pkgOf(name)),
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build the viewer's library loader for `openFaiZip`.
|
|
160
|
+
*
|
|
161
|
+
* @param opts - the `libs` config from `OpenFaiZipOptions`; `undefined` means
|
|
162
|
+
* dynamic loading is enabled with the loader defaults.
|
|
163
|
+
* @returns the loader, or `undefined` when dynamic loading is disabled
|
|
164
|
+
* (`libs.enabled === false`) — unregistered library imports then fail as
|
|
165
|
+
* unbound namespaces at execution.
|
|
166
|
+
*/
|
|
167
|
+
export async function createViewerLibLoader(opts) {
|
|
168
|
+
if (opts?.enabled === false)
|
|
169
|
+
return undefined;
|
|
170
|
+
if (inNodeEnv())
|
|
171
|
+
return createNodeLibLoader(opts ?? {});
|
|
172
|
+
const inner = createBrowserLibLoader({
|
|
173
|
+
cdnBase: opts?.cdnBase,
|
|
174
|
+
versions: opts?.versions,
|
|
175
|
+
aliases: opts?.aliases,
|
|
176
|
+
importModule: opts?.importModule,
|
|
177
|
+
});
|
|
178
|
+
// Wrap core's loader with the shared access policy: core's `libs` option,
|
|
179
|
+
// when omitted, allows ANY package — an untrusted `.fai.zip` must not reach
|
|
180
|
+
// that. Every `@faicad/*` package loads by default; anything else is
|
|
181
|
+
// rejected before any CDN import.
|
|
182
|
+
const { loadLib, ...rest } = inner;
|
|
183
|
+
return {
|
|
184
|
+
...rest,
|
|
185
|
+
loadLib: async (name) => {
|
|
186
|
+
const pkg = opts?.aliases?.[name] ?? name;
|
|
187
|
+
assertScoped(pkg, name);
|
|
188
|
+
return loadLib(name);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=libs.js.map
|
package/dist/libs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"libs.js","sourceRoot":"","sources":["../src/libs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * libs — the viewer's third-party library loader (the dynamic side of\n * `openFaiZip`).\n *\n * The engine loads a model's namespace imports lazily at execute time\n * (`autoLoadLibsFromImports` → `HostPorts.libLoader.loadLib`). This module\n * builds that loader for the viewer. Preset libraries (core, sketch,\n * faijs-extra — merged into the default `cad` namespace) never pass through it;\n * it serves scripts like `import * as gears from \"@faicad/faijs-gears\"` only.\n *\n * Dispatch follows the environment:\n * - a browser host gets `createBrowserLibLoader` (jsDelivr CDN dynamic\n * `import()`, version-pinned `+esm` links when `versions` is provided);\n * - a Node host gets an internal whitelisted `import(pkg)` loader mirroring\n * core's CLI loader semantics: per-library `faijs.autoLift` from each\n * package's own `package.json`, plus `loadSource` for determinism scanning.\n *\n * **Access policy (user-decided, 2026-10-04):** every `@faicad/*` package is\n * allowed by default — no whitelist is required. Non-`@faicad/*` packages are\n * rejected (defence against look-alike stranger packages from an untrusted\n * `.fai.zip`). The Node branch enforces the scoped prefix directly; the\n * browser branch wraps core's loader (whose `libs` option, when omitted,\n * would otherwise allow everything) with the same check.\n *\n * The Node branches touch `node:` builtins, so they are reached only through\n * dynamic `import(/* @vite-ignore */)` calls behind an `inNodeEnv()` guard —\n * the same pattern `ensureSketchSolver` uses in `open-fai-zip.ts`. A browser\n * bundler never bundles the `node:` branch into a shipped chunk.\n */\n\nimport { createBrowserLibLoader } from '@faicad/faijs/browser'\nimport type { LibLoader } from '@faicad/faijs'\nimport type { LibNamespace } from '@faicad/faijs/runtime-state'\nimport type { FaiViewerLibsOptions } from './types'\n\n/** Node runner detection (mirrors core's `hasBrowserWindow`). */\nfunction inNodeEnv(): boolean {\n return typeof window === 'undefined' || typeof window.addEventListener !== 'function'\n}\n\n/** Allowed scoped prefix: every `@faicad/` package loads without a whitelist. */\nconst SCOPED_PREFIX = '@faicad/'\n\n/**\n * Access policy shared by both branches: every `@faicad/*` package is allowed\n * by default; anything else is rejected. (User-decided 2026-10-04: no\n * whitelist parameter — all `@faicad` packages load without configuration.)\n */\nfunction assertScoped(pkg: string, name: string): void {\n if (!pkg.startsWith(SCOPED_PREFIX)) {\n throw new Error(`package \"${name}\" is not a scoped @faicad/ library`)\n }\n}\n\n/**\n * Build the Node library loader.\n *\n * `import(pkg)` resolves through the host's module graph: the package must be\n * installed (or reachable) from the consumer. An unresolved package throws\n * `ERR_MODULE_NOT_FOUND`, which the runtime surfaces as a structured load\n * failure at the import statement — never a viewer throw.\n */\nasync function createNodeLibLoader(opts: FaiViewerLibsOptions): Promise<LibLoader> {\n // node:* builtins are imported dynamically behind the Node guard so a\n // browser-targeted bundler never sees them statically.\n const { createRequire } = await import(/* @vite-ignore */ 'node:module')\n const { readFileSync, existsSync } = await import(/* @vite-ignore */ 'node:fs')\n const { dirname, join } = await import(/* @vite-ignore */ 'node:path')\n const requireNode = createRequire(import.meta.url)\n\n const aliases: Record<string, string> = { ...(opts.aliases ?? {}) }\n\n /** specifier → npm package name. */\n const pkgOf = (name: string): string => aliases[name] ?? name\n\n /**\n * Locate a package's root directory without tripping on its `exports` map.\n *\n * `require.resolve(pkg/package.json)` fails with ERR_PACKAGE_PATH_NOT_EXPORTED\n * for packages whose `exports` does not list the `package.json` subpath (all\n * @faicad family packages today). Resolving the package's main entry instead\n * (always listed in `exports`), then walking up to the first `package.json`\n * whose `name` matches, yields the root in both symlinked-workspace and\n * installed-from-registry layouts. GOTCHA (2026-10-04): core's own CLI loader\n * (`cli.ts` `loadSource` / `readLibAutoLift`) uses the direct\n * `resolve(pkg/package.json)` form and therefore silently returns `undefined`\n * for every exported library — the viewer must not copy that behaviour.\n */\n function findPkgDir(pkg: string): string | undefined {\n try {\n const entry = requireNode.resolve(pkg)\n let dir = dirname(entry)\n for (let i = 0; i < 6; i++) {\n const pjPath = join(dir, 'package.json')\n if (existsSync(pjPath)) {\n try {\n const pj = JSON.parse(readFileSync(pjPath, 'utf8')) as { name?: string }\n if (pj.name === pkg) return dir\n } catch {\n // Unparseable package.json: keep walking up.\n }\n }\n const parent = dirname(dir)\n if (parent === dir) break\n dir = parent\n }\n } catch {\n // Unresolvable package: caller handles `undefined`.\n }\n return undefined\n }\n\n /** Read `faijs.autoLift` from the library's own `package.json` (D3-autoLift).\n * Absent → `undefined` (runtime falls back to its `!hasDualOp` inference). */\n function readLibAutoLift(pkg: string): boolean | undefined {\n const pkgDir = findPkgDir(pkg)\n if (!pkgDir) return undefined\n try {\n const pj = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')) as {\n faijs?: { autoLift?: boolean }\n }\n const v = pj.faijs?.autoLift\n return typeof v === 'boolean' ? v : undefined\n } catch {\n return undefined\n }\n }\n\n return {\n loadLib: async (name: string) => {\n const pkg = pkgOf(name)\n assertScoped(pkg, name)\n return (await import(pkg)) as LibNamespace\n },\n\n listLibs: () => [...Object.keys(aliases), '@faicad/'],\n\n loadSource: async (name: string) => {\n const pkg = pkgOf(name)\n const pkgDir = findPkgDir(pkg)\n if (!pkgDir) return undefined\n // Compiled JS first: the runtime's determinism scan (`scanLibrarySource`,\n // strict mode) rejects TS sources outright (\"libraries must ship compiled\n // JS\"), and it runs against every @faicad/* import before loadLib.\n for (const p of [\n join(pkgDir, 'dist', 'index.js'),\n join(pkgDir, 'src', 'index.js'),\n join(pkgDir, 'src', 'index.ts'),\n ]) {\n if (existsSync(p)) return readFileSync(p, 'utf8')\n }\n return undefined\n },\n\n options: {\n // Lifting defaults to the runtime's `!hasDualOp(ns)` inference: bare\n // function libraries (gears, fasteners, sheetmetal) lift to the\n // script-facing shape automatically, dual-op libraries stay unlifted.\n // Each library overrides through its own `faijs.autoLift` declaration\n // (`autoLiftFor`), mirroring core's per-library D3 convention. Note this\n // deliberately differs from core's CLI loader, which defaults\n // `autoLift: false` and leaves undeclared bare-function libraries\n // un-lifted — a CLI-side limitation the viewer does not copy.\n autoLiftFor: (name: string) => readLibAutoLift(pkgOf(name)),\n },\n }\n}\n\n/**\n * Build the viewer's library loader for `openFaiZip`.\n *\n * @param opts - the `libs` config from `OpenFaiZipOptions`; `undefined` means\n * dynamic loading is enabled with the loader defaults.\n * @returns the loader, or `undefined` when dynamic loading is disabled\n * (`libs.enabled === false`) — unregistered library imports then fail as\n * unbound namespaces at execution.\n */\nexport async function createViewerLibLoader(opts: FaiViewerLibsOptions | undefined): Promise<LibLoader | undefined> {\n if (opts?.enabled === false) return undefined\n if (inNodeEnv()) return createNodeLibLoader(opts ?? {})\n const inner = createBrowserLibLoader({\n cdnBase: opts?.cdnBase,\n versions: opts?.versions,\n aliases: opts?.aliases,\n importModule: opts?.importModule,\n })\n // Wrap core's loader with the shared access policy: core's `libs` option,\n // when omitted, allows ANY package — an untrusted `.fai.zip` must not reach\n // that. Every `@faicad/*` package loads by default; anything else is\n // rejected before any CDN import.\n const { loadLib, ...rest } = inner\n return {\n ...rest,\n loadLib: async (name: string) => {\n const pkg = opts?.aliases?.[name] ?? name\n assertScoped(pkg, name)\n return loadLib(name)\n },\n }\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `openFaiZip` — the reference API of `@faicad/faijs-viewer`.
|
|
3
|
+
*
|
|
4
|
+
* v1 contract:
|
|
5
|
+
* ```
|
|
6
|
+
* openFaiZip(bytes, { wasm: { occtUrl, manifoldUrl, brepkitUrl } })
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* Given a project `.fai.zip` byte buffer and the three engine wasm URLs, this
|
|
10
|
+
* reads the container, resolves the active (or requested) model, executes it
|
|
11
|
+
* through `@faicad/faijs` (`createRuntime` over the container's own module
|
|
12
|
+
* graph), and returns structured, tessellated mesh data plus provenance.
|
|
13
|
+
*
|
|
14
|
+
* The return value is host- and rendering-agnostic: arrays of
|
|
15
|
+
* `Float32Array` / `Uint32Array` mesh data, no THREE or DOM in sight. A
|
|
16
|
+
* render-relevant failure never throws — it is surfaced on `result.error` as a
|
|
17
|
+
* structured `FaiViewerError`. Environment/setup failures (e.g. a missing or
|
|
18
|
+
* empty `wasm` URL) throw a code-bearing error from `installEngine`.
|
|
19
|
+
*/
|
|
20
|
+
import type { OpenFaiZipOptions, OpenFaiResult } from './types.js';
|
|
21
|
+
/**
|
|
22
|
+
* Open, execute, and tessellate a project `.fai.zip`.
|
|
23
|
+
*
|
|
24
|
+
* @param bytes the container byte array.
|
|
25
|
+
* @param opts the viewer options; `opts.wasm` (three URLs) is required.
|
|
26
|
+
* @returns an `OpenFaiResult` with structured meshes, or an `error`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function openFaiZip(bytes: Uint8Array, opts: OpenFaiZipOptions): Promise<OpenFaiResult>;
|
|
29
|
+
//# sourceMappingURL=open-fai-zip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-fai-zip.d.ts","sourceRoot":"","sources":["../src/open-fai-zip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAUH,OAAO,KAAK,EAAiC,iBAAiB,EAAE,aAAa,EAAyB,MAAM,SAAS,CAAA;AAoJrH;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC,CA6EnG"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `openFaiZip` — the reference API of `@faicad/faijs-viewer`.
|
|
3
|
+
*
|
|
4
|
+
* v1 contract:
|
|
5
|
+
* ```
|
|
6
|
+
* openFaiZip(bytes, { wasm: { occtUrl, manifoldUrl, brepkitUrl } })
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* Given a project `.fai.zip` byte buffer and the three engine wasm URLs, this
|
|
10
|
+
* reads the container, resolves the active (or requested) model, executes it
|
|
11
|
+
* through `@faicad/faijs` (`createRuntime` over the container's own module
|
|
12
|
+
* graph), and returns structured, tessellated mesh data plus provenance.
|
|
13
|
+
*
|
|
14
|
+
* The return value is host- and rendering-agnostic: arrays of
|
|
15
|
+
* `Float32Array` / `Uint32Array` mesh data, no THREE or DOM in sight. A
|
|
16
|
+
* render-relevant failure never throws — it is surfaced on `result.error` as a
|
|
17
|
+
* structured `FaiViewerError`. Environment/setup failures (e.g. a missing or
|
|
18
|
+
* empty `wasm` URL) throw a code-bearing error from `installEngine`.
|
|
19
|
+
*/
|
|
20
|
+
import { createBrowserPorts, createRuntime, isMeshShape } from '@faicad/faijs/browser';
|
|
21
|
+
import { createApiNamespace } from '@faicad/faijs/api/api-namespace';
|
|
22
|
+
import { mergeSketchNamespace, installSketchSolver, registerSketchSymbols } from '@faicad/faijs-sketch';
|
|
23
|
+
import { mergeEditorNamespace, registerEditorSymbols } from '@faicad/faijs-extra';
|
|
24
|
+
import { openContainer } from '@faicad/faijs/io/fai-zip';
|
|
25
|
+
import { installEngine } from './engine.js';
|
|
26
|
+
import { createViewerLibLoader } from './libs.js';
|
|
27
|
+
function failure(code, message, detail) {
|
|
28
|
+
return { code, message, detail };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Hand the exact typed-array byte range out to the host without sharing the
|
|
32
|
+
* archive page. Mesh arrays are already `Float32Array` / `Uint32Array`; this
|
|
33
|
+
* isolates the backing buffer so the host (and potential transfer) is safe.
|
|
34
|
+
*/
|
|
35
|
+
function isolateArray(array) {
|
|
36
|
+
const ctor = array.constructor;
|
|
37
|
+
return new ctor(array);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Structure the visible terminals of an execution result into a flat mesh
|
|
41
|
+
* list. Mirrors `packages/demo/main.ts#extractShapes`: hidden terminals
|
|
42
|
+
* (boolean sources, etc.) are never counted; when no explicit terminal exists,
|
|
43
|
+
* the last statement output is used.
|
|
44
|
+
*/
|
|
45
|
+
function extractMeshes(result) {
|
|
46
|
+
const meshes = [];
|
|
47
|
+
const visible = result.terminals.filter((t) => !t.hidden);
|
|
48
|
+
if (visible.length > 0) {
|
|
49
|
+
for (const terminal of visible) {
|
|
50
|
+
const shape = result.outputs.get(terminal.id);
|
|
51
|
+
if (shape && isMeshShape(shape)) {
|
|
52
|
+
meshes.push({
|
|
53
|
+
name: terminal.id,
|
|
54
|
+
positions: isolateArray(shape.positions),
|
|
55
|
+
indices: isolateArray(shape.indices),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return meshes;
|
|
60
|
+
}
|
|
61
|
+
const outputs = Array.from(result.outputs.values());
|
|
62
|
+
const last = outputs[outputs.length - 1];
|
|
63
|
+
if (last && last.positions && last.indices) {
|
|
64
|
+
meshes.push({
|
|
65
|
+
name: 'output',
|
|
66
|
+
positions: isolateArray(last.positions),
|
|
67
|
+
indices: isolateArray(last.indices),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return meshes;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Build an asset resolver over the container's `assets/` payload map, backing
|
|
74
|
+
* `cad.loadByKey` and the BREP-asset import op with the exact bytes the archive
|
|
75
|
+
* carries. A missing key throws (mirrors `FetchAssetResolver`'s hard error).
|
|
76
|
+
*/
|
|
77
|
+
function containerAssetResolver(assets) {
|
|
78
|
+
return {
|
|
79
|
+
async resolveByKey(key) {
|
|
80
|
+
const bytes = assets[key];
|
|
81
|
+
if (bytes === undefined) {
|
|
82
|
+
throw new Error(`[faijs-viewer] asset key "${key}" is not present in this .fai.zip`);
|
|
83
|
+
}
|
|
84
|
+
const copy = new Uint8Array(bytes);
|
|
85
|
+
return { bytes: copy.buffer, format: 'brp' };
|
|
86
|
+
},
|
|
87
|
+
async resolveFile(_path) {
|
|
88
|
+
throw new Error('[faijs-viewer] resolveFile is unavailable — a .fai.zip is self-contained; load assets by key (`cad.load`) instead.');
|
|
89
|
+
},
|
|
90
|
+
async resolveUrl(url) {
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetch(url);
|
|
93
|
+
if (!res.ok)
|
|
94
|
+
throw new Error(`[faijs-viewer] resolveUrl fetch failed: ${url} (${res.status})`);
|
|
95
|
+
return await res.arrayBuffer();
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
throw new Error(`[faijs-viewer] resolveUrl failed for ${url}: ${String(e)}`);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Node runner detection (mirrors core's `hasBrowserWindow`). */
|
|
104
|
+
function inNodeEnv() {
|
|
105
|
+
return typeof window === 'undefined' || typeof window.addEventListener !== 'function';
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Make the `cad.sketch` / `cad.draw` constraint solver available to the
|
|
109
|
+
* shared, module-global sketch op.
|
|
110
|
+
*
|
|
111
|
+
* The solver lives in `@faicad/faijs-sketch` and is host-injected via
|
|
112
|
+
* `installSketchSolver`, because the planegcs wasm source differs between Node
|
|
113
|
+
* and browser hosts. Node auto-loads the solver (its wasm ships in
|
|
114
|
+
* `@salusoft89/planegcs`); a browser host self-hosts `planegcs.wasm` and passes
|
|
115
|
+
* `opts.sketch.planegcsUrl`. The `E_SKETCH_NO_SOLVER` error is surfaced through
|
|
116
|
+
* `OpenFaiResult.error`, never thrown.
|
|
117
|
+
*
|
|
118
|
+
* The Node-only subpath (specified by `NODE_SKETCH_SOLVER_SPECIFIER`) imports
|
|
119
|
+
* `node:module` via `createRequire`, which a browser-targeted bundler cannot
|
|
120
|
+
* shim for a code-splitting worker/chunk. It is therefore never written as a
|
|
121
|
+
* literal in an `import()` here: `/* @vite-ignore *` plus an opaque specifier
|
|
122
|
+
* leaves resolution entirely to the runtime, so Vite/Rollup/webpack never
|
|
123
|
+
* bundle its node:* into a browser build. A Node host resolves it normally; a
|
|
124
|
+
* browser host never reaches that branch.
|
|
125
|
+
*/
|
|
126
|
+
const NODE_SKETCH_SOLVER_SPECIFIER = '@faicad/faijs-sketch/node';
|
|
127
|
+
async function ensureSketchSolver(opts) {
|
|
128
|
+
if (inNodeEnv()) {
|
|
129
|
+
// The /node subpath imports `node:module`, so it must never be statically
|
|
130
|
+
// imported from a browser-targeted entry. Dynamic import keeps the browser
|
|
131
|
+
// bundle free of `node:*`.
|
|
132
|
+
try {
|
|
133
|
+
const nodeSolver = (await import(/* @vite-ignore */ NODE_SKETCH_SOLVER_SPECIFIER));
|
|
134
|
+
installSketchSolver(nodeSolver.createNodePlanegcsSolver);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// The solver package is not installed in this process — sketch models
|
|
138
|
+
// will surface E_SKETCHC_NO_SOLVER. Core mesh-only containers are unaffected.
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const planegcsUrl = opts?.sketch?.planegcsUrl;
|
|
143
|
+
if (!planegcsUrl)
|
|
144
|
+
return; // browser without a self-hosted solver → E_SKETCHC_NO_SOLVER at execution time
|
|
145
|
+
try {
|
|
146
|
+
const res = await fetch(planegcsUrl);
|
|
147
|
+
if (!res.ok)
|
|
148
|
+
throw new Error(`planegcs fetch failed: ${planegcsUrl} (${res.status})`);
|
|
149
|
+
const wasmBytes = new Uint8Array(await res.arrayBuffer());
|
|
150
|
+
const { createPlanegcsSolver } = await import('@faicad/faijs-sketch');
|
|
151
|
+
installSketchSolver(async () => createPlanegcsSolver({ wasmBytes }));
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
// Degrade, mirroring the Node branch: an unreachable solver (offline CDN,
|
|
155
|
+
// network policy, …) must not sink every `.fai.zip` open — models that
|
|
156
|
+
// never call `cad.sketch` load fine, and models that do surface a
|
|
157
|
+
// structured `E_SKETCHC_NO_SOLVER` at execution. Previously this threw
|
|
158
|
+
// `[faijs-viewer] planegcs solver init failed: …`, failing even pure
|
|
159
|
+
// `cad.box` containers when the solver URL could not be fetched.
|
|
160
|
+
console.warn(`[faijs-viewer] planegcs solver unavailable, sketch models will fail: ${String(e)}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Open, execute, and tessellate a project `.fai.zip`.
|
|
165
|
+
*
|
|
166
|
+
* @param bytes the container byte array.
|
|
167
|
+
* @param opts the viewer options; `opts.wasm` (three URLs) is required.
|
|
168
|
+
* @returns an `OpenFaiResult` with structured meshes, or an `error`.
|
|
169
|
+
*/
|
|
170
|
+
export async function openFaiZip(bytes, opts) {
|
|
171
|
+
await ensureSketchSolver(opts);
|
|
172
|
+
// 1. Engine contract. The three wasm urls are mandatory; a missing or empty
|
|
173
|
+
// url throws (E_WASM_URL) — this is a caller contract violation, not a
|
|
174
|
+
// per-bytes render failure, so it propagates rather than returning an
|
|
175
|
+
// `error` result. In a browser the URLs bind occt/manifold wasm; in a
|
|
176
|
+
// Node runner they are validated and local engines auto-load.
|
|
177
|
+
await installEngine(opts.wasm);
|
|
178
|
+
// 2. Reader-side: parse the container (manifest, active model, module graph,
|
|
179
|
+
// payloads). Hard container errors become an E_CONTAINER result.
|
|
180
|
+
let container;
|
|
181
|
+
try {
|
|
182
|
+
container = openContainer(bytes, { modelId: opts?.modelId });
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
return { modelId: '', meshes: [], error: failure('E_CONTAINER', e.message, e) };
|
|
186
|
+
}
|
|
187
|
+
// 3. Host ports: archive asset authorisation + the container module loader.
|
|
188
|
+
// The loader already enumerates/reads the `model/**` module graph. The
|
|
189
|
+
// library loader (dynamic side) serves namespace imports of third-party
|
|
190
|
+
// faijs packages (`@faicad/faijs-gears`, `@faicad/sheetmetal`, …) — the
|
|
191
|
+
// engine calls it lazily at execute time, only for libraries a model
|
|
192
|
+
// actually imports. `libs.enabled === false` omits it, so unregistered
|
|
193
|
+
// library imports fail as unbound namespaces.
|
|
194
|
+
const libLoader = await createViewerLibLoader(opts?.libs);
|
|
195
|
+
const ports = await createBrowserPorts({
|
|
196
|
+
assets: containerAssetResolver(container.assets),
|
|
197
|
+
projectLoader: container.loader,
|
|
198
|
+
...(libLoader ? { libLoader } : {}),
|
|
199
|
+
});
|
|
200
|
+
// 4. Assemble a runtime bound to those ports, then execute the active model.
|
|
201
|
+
const runtime = createRuntime(ports, opts?.mode ?? 'auto');
|
|
202
|
+
// Real converted containers call `cad.sketch` and editor-extension ops
|
|
203
|
+
// (`cad.fai_drill` / `cad.group` / `cad.text` / …), which live in the sketch
|
|
204
|
+
// and faijs-extra libraries rather than core. Re-register the default `cad`
|
|
205
|
+
// binding with those merged in so such models execute without any import
|
|
206
|
+
// statement. The symbol-table entries are also registered so static analysis
|
|
207
|
+
// recognises them. (`@faicad/faijs-draw` is deprecated and deliberately not
|
|
208
|
+
// merged: a `cad.draw` call fails as an unknown callee.)
|
|
209
|
+
registerSketchSymbols();
|
|
210
|
+
registerEditorSymbols();
|
|
211
|
+
runtime.registerLib('cad', mergeEditorNamespace(mergeSketchNamespace(createApiNamespace())), { default: true });
|
|
212
|
+
const entryKey = container.activeModel.entry.slice('model/'.length);
|
|
213
|
+
const entrySource = await container.loader.readSource(entryKey);
|
|
214
|
+
const result = await runtime.execute(entrySource, { entryKey });
|
|
215
|
+
if (result.failedAt) {
|
|
216
|
+
return {
|
|
217
|
+
sourceFile: container.manifest.source?.file,
|
|
218
|
+
modelId: container.activeModel.id,
|
|
219
|
+
meshes: [],
|
|
220
|
+
error: failure('E_EXECUTION', `Execution failed at op "${result.failedAt.callee}": ${result.failedAt.message}`, result.failedAt),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// 5. Structure the visible terminals into host-friendly meshes.
|
|
224
|
+
const meshes = extractMeshes(result);
|
|
225
|
+
if (meshes.length === 0) {
|
|
226
|
+
return {
|
|
227
|
+
sourceFile: container.manifest.source?.file,
|
|
228
|
+
modelId: container.activeModel.id,
|
|
229
|
+
meshes: [],
|
|
230
|
+
error: failure('E_NO_GEOMETRY', 'The model produced no visible, tessellable geometry.'),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
sourceFile: container.manifest.source?.file,
|
|
235
|
+
modelId: container.activeModel.id,
|
|
236
|
+
meshes,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=open-fai-zip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-fai-zip.js","sourceRoot":"","sources":["../src/open-fai-zip.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `openFaiZip` — the reference API of `@faicad/faijs-viewer`.\n *\n * v1 contract:\n * ```\n * openFaiZip(bytes, { wasm: { occtUrl, manifoldUrl, brepkitUrl } })\n * ```\n *\n * Given a project `.fai.zip` byte buffer and the three engine wasm URLs, this\n * reads the container, resolves the active (or requested) model, executes it\n * through `@faicad/faijs` (`createRuntime` over the container's own module\n * graph), and returns structured, tessellated mesh data plus provenance.\n *\n * The return value is host- and rendering-agnostic: arrays of\n * `Float32Array` / `Uint32Array` mesh data, no THREE or DOM in sight. A\n * render-relevant failure never throws — it is surfaced on `result.error` as a\n * structured `FaiViewerError`. Environment/setup failures (e.g. a missing or\n * empty `wasm` URL) throw a code-bearing error from `installEngine`.\n */\n\nimport { createBrowserPorts, createRuntime, isMeshShape } from '@faicad/faijs/browser'\nimport type { AssetResolver, ExecutionResult } from '@faicad/faijs'\nimport { createApiNamespace } from '@faicad/faijs/api/api-namespace'\nimport { mergeSketchNamespace, installSketchSolver, registerSketchSymbols } from '@faicad/faijs-sketch'\nimport { mergeEditorNamespace, registerEditorSymbols } from '@faicad/faijs-extra'\nimport { openContainer, type OpenContainerResult } from '@faicad/faijs/io/fai-zip'\nimport { installEngine } from './engine'\nimport { createViewerLibLoader } from './libs'\nimport type { FaiViewerMesh, FaiViewerError, OpenFaiZipOptions, OpenFaiResult, FaiZipViewerErrorCode } from './types'\n\n/** Error codes emitted by this module (a strict subset of the public union). */\ntype LocalCode = Exclude<FaiZipViewerErrorCode, 'E_WASM_URL'>\n\nfunction failure(code: LocalCode, message: string, detail?: unknown): FaiViewerError {\n return { code, message, detail }\n}\n\n/**\n * Hand the exact typed-array byte range out to the host without sharing the\n * archive page. Mesh arrays are already `Float32Array` / `Uint32Array`; this\n * isolates the backing buffer so the host (and potential transfer) is safe.\n */\nfunction isolateArray<T extends Float32Array | Uint32Array>(array: Float32Array | Uint32Array): T {\n const ctor = array.constructor as new (source: ArrayLike<number>) => T\n return new ctor(array) as T\n}\n\n/**\n * Structure the visible terminals of an execution result into a flat mesh\n * list. Mirrors `packages/demo/main.ts#extractShapes`: hidden terminals\n * (boolean sources, etc.) are never counted; when no explicit terminal exists,\n * the last statement output is used.\n */\nfunction extractMeshes(result: ExecutionResult): FaiViewerMesh[] {\n const meshes: FaiViewerMesh[] = []\n const visible = result.terminals.filter((t) => !t.hidden)\n\n if (visible.length > 0) {\n for (const terminal of visible) {\n const shape = result.outputs.get(terminal.id)\n if (shape && isMeshShape(shape)) {\n meshes.push({\n name: terminal.id,\n positions: isolateArray<Float32Array>(shape.positions),\n indices: isolateArray<Uint32Array>(shape.indices),\n })\n }\n }\n return meshes\n }\n\n const outputs = Array.from(result.outputs.values())\n const last = outputs[outputs.length - 1] as { positions?: Float32Array; indices?: Uint32Array } | undefined\n if (last && last.positions && last.indices) {\n meshes.push({\n name: 'output',\n positions: isolateArray<Float32Array>(last.positions),\n indices: isolateArray<Uint32Array>(last.indices),\n })\n }\n return meshes\n}\n\n/**\n * Build an asset resolver over the container's `assets/` payload map, backing\n * `cad.loadByKey` and the BREP-asset import op with the exact bytes the archive\n * carries. A missing key throws (mirrors `FetchAssetResolver`'s hard error).\n */\nfunction containerAssetResolver(assets: Record<string, Uint8Array>): AssetResolver {\n return {\n async resolveByKey(key: string) {\n const bytes = assets[key]\n if (bytes === undefined) {\n throw new Error(`[faijs-viewer] asset key \"${key}\" is not present in this .fai.zip`)\n }\n const copy = new Uint8Array(bytes)\n return { bytes: copy.buffer as ArrayBuffer, format: 'brp' }\n },\n async resolveFile(_path: string) {\n throw new Error(\n '[faijs-viewer] resolveFile is unavailable — a .fai.zip is self-contained; load assets by key (`cad.load`) instead.',\n )\n },\n async resolveUrl(url: string) {\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`[faijs-viewer] resolveUrl fetch failed: ${url} (${res.status})`)\n return await res.arrayBuffer()\n } catch (e) {\n throw new Error(`[faijs-viewer] resolveUrl failed for ${url}: ${String(e)}`)\n }\n },\n }\n}\n\n/** Node runner detection (mirrors core's `hasBrowserWindow`). */\nfunction inNodeEnv(): boolean {\n return typeof window === 'undefined' || typeof window.addEventListener !== 'function'\n}\n\n/**\n * Make the `cad.sketch` / `cad.draw` constraint solver available to the\n * shared, module-global sketch op.\n *\n * The solver lives in `@faicad/faijs-sketch` and is host-injected via\n * `installSketchSolver`, because the planegcs wasm source differs between Node\n * and browser hosts. Node auto-loads the solver (its wasm ships in\n * `@salusoft89/planegcs`); a browser host self-hosts `planegcs.wasm` and passes\n * `opts.sketch.planegcsUrl`. The `E_SKETCH_NO_SOLVER` error is surfaced through\n * `OpenFaiResult.error`, never thrown.\n *\n * The Node-only subpath (specified by `NODE_SKETCH_SOLVER_SPECIFIER`) imports\n * `node:module` via `createRequire`, which a browser-targeted bundler cannot\n * shim for a code-splitting worker/chunk. It is therefore never written as a\n * literal in an `import()` here: `/* @vite-ignore *` plus an opaque specifier\n * leaves resolution entirely to the runtime, so Vite/Rollup/webpack never\n * bundle its node:* into a browser build. A Node host resolves it normally; a\n * browser host never reaches that branch.\n */\nconst NODE_SKETCH_SOLVER_SPECIFIER = '@faicad/faijs-sketch/node'\n\nasync function ensureSketchSolver(opts: OpenFaiZipOptions): Promise<void> {\n if (inNodeEnv()) {\n // The /node subpath imports `node:module`, so it must never be statically\n // imported from a browser-targeted entry. Dynamic import keeps the browser\n // bundle free of `node:*`.\n try {\n const nodeSolver = (await import(/* @vite-ignore */ NODE_SKETCH_SOLVER_SPECIFIER)) as {\n createNodePlanegcsSolver: unknown\n }\n installSketchSolver(nodeSolver.createNodePlanegcsSolver as Parameters<typeof installSketchSolver>[0])\n } catch {\n // The solver package is not installed in this process — sketch models\n // will surface E_SKETCHC_NO_SOLVER. Core mesh-only containers are unaffected.\n }\n return\n }\n const planegcsUrl = opts?.sketch?.planegcsUrl\n if (!planegcsUrl) return // browser without a self-hosted solver → E_SKETCHC_NO_SOLVER at execution time\n try {\n const res = await fetch(planegcsUrl)\n if (!res.ok) throw new Error(`planegcs fetch failed: ${planegcsUrl} (${res.status})`)\n const wasmBytes = new Uint8Array(await res.arrayBuffer())\n const { createPlanegcsSolver } = await import('@faicad/faijs-sketch')\n installSketchSolver(async () => createPlanegcsSolver({ wasmBytes }))\n } catch (e) {\n // Degrade, mirroring the Node branch: an unreachable solver (offline CDN,\n // network policy, …) must not sink every `.fai.zip` open — models that\n // never call `cad.sketch` load fine, and models that do surface a\n // structured `E_SKETCHC_NO_SOLVER` at execution. Previously this threw\n // `[faijs-viewer] planegcs solver init failed: …`, failing even pure\n // `cad.box` containers when the solver URL could not be fetched.\n console.warn(`[faijs-viewer] planegcs solver unavailable, sketch models will fail: ${String(e)}`)\n }\n}\n\n/**\n * Open, execute, and tessellate a project `.fai.zip`.\n *\n * @param bytes the container byte array.\n * @param opts the viewer options; `opts.wasm` (three URLs) is required.\n * @returns an `OpenFaiResult` with structured meshes, or an `error`.\n */\nexport async function openFaiZip(bytes: Uint8Array, opts: OpenFaiZipOptions): Promise<OpenFaiResult> {\n await ensureSketchSolver(opts)\n // 1. Engine contract. The three wasm urls are mandatory; a missing or empty\n // url throws (E_WASM_URL) — this is a caller contract violation, not a\n // per-bytes render failure, so it propagates rather than returning an\n // `error` result. In a browser the URLs bind occt/manifold wasm; in a\n // Node runner they are validated and local engines auto-load.\n await installEngine(opts.wasm)\n\n // 2. Reader-side: parse the container (manifest, active model, module graph,\n // payloads). Hard container errors become an E_CONTAINER result.\n let container: OpenContainerResult\n try {\n container = openContainer(bytes, { modelId: opts?.modelId })\n } catch (e) {\n return { modelId: '', meshes: [], error: failure('E_CONTAINER', (e as Error).message, e) }\n }\n\n // 3. Host ports: archive asset authorisation + the container module loader.\n // The loader already enumerates/reads the `model/**` module graph. The\n // library loader (dynamic side) serves namespace imports of third-party\n // faijs packages (`@faicad/faijs-gears`, `@faicad/sheetmetal`, …) — the\n // engine calls it lazily at execute time, only for libraries a model\n // actually imports. `libs.enabled === false` omits it, so unregistered\n // library imports fail as unbound namespaces.\n const libLoader = await createViewerLibLoader(opts?.libs)\n const ports = await createBrowserPorts({\n assets: containerAssetResolver(container.assets),\n projectLoader: container.loader,\n ...(libLoader ? { libLoader } : {}),\n })\n\n // 4. Assemble a runtime bound to those ports, then execute the active model.\n const runtime = createRuntime(ports, opts?.mode ?? 'auto')\n // Real converted containers call `cad.sketch` and editor-extension ops\n // (`cad.fai_drill` / `cad.group` / `cad.text` / …), which live in the sketch\n // and faijs-extra libraries rather than core. Re-register the default `cad`\n // binding with those merged in so such models execute without any import\n // statement. The symbol-table entries are also registered so static analysis\n // recognises them. (`@faicad/faijs-draw` is deprecated and deliberately not\n // merged: a `cad.draw` call fails as an unknown callee.)\n registerSketchSymbols()\n registerEditorSymbols()\n runtime.registerLib('cad', mergeEditorNamespace(mergeSketchNamespace(createApiNamespace())), { default: true })\n const entryKey = container.activeModel.entry.slice('model/'.length)\n const entrySource = await container.loader.readSource(entryKey)\n const result = await runtime.execute(entrySource, { entryKey })\n\n if (result.failedAt) {\n return {\n sourceFile: container.manifest.source?.file,\n modelId: container.activeModel.id,\n meshes: [],\n error: failure(\n 'E_EXECUTION',\n `Execution failed at op \"${result.failedAt.callee}\": ${result.failedAt.message}`,\n result.failedAt,\n ),\n }\n }\n\n // 5. Structure the visible terminals into host-friendly meshes.\n const meshes = extractMeshes(result)\n if (meshes.length === 0) {\n return {\n sourceFile: container.manifest.source?.file,\n modelId: container.activeModel.id,\n meshes: [],\n error: failure('E_NO_GEOMETRY', 'The model produced no visible, tessellable geometry.'),\n }\n }\n\n return {\n sourceFile: container.manifest.source?.file,\n modelId: container.activeModel.id,\n meshes,\n }\n}"]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for `@faicad/faijs-viewer`.
|
|
3
|
+
*
|
|
4
|
+
* The viewer is the reference consumer of a project `.fai.zip` document. Its
|
|
5
|
+
* v1 surface is deliberately narrow: read the container, execute the active
|
|
6
|
+
* (or requested) model, and hand back tessellated mesh data plus the result's
|
|
7
|
+
* failure information when execution does not complete. Structuring the meshes
|
|
8
|
+
* on screen is the host's job — this return value never depends on THREE, DOM,
|
|
9
|
+
* or any GL/rendering library.
|
|
10
|
+
*/
|
|
11
|
+
/** The three engine wasm URLs. All three are required by the v1 contract. */
|
|
12
|
+
export interface FaiZipViewerWasmOptions {
|
|
13
|
+
/** URL of the occt-wasm `.wasm` (BREP execution chain engine). */
|
|
14
|
+
occtUrl: string;
|
|
15
|
+
/** URL of the manifold-3d `.wasm` (mesh boolean/CSG engine). */
|
|
16
|
+
manifoldUrl: string;
|
|
17
|
+
/** URL of the brepkit `brepkit_wasm_bg.wasm` (secondary BREP engine). */
|
|
18
|
+
brepkitUrl: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Options for the `cad.sketch` constraint solver that real FreeCAD-converted
|
|
22
|
+
* `.fai.zip` models require.
|
|
23
|
+
*
|
|
24
|
+
* The sketch op folds `@faicad/faijs-sketch` into the `cad` namespace and needs
|
|
25
|
+
* the planegcs constraint solver. In a Node runner the solver auto-loads its
|
|
26
|
+
* wasm from the installed `@salusoft89/planegcs` package; a browser host must
|
|
27
|
+
* self-host the `planegcs.wasm` binary and supply its URL here. When running in
|
|
28
|
+
* a browser without `planegcsUrl`, sketch-based models fail with
|
|
29
|
+
* `E_SKETCHC_NO_SOLVER` (reported through `OpenFaiResult.error`).
|
|
30
|
+
*/
|
|
31
|
+
export interface SketchSolverOptions {
|
|
32
|
+
/** Browser-only: URL of the self-hosted `planegcs.wasm` constraint solver. */
|
|
33
|
+
planegcsUrl?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Dynamic-loading configuration for third-party faijs libraries.
|
|
37
|
+
*
|
|
38
|
+
* The engine already loads a model's namespace imports on demand
|
|
39
|
+
* (`autoLoadLibsFromImports`): a script like
|
|
40
|
+
* `import * as gears from "@faicad/faijs-gears"` triggers a lazy
|
|
41
|
+
* `libLoader.loadLib(packageName)` at execute time, and only for the libraries
|
|
42
|
+
* that model actually imports. This option configures the loader the viewer
|
|
43
|
+
* builds for that purpose — preset libraries (core, sketch, faijs-extra, merged
|
|
44
|
+
* into the default `cad` namespace) never go through it.
|
|
45
|
+
*
|
|
46
|
+
* Loader dispatch follows the environment: a browser host dynamic-imports from
|
|
47
|
+
* the CDN (jsDelivr), a Node host resolves installed packages. Loading or
|
|
48
|
+
* version-verification failures surface as structured `E_EXECUTION`
|
|
49
|
+
* (`failedAt`) on `OpenFaiResult.error`, never a throw.
|
|
50
|
+
*/
|
|
51
|
+
export interface FaiViewerLibsOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Master switch for dynamic loading of non-preset libraries. Default `true`.
|
|
54
|
+
* `false` removes the loader entirely, so an import of an unregistered
|
|
55
|
+
* library fails with an unbound-namespace execution error.
|
|
56
|
+
*/
|
|
57
|
+
enabled?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* CDN base for browser hosts (must end in `/`). Defaults to the jsDelivr npm
|
|
60
|
+
* mirror (`https://cdn.jsdelivr.net/npm/`).
|
|
61
|
+
*/
|
|
62
|
+
cdnBase?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Exact version pins: npm package name → version. Browser hosts build
|
|
65
|
+
* `+esm` direct links from these. Pin to the host engine's version line —
|
|
66
|
+
* `registerLib` rejects a library whose `contractVersion` does not match.
|
|
67
|
+
*/
|
|
68
|
+
versions?: Record<string, string>;
|
|
69
|
+
/** Aliases mapping script specifiers to npm package names (`gears` → `@faicad/faijs-gears`). */
|
|
70
|
+
aliases?: Record<string, string>;
|
|
71
|
+
/**
|
|
72
|
+
* Injectable dynamic-import implementation (tests, or hosts with a custom
|
|
73
|
+
* loading channel). Defaults to the native dynamic `import()`.
|
|
74
|
+
*/
|
|
75
|
+
importModule?: (url: string) => Promise<unknown>;
|
|
76
|
+
}
|
|
77
|
+
/** Options for {@link openFaiZip}. Only `wasm` is required. */
|
|
78
|
+
export interface OpenFaiZipOptions {
|
|
79
|
+
/** The three engine wasm URLs; each must be a non-empty string. */
|
|
80
|
+
wasm: FaiZipViewerWasmOptions;
|
|
81
|
+
/** Execute this specific model id. Defaults to container `active`, else `models[0]`. */
|
|
82
|
+
modelId?: string;
|
|
83
|
+
/** Execution mode. Default `'auto'` (static BREP/mesh dispatch). */
|
|
84
|
+
mode?: 'auto' | 'brep' | 'mesh';
|
|
85
|
+
/** Execution timeout in milliseconds. Omitted = engine default. */
|
|
86
|
+
executionTimeoutMs?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Optional constraint-solver config for models that use `cad.sketch`.
|
|
89
|
+
* Node auto-loads the solver; a browser host passes
|
|
90
|
+
* `planegcsUrl` to self-host the `planegcs.wasm` binary.
|
|
91
|
+
*/
|
|
92
|
+
sketch?: SketchSolverOptions;
|
|
93
|
+
/**
|
|
94
|
+
* Optional dynamic-loading config for third-party faijs libraries (e.g.
|
|
95
|
+
* `@faicad/faijs-gears`, `@faicad/sheetmetal`). Omitted = dynamic loading
|
|
96
|
+
* enabled with unrestricted package set (see `FaiViewerLibsOptions`).
|
|
97
|
+
*/
|
|
98
|
+
libs?: FaiViewerLibsOptions;
|
|
99
|
+
}
|
|
100
|
+
/** One tessellated mesh returned by {@link openFaiView}. */
|
|
101
|
+
export interface FaiViewerMesh {
|
|
102
|
+
/** Part/terminal name this mesh belongs to. */
|
|
103
|
+
name: string;
|
|
104
|
+
/** Interleaved `x,y,z` triangle vertices. */
|
|
105
|
+
positions: Float32Array;
|
|
106
|
+
/** Triangle indices (groups of three) into `positions`. */
|
|
107
|
+
indices: Uint32Array;
|
|
108
|
+
}
|
|
109
|
+
/** Structured failure of a viewer operation. */
|
|
110
|
+
export interface FaiViewerError {
|
|
111
|
+
code: FaiZipViewerErrorCode;
|
|
112
|
+
message: string;
|
|
113
|
+
/** Underlying engine error, when the failure was thrown. */
|
|
114
|
+
detail?: unknown;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Structured resolution code for a viewer failure. Reported through
|
|
118
|
+
* {@link OpenFaiResult.error} rather than thrown, so hosts get an inspectable
|
|
119
|
+
* result object.
|
|
120
|
+
*/
|
|
121
|
+
export type FaiZipViewerErrorCode =
|
|
122
|
+
/** The wasm options were missing or one of the three urls was empty. */
|
|
123
|
+
'E_WASM_URL'
|
|
124
|
+
/** The `.fai.zip` bytes failed container validation / read caps. */
|
|
125
|
+
| 'E_CONTAINER'
|
|
126
|
+
/** The model graph failed to execute (see `detail`). */
|
|
127
|
+
| 'E_EXECUTION'
|
|
128
|
+
/** The model produced no visible mesh. */
|
|
129
|
+
| 'E_NO_GEOMETRY';
|
|
130
|
+
/** Result of {@link openFaiView}. A failure is reported via `error`, not a throw (except wasm/environment errors, which throw). */
|
|
131
|
+
export interface OpenFaiResult {
|
|
132
|
+
/** Source file name recorded in conversion provenance, when present. */
|
|
133
|
+
sourceFile?: string;
|
|
134
|
+
/** The executed model id (after active model defaulting). */
|
|
135
|
+
modelId: string;
|
|
136
|
+
/** The visible, structured mesh list. */
|
|
137
|
+
meshes: FaiViewerMesh[];
|
|
138
|
+
/** Set when reading or executing fails; see `FaiViewerError`. */
|
|
139
|
+
error?: FaiViewerError;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAA;IACf,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IAClC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACjC,gGAAgG;IAChG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACjD;AAED,+DAA+D;AAC/D,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,IAAI,EAAE,uBAAuB,CAAA;IAC7B,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IAC/B,mEAAmE;IACnE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,mBAAmB,CAAA;IAC5B;;;;OAIG;IACH,IAAI,CAAC,EAAE,oBAAoB,CAAA;CAC5B;AAED,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAA;IACZ,6CAA6C;IAC7C,SAAS,EAAE,YAAY,CAAA;IACvB,2DAA2D;IAC3D,OAAO,EAAE,WAAW,CAAA;CACrB;AAED,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,qBAAqB,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB;AAC/B,wEAAwE;AACtE,YAAY;AACd,oEAAoE;GAClE,aAAa;AACf,wDAAwD;GACtD,aAAa;AACf,0CAA0C;GACxC,eAAe,CAAA;AAEnB,mIAAmI;AACnI,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAA;IACf,yCAAyC;IACzC,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,iEAAiE;IACjE,KAAK,CAAC,EAAE,cAAc,CAAA;CACvB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for `@faicad/faijs-viewer`.
|
|
3
|
+
*
|
|
4
|
+
* The viewer is the reference consumer of a project `.fai.zip` document. Its
|
|
5
|
+
* v1 surface is deliberately narrow: read the container, execute the active
|
|
6
|
+
* (or requested) model, and hand back tessellated mesh data plus the result's
|
|
7
|
+
* failure information when execution does not complete. Structuring the meshes
|
|
8
|
+
* on screen is the host's job — this return value never depends on THREE, DOM,
|
|
9
|
+
* or any GL/rendering library.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG","sourcesContent":["/**\n * Public types for `@faicad/faijs-viewer`.\n *\n * The viewer is the reference consumer of a project `.fai.zip` document. Its\n * v1 surface is deliberately narrow: read the container, execute the active\n * (or requested) model, and hand back tessellated mesh data plus the result's\n * failure information when execution does not complete. Structuring the meshes\n * on screen is the host's job — this return value never depends on THREE, DOM,\n * or any GL/rendering library.\n */\n\n/** The three engine wasm URLs. All three are required by the v1 contract. */\nexport interface FaiZipViewerWasmOptions {\n /** URL of the occt-wasm `.wasm` (BREP execution chain engine). */\n occtUrl: string\n /** URL of the manifold-3d `.wasm` (mesh boolean/CSG engine). */\n manifoldUrl: string\n /** URL of the brepkit `brepkit_wasm_bg.wasm` (secondary BREP engine). */\n brepkitUrl: string\n}\n\n/**\n * Options for the `cad.sketch` constraint solver that real FreeCAD-converted\n * `.fai.zip` models require.\n *\n * The sketch op folds `@faicad/faijs-sketch` into the `cad` namespace and needs\n * the planegcs constraint solver. In a Node runner the solver auto-loads its\n * wasm from the installed `@salusoft89/planegcs` package; a browser host must\n * self-host the `planegcs.wasm` binary and supply its URL here. When running in\n * a browser without `planegcsUrl`, sketch-based models fail with\n * `E_SKETCHC_NO_SOLVER` (reported through `OpenFaiResult.error`).\n */\nexport interface SketchSolverOptions {\n /** Browser-only: URL of the self-hosted `planegcs.wasm` constraint solver. */\n planegcsUrl?: string\n}\n\n/**\n * Dynamic-loading configuration for third-party faijs libraries.\n *\n * The engine already loads a model's namespace imports on demand\n * (`autoLoadLibsFromImports`): a script like\n * `import * as gears from \"@faicad/faijs-gears\"` triggers a lazy\n * `libLoader.loadLib(packageName)` at execute time, and only for the libraries\n * that model actually imports. This option configures the loader the viewer\n * builds for that purpose — preset libraries (core, sketch, faijs-extra, merged\n * into the default `cad` namespace) never go through it.\n *\n * Loader dispatch follows the environment: a browser host dynamic-imports from\n * the CDN (jsDelivr), a Node host resolves installed packages. Loading or\n * version-verification failures surface as structured `E_EXECUTION`\n * (`failedAt`) on `OpenFaiResult.error`, never a throw.\n */\nexport interface FaiViewerLibsOptions {\n /**\n * Master switch for dynamic loading of non-preset libraries. Default `true`.\n * `false` removes the loader entirely, so an import of an unregistered\n * library fails with an unbound-namespace execution error.\n */\n enabled?: boolean\n /**\n * CDN base for browser hosts (must end in `/`). Defaults to the jsDelivr npm\n * mirror (`https://cdn.jsdelivr.net/npm/`).\n */\n cdnBase?: string\n /**\n * Exact version pins: npm package name → version. Browser hosts build\n * `+esm` direct links from these. Pin to the host engine's version line —\n * `registerLib` rejects a library whose `contractVersion` does not match.\n */\n versions?: Record<string, string>\n /** Aliases mapping script specifiers to npm package names (`gears` → `@faicad/faijs-gears`). */\n aliases?: Record<string, string>\n /**\n * Injectable dynamic-import implementation (tests, or hosts with a custom\n * loading channel). Defaults to the native dynamic `import()`.\n */\n importModule?: (url: string) => Promise<unknown>\n}\n\n/** Options for {@link openFaiZip}. Only `wasm` is required. */\nexport interface OpenFaiZipOptions {\n /** The three engine wasm URLs; each must be a non-empty string. */\n wasm: FaiZipViewerWasmOptions\n /** Execute this specific model id. Defaults to container `active`, else `models[0]`. */\n modelId?: string\n /** Execution mode. Default `'auto'` (static BREP/mesh dispatch). */\n mode?: 'auto' | 'brep' | 'mesh'\n /** Execution timeout in milliseconds. Omitted = engine default. */\n executionTimeoutMs?: number\n /**\n * Optional constraint-solver config for models that use `cad.sketch`.\n * Node auto-loads the solver; a browser host passes\n * `planegcsUrl` to self-host the `planegcs.wasm` binary.\n */\n sketch?: SketchSolverOptions\n /**\n * Optional dynamic-loading config for third-party faijs libraries (e.g.\n * `@faicad/faijs-gears`, `@faicad/sheetmetal`). Omitted = dynamic loading\n * enabled with unrestricted package set (see `FaiViewerLibsOptions`).\n */\n libs?: FaiViewerLibsOptions\n}\n\n/** One tessellated mesh returned by {@link openFaiView}. */\nexport interface FaiViewerMesh {\n /** Part/terminal name this mesh belongs to. */\n name: string\n /** Interleaved `x,y,z` triangle vertices. */\n positions: Float32Array\n /** Triangle indices (groups of three) into `positions`. */\n indices: Uint32Array\n}\n\n/** Structured failure of a viewer operation. */\nexport interface FaiViewerError {\n code: FaiZipViewerErrorCode\n message: string\n /** Underlying engine error, when the failure was thrown. */\n detail?: unknown\n}\n\n/**\n * Structured resolution code for a viewer failure. Reported through\n * {@link OpenFaiResult.error} rather than thrown, so hosts get an inspectable\n * result object.\n */\nexport type FaiZipViewerErrorCode =\n /** The wasm options were missing or one of the three urls was empty. */\n | 'E_WASM_URL'\n /** The `.fai.zip` bytes failed container validation / read caps. */\n | 'E_CONTAINER'\n /** The model graph failed to execute (see `detail`). */\n | 'E_EXECUTION'\n /** The model produced no visible mesh. */\n | 'E_NO_GEOMETRY'\n\n/** Result of {@link openFaiView}. A failure is reported via `error`, not a throw (except wasm/environment errors, which throw). */\nexport interface OpenFaiResult {\n /** Source file name recorded in conversion provenance, when present. */\n sourceFile?: string\n /** The executed model id (after active model defaulting). */\n modelId: string\n /** The visible, structured mesh list. */\n meshes: FaiViewerMesh[]\n /** Set when reading or executing fails; see `FaiViewerError`. */\n error?: FaiViewerError\n}"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@faicad/faijs-viewer",
|
|
3
|
+
"version": "0.29.3",
|
|
4
|
+
"description": "Read + execute + tessellate a project .fai.zip for any third-party host. v1 API: openFaiZip(bytes, { wasm: { occtUrl, manifoldUrl, brepkitUrl } })",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"faijs",
|
|
7
|
+
"fai",
|
|
8
|
+
"zip",
|
|
9
|
+
"viewer",
|
|
10
|
+
"cad",
|
|
11
|
+
"occt",
|
|
12
|
+
"manifold",
|
|
13
|
+
"three"
|
|
14
|
+
],
|
|
15
|
+
"license": "Apache-2.0",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://gitcode.com/Faicad/faijs.git"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"NOTICE",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@faicad/faijs": "^0.29.0",
|
|
38
|
+
"@faicad/faijs-extra": "^0.29.0",
|
|
39
|
+
"@faicad/faijs-sketch": "^0.29.0",
|
|
40
|
+
"occt-wasm": "3.8.4",
|
|
41
|
+
"three": "^0.162.0 || ^0.184.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@faicad/faijs": "file:../core",
|
|
45
|
+
"@faicad/faijs-extra": "file:../faijs-extra",
|
|
46
|
+
"@faicad/faijs-gears": "file:../faijs-gears",
|
|
47
|
+
"@types/three": "^0.184.1",
|
|
48
|
+
"occt-wasm": "^3.8.4",
|
|
49
|
+
"three": "^0.184.0",
|
|
50
|
+
"typescript": "*",
|
|
51
|
+
"vitest": "*"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsc -p tsconfig.build.json && node ../../scripts/fix-import-extensions.mjs dist",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"lint": "eslint src"
|
|
58
|
+
}
|
|
59
|
+
}
|