@openbim/ifcx 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -10
- package/bundler/fetch-imports.d.ts +53 -0
- package/bundler/fetch-imports.js +137 -0
- package/bundler/openbim_ifcx_wasm.d.ts +139 -0
- package/bundler/openbim_ifcx_wasm.js +10 -0
- package/bundler/openbim_ifcx_wasm_bg.js +481 -0
- package/bundler/openbim_ifcx_wasm_bg.wasm +0 -0
- package/bundler/openbim_ifcx_wasm_bg.wasm.d.ts +18 -0
- package/bundler/package.json +3 -0
- package/fetch-imports.d.ts +53 -0
- package/fetch-imports.js +139 -0
- package/openbim_ifcx_wasm.d.ts +3 -1
- package/openbim_ifcx_wasm.js +1 -0
- package/openbim_ifcx_wasm_bg.wasm +0 -0
- package/package.json +29 -3
- package/web/fetch-imports.d.ts +53 -0
- package/web/fetch-imports.js +137 -0
- package/web/openbim_ifcx_wasm.d.ts +182 -0
- package/web/openbim_ifcx_wasm.js +585 -0
- package/web/openbim_ifcx_wasm_bg.wasm +0 -0
- package/web/openbim_ifcx_wasm_bg.wasm.d.ts +18 -0
- package/web/package.json +3 -0
package/README.md
CHANGED
|
@@ -5,9 +5,16 @@ GLB, from JavaScript and TypeScript. A WebAssembly build of the
|
|
|
5
5
|
[`openbim-ifcx`](https://crates.io/crates/openbim-ifcx) Rust crate, from the
|
|
6
6
|
`openbim-ifcx-wasm` crate in [openbimrs/ifcx](https://github.com/openbimrs/ifcx).
|
|
7
7
|
|
|
8
|
-
Published to npm as `@openbim/ifcx
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
Published to npm as `@openbim/ifcx`: one package for Node 18 and later,
|
|
9
|
+
bundlers (webpack, Rollup) and plain browser pages, with TypeScript
|
|
10
|
+
declarations for each. Every build is tested from the packed tarball,
|
|
11
|
+
the browser builds in headless Chrome.
|
|
12
|
+
|
|
13
|
+
Try it in the browser: the [viewer](https://openbimrs.github.io/ifcx/viewer/)
|
|
14
|
+
composes a file with this package and shows the GLB with three.js, on
|
|
15
|
+
desktop and on phones. The [documentation](https://openbimrs.github.io/ifcx/)
|
|
16
|
+
has a [JavaScript guide](https://openbimrs.github.io/ifcx/guide/javascript)
|
|
17
|
+
and the [API reference](https://openbimrs.github.io/ifcx/reference/crates/openbim-ifcx-wasm).
|
|
11
18
|
|
|
12
19
|
Targets the `ifcx_alpha` draft of buildingSMART's IFC5. IFCX is still a
|
|
13
20
|
moving draft; see the
|
|
@@ -19,12 +26,26 @@ moving draft; see the
|
|
|
19
26
|
npm install @openbim/ifcx
|
|
20
27
|
```
|
|
21
28
|
|
|
22
|
-
##
|
|
29
|
+
## Entry points
|
|
30
|
+
|
|
31
|
+
| Import | Build | Loads the wasm module |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `@openbim/ifcx` in Node (`require` or `import`) | CommonJS (`wasm-bindgen --target nodejs`) | synchronously, on load |
|
|
34
|
+
| `@openbim/ifcx` in a bundler | ES module (`--target bundler`) | through the bundler's WebAssembly support, e.g. webpack 5 `experiments.asyncWebAssembly` |
|
|
35
|
+
| `@openbim/ifcx/web` | ES module (`--target web`) | when you `await init()` |
|
|
36
|
+
|
|
37
|
+
`package.json` `exports` picks the build: the `node` condition gets the
|
|
38
|
+
CommonJS build, everything else the bundler build. Use `@openbim/ifcx/web`
|
|
39
|
+
for a page without a bundler, and for bundlers without WebAssembly ES
|
|
40
|
+
module integration, such as Vite (or add `vite-plugin-wasm`). All three
|
|
41
|
+
export the same API; the `web` build adds the default `init` export (and
|
|
42
|
+
`initSync`).
|
|
43
|
+
|
|
44
|
+
## Node
|
|
23
45
|
|
|
24
46
|
```js
|
|
25
|
-
const { readFileSync } = require("node:fs");
|
|
26
|
-
const {
|
|
27
|
-
const { IfcxFile, compose, exportGlb } = require("@openbim/ifcx");
|
|
47
|
+
const { readFileSync, writeFileSync } = require("node:fs");
|
|
48
|
+
const { IfcxFile, compose, exportGlb } = require("@openbim/ifcx"); // or import
|
|
28
49
|
|
|
29
50
|
const model = readFileSync("model.ifcx"); // a Buffer is a Uint8Array
|
|
30
51
|
const file = IfcxFile.parse(model); // or a string
|
|
@@ -44,6 +65,39 @@ console.log(Object.keys(tree.children)); // the root nodes
|
|
|
44
65
|
writeFileSync("model.glb", exportGlb([model, readFileSync("overlay.ifcx")]));
|
|
45
66
|
```
|
|
46
67
|
|
|
68
|
+
## Browser
|
|
69
|
+
|
|
70
|
+
With a bundler, import the package as usual; the bundler loads the wasm
|
|
71
|
+
module:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
import { compose, exportGlb, fetchImports } from "@openbim/ifcx";
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Without one, serve the package's `web/` directory, map the name in an
|
|
78
|
+
import map (or import the file by URL), and call `init()` once before
|
|
79
|
+
anything else. `init()` fetches `openbim_ifcx_wasm_bg.wasm` from next to
|
|
80
|
+
the module; pass `init({ module_or_path: url })` to load it from elsewhere.
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<script type="importmap">
|
|
84
|
+
{ "imports": { "@openbim/ifcx/web": "/node_modules/@openbim/ifcx/web/openbim_ifcx_wasm.js" } }
|
|
85
|
+
</script>
|
|
86
|
+
<script type="module">
|
|
87
|
+
import init, { IfcxFile, exportGlb, fetchImports, validate } from "@openbim/ifcx/web";
|
|
88
|
+
|
|
89
|
+
await init();
|
|
90
|
+
const url = new URL("models/house.ifcx", location.href);
|
|
91
|
+
const model = new Uint8Array(await (await fetch(url)).arrayBuffer());
|
|
92
|
+
|
|
93
|
+
// Fetch what the model imports, relative to its own URL, then use it.
|
|
94
|
+
const imports = await fetchImports(model, { baseUrl: url });
|
|
95
|
+
const report = validate(model, { imports });
|
|
96
|
+
const glb = exportGlb(model, { imports }); // e.g. for three.js's GLTFLoader.parse
|
|
97
|
+
console.log(IfcxFile.parse(model).header.id, report.valid, glb.length);
|
|
98
|
+
</script>
|
|
99
|
+
```
|
|
100
|
+
|
|
47
101
|
## API
|
|
48
102
|
|
|
49
103
|
| Call | Returns |
|
|
@@ -57,6 +111,8 @@ writeFileSync("model.glb", exportGlb([model, readFileSync("overlay.ifcx")]));
|
|
|
57
111
|
| `compose(layers, options?)` | the composed tree as a `ComposedNode` |
|
|
58
112
|
| `validate(layers, options?)` | a `ValidationReport` over all layers and resolved imports |
|
|
59
113
|
| `exportGlb(layers, options?)` | the composed model as binary glTF 2.0, a `Uint8Array` |
|
|
114
|
+
| `fetchImports(layers, options?)` | a promise of a `Map` from import `uri` to file, for `options.imports` |
|
|
115
|
+
| `init(input?)` | `@openbim/ifcx/web` only: loads the wasm module; await it once first |
|
|
60
116
|
|
|
61
117
|
`layers` is one input or an array of them, weakest first. TypeScript
|
|
62
118
|
declarations ship with the package.
|
|
@@ -79,8 +135,31 @@ a file), the layers become the imports of a synthetic main layer, as
|
|
|
79
135
|
upstream's `ifcx compose` command builds it, and every import must be
|
|
80
136
|
supplied. `integrity` values are checked against the supplied bytes. In
|
|
81
137
|
upstream order an import overrides the layer that imports it, and a later
|
|
82
|
-
layer overrides both.
|
|
83
|
-
filesystem.
|
|
138
|
+
layer overrides both. The WebAssembly module never touches the network or
|
|
139
|
+
the filesystem.
|
|
140
|
+
|
|
141
|
+
**Fetching imports.** `fetchImports(layers, options?)` collects them for
|
|
142
|
+
you, in JavaScript: it reads each layer's `imports`, fetches every `uri`
|
|
143
|
+
(recursively, each once, concurrently) and resolves to a `Map` keyed by
|
|
144
|
+
the exact import `uri`, ready for `options.imports`.
|
|
145
|
+
|
|
146
|
+
- `baseUrl`: what relative URIs of the given layers resolve against;
|
|
147
|
+
`location.href` by default in a browser. A fetched file's own imports
|
|
148
|
+
resolve against its URL.
|
|
149
|
+
- `fetch`: the function that loads a URL, the global `fetch` by default.
|
|
150
|
+
Pass your own for credentials, a cache, a mirror, or Node files:
|
|
151
|
+
`{ fetch: async (url) => readFile(new URL(url)) }`. It may return a
|
|
152
|
+
`Response`, a `Uint8Array`, an `ArrayBuffer` or a string. Without a
|
|
153
|
+
`baseUrl` it receives a relative `uri` unchanged.
|
|
154
|
+
- `imports`: files already at hand, never fetched again.
|
|
155
|
+
- `signal`: an `AbortSignal` passed to every fetch.
|
|
156
|
+
|
|
157
|
+
The in-memory map keys a file by its exact `uri`, as upstream's
|
|
158
|
+
`InMemoryLayerProvider` does, so two different files imported under the
|
|
159
|
+
same relative `uri` from different directories cannot both be supplied;
|
|
160
|
+
the first one fetched is used. Anything that cannot be fetched rejects with
|
|
161
|
+
an `IfcxError` with code `fetch`; `integrity` and import cycles are checked
|
|
162
|
+
by `compose`, `validate` and `exportGlb`.
|
|
84
163
|
|
|
85
164
|
**GLB export.** `exportGlb` writes the render scene of the composed tree:
|
|
86
165
|
transformed, instanced meshes, lines and points with their materials, one
|
|
@@ -111,6 +190,7 @@ Every failure throws an `Error` with `name === "IfcxError"` and a stable
|
|
|
111
190
|
| `compose` | a reference cycle, or a reference to a node no layer defines |
|
|
112
191
|
| `invalid-argument` | an argument of the wrong type, or no layers |
|
|
113
192
|
| `glb` | the scene could not be written as GLB (over 4 GiB) |
|
|
193
|
+
| `fetch` | `fetchImports` could not fetch an import, or got no file back |
|
|
114
194
|
|
|
115
195
|
A code is never renamed or reused.
|
|
116
196
|
|
|
@@ -118,9 +198,17 @@ A code is never renamed or reused.
|
|
|
118
198
|
|
|
119
199
|
```sh
|
|
120
200
|
cargo install wasm-bindgen-cli --version 0.2.128 --locked
|
|
121
|
-
crates/openbim-ifcx-wasm/scripts/build-
|
|
201
|
+
crates/openbim-ifcx-wasm/scripts/build-npm-pkg.sh # builds pkg/, runs every check
|
|
122
202
|
```
|
|
123
203
|
|
|
204
|
+
The script binds one release build three times (`pkg/`, `pkg/bundler/`,
|
|
205
|
+
`pkg/web/`), runs the Node suite, then `npm pack`s the package and checks
|
|
206
|
+
each entry point from the tarball: Node `require` and `import`, a webpack
|
|
207
|
+
bundle, and the `web` and bundler builds in headless Chrome, which parse,
|
|
208
|
+
validate, compose, fetch imports and export GLB from the repository's
|
|
209
|
+
fixtures. It finds Chrome through `CHROME_BIN`, `PATH` or a Playwright
|
|
210
|
+
download; `IFCX_SKIP_BROWSER=1` skips the browser part with a warning.
|
|
211
|
+
|
|
124
212
|
## License
|
|
125
213
|
|
|
126
214
|
MIT, like the rest of `openbimrs/ifcx`.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { IfcxInput } from "./openbim_ifcx_wasm.js";
|
|
2
|
+
|
|
3
|
+
/** The part of a `fetch` `Response` that `fetchImports` reads. */
|
|
4
|
+
export interface FetchedResponse {
|
|
5
|
+
ok?: boolean;
|
|
6
|
+
status?: number;
|
|
7
|
+
statusText?: string;
|
|
8
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Loads one imported file. `fetch` itself fits; a custom function may also
|
|
13
|
+
* return the file directly. `url` is the import `uri` resolved against the
|
|
14
|
+
* importing file's URL (or `baseUrl`); when a relative `uri` has no base to
|
|
15
|
+
* resolve against, a custom function receives the `uri` unchanged.
|
|
16
|
+
*/
|
|
17
|
+
export type ImportFetcher = (
|
|
18
|
+
url: string,
|
|
19
|
+
init?: { signal?: AbortSignal },
|
|
20
|
+
) => Promise<FetchedResponse | IfcxInput | ArrayBuffer>;
|
|
21
|
+
|
|
22
|
+
/** Options for `fetchImports`. */
|
|
23
|
+
export interface FetchImportsOptions {
|
|
24
|
+
/**
|
|
25
|
+
* URL that relative import URIs of the given layers resolve against.
|
|
26
|
+
* Defaults to `location.href` in a browser. Imports of a fetched file
|
|
27
|
+
* resolve against that file's URL.
|
|
28
|
+
*/
|
|
29
|
+
baseUrl?: string | URL;
|
|
30
|
+
/** Loads each file. Defaults to the global `fetch`. */
|
|
31
|
+
fetch?: ImportFetcher;
|
|
32
|
+
/** Files already at hand, keyed by import `uri`; never fetched again. */
|
|
33
|
+
imports?: Record<string, IfcxInput> | Map<string, IfcxInput>;
|
|
34
|
+
/** Passed to every fetch call. */
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Fetch every file the layers import, recursively and each `uri` once, and
|
|
40
|
+
* return them keyed by the exact import `uri`: the `imports` option of
|
|
41
|
+
* `compose`, `validate` and `exportGlb`. Network access happens here, in
|
|
42
|
+
* JavaScript; the WebAssembly module never fetches anything. A file that
|
|
43
|
+
* cannot be fetched rejects with an `IfcxError` whose `code` is `"fetch"`.
|
|
44
|
+
*
|
|
45
|
+
* ```js
|
|
46
|
+
* const imports = await fetchImports(layers, { baseUrl: "https://example.org/model/" });
|
|
47
|
+
* const tree = compose(layers, { imports });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function fetchImports(
|
|
51
|
+
layers: IfcxInput | IfcxInput[],
|
|
52
|
+
options?: FetchImportsOptions,
|
|
53
|
+
): Promise<Map<string, IfcxInput>>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Import resolution over `fetch`, for the `imports` option of `compose`,
|
|
2
|
+
// `validate` and `exportGlb`.
|
|
3
|
+
//
|
|
4
|
+
// Plain JavaScript on purpose: the Rust crates perform no network access
|
|
5
|
+
// (ADR 0002), so the binding resolves imports in memory only and this
|
|
6
|
+
// module does the fetching on the JavaScript side. It reads each layer's
|
|
7
|
+
// `imports` list, fetches every named file (recursively, each URI once) and
|
|
8
|
+
// returns the files keyed by their exact import `uri`, which is what the
|
|
9
|
+
// in-memory `imports` option expects. Integrity values are checked later,
|
|
10
|
+
// by the binding, against the fetched bytes.
|
|
11
|
+
//
|
|
12
|
+
// This file is the ES module source. scripts/build-npm-pkg.sh copies it into
|
|
13
|
+
// the `web` and `bundler` builds and derives the CommonJS copy for the Node
|
|
14
|
+
// build from it, so it must stay self-contained: no imports, and every
|
|
15
|
+
// export a top-level `export async function` or `export function`.
|
|
16
|
+
|
|
17
|
+
/** The imports of an IFCX file, or none when it is not readable JSON. */
|
|
18
|
+
function importUris(input) {
|
|
19
|
+
let text = input;
|
|
20
|
+
if (typeof input !== "string") {
|
|
21
|
+
text = new TextDecoder().decode(input);
|
|
22
|
+
}
|
|
23
|
+
let file;
|
|
24
|
+
try {
|
|
25
|
+
file = JSON.parse(text);
|
|
26
|
+
} catch {
|
|
27
|
+
// Not JSON: the binding call that receives it reports where.
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const imports = file !== null && typeof file === "object" ? file.imports : undefined;
|
|
31
|
+
if (!Array.isArray(imports)) return [];
|
|
32
|
+
return imports
|
|
33
|
+
.map((entry) => (entry !== null && typeof entry === "object" ? entry.uri : undefined))
|
|
34
|
+
.filter((uri) => typeof uri === "string");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function fetchError(message, cause) {
|
|
38
|
+
const error = new Error(message, cause === undefined ? undefined : { cause });
|
|
39
|
+
error.name = "IfcxError";
|
|
40
|
+
error.code = "fetch";
|
|
41
|
+
return error;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isInput(value) {
|
|
45
|
+
return typeof value === "string" || value instanceof Uint8Array;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The URL `uri` names, relative to `base`; `undefined` if it cannot be resolved. */
|
|
49
|
+
function resolveUrl(uri, base) {
|
|
50
|
+
try {
|
|
51
|
+
return base === undefined ? new URL(uri).href : new URL(uri, base).href;
|
|
52
|
+
} catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** What a fetch function returned, as an IFCX input. */
|
|
58
|
+
async function readResponse(response, uri, url) {
|
|
59
|
+
if (isInput(response)) return response;
|
|
60
|
+
if (response instanceof ArrayBuffer) return new Uint8Array(response);
|
|
61
|
+
if (response !== null && typeof response === "object" && typeof response.arrayBuffer === "function") {
|
|
62
|
+
if (response.ok === false) {
|
|
63
|
+
const status = [response.status, response.statusText].filter(Boolean).join(" ");
|
|
64
|
+
throw fetchError(`could not fetch import ${JSON.stringify(uri)} from ${url}: HTTP ${status}`);
|
|
65
|
+
}
|
|
66
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
67
|
+
}
|
|
68
|
+
throw fetchError(
|
|
69
|
+
`the fetch function returned neither a Response, a Uint8Array, an ArrayBuffer nor a string for ${JSON.stringify(uri)}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Fetch every file the layers import, recursively, keyed by the exact
|
|
75
|
+
* import `uri`, for the `imports` option of `compose`, `validate` and
|
|
76
|
+
* `exportGlb`.
|
|
77
|
+
*/
|
|
78
|
+
export async function fetchImports(layers, options = {}) {
|
|
79
|
+
const list = Array.isArray(layers) ? layers : [layers];
|
|
80
|
+
for (const layer of list) {
|
|
81
|
+
if (!isInput(layer)) {
|
|
82
|
+
throw fetchError("every layer must be a string or a Uint8Array");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
86
|
+
if (typeof fetcher !== "function") {
|
|
87
|
+
throw fetchError("no fetch function: pass options.fetch");
|
|
88
|
+
}
|
|
89
|
+
const custom = options.fetch !== undefined;
|
|
90
|
+
const base =
|
|
91
|
+
options.baseUrl !== undefined ? String(options.baseUrl) : globalThis.location?.href;
|
|
92
|
+
const init = options.signal === undefined ? undefined : { signal: options.signal };
|
|
93
|
+
|
|
94
|
+
const found = new Map();
|
|
95
|
+
if (options.imports !== undefined) {
|
|
96
|
+
const known = options.imports instanceof Map ? options.imports : Object.entries(options.imports);
|
|
97
|
+
for (const [uri, file] of known) found.set(uri, file);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Breadth first; each level's fetches run concurrently. `url` is where a
|
|
101
|
+
// layer came from, so its relative imports resolve against it.
|
|
102
|
+
let level = list.map((file) => ({ file, url: base }));
|
|
103
|
+
for (const [, file] of found) level.push({ file, url: base });
|
|
104
|
+
while (level.length > 0) {
|
|
105
|
+
const pending = [];
|
|
106
|
+
for (const { file, url: importer } of level) {
|
|
107
|
+
for (const uri of importUris(file)) {
|
|
108
|
+
if (found.has(uri)) continue;
|
|
109
|
+
found.set(uri, undefined); // claimed: fetch each URI once
|
|
110
|
+
const url = resolveUrl(uri, importer);
|
|
111
|
+
if (url === undefined && !custom) {
|
|
112
|
+
throw fetchError(
|
|
113
|
+
`cannot resolve import ${JSON.stringify(uri)} to a URL: pass options.baseUrl`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
pending.push(
|
|
117
|
+
(async () => {
|
|
118
|
+
let response;
|
|
119
|
+
try {
|
|
120
|
+
response = await fetcher(url ?? uri, init);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw fetchError(
|
|
123
|
+
`could not fetch import ${JSON.stringify(uri)} from ${url ?? uri}: ${error?.message ?? error}`,
|
|
124
|
+
error,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const fetched = await readResponse(response, uri, url ?? uri);
|
|
128
|
+
found.set(uri, fetched);
|
|
129
|
+
return { file: fetched, url: url ?? importer };
|
|
130
|
+
})(),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
level = await Promise.all(pending);
|
|
135
|
+
}
|
|
136
|
+
return found;
|
|
137
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/** An IFCX file as JSON text or as its UTF-8 bytes. */
|
|
5
|
+
export type IfcxInput = string | Uint8Array;
|
|
6
|
+
|
|
7
|
+
/** Options for `compose` and `validate`. */
|
|
8
|
+
export interface LayerOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Files that imports name, keyed by the exact import `uri`. When given
|
|
11
|
+
* (even empty), imports are resolved as upstream does and every import
|
|
12
|
+
* must be here; without it, imports are not resolved.
|
|
13
|
+
*/
|
|
14
|
+
imports?: Record<string, IfcxInput> | Map<string, IfcxInput>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** One node of a composed tree. The root has path `""`. */
|
|
18
|
+
export interface ComposedNode {
|
|
19
|
+
path: string;
|
|
20
|
+
attributes: Record<string, unknown>;
|
|
21
|
+
children: Record<string, ComposedNode>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The `kind` of a validation failure. */
|
|
25
|
+
export type ValidationFailureKind =
|
|
26
|
+
| "missing-schema"
|
|
27
|
+
| "unknown-inherited-schema"
|
|
28
|
+
| "inheritance-cycle"
|
|
29
|
+
| "type-mismatch"
|
|
30
|
+
| "not-an-integer"
|
|
31
|
+
| "not-an-option"
|
|
32
|
+
| "missing-key"
|
|
33
|
+
| "too-few-elements"
|
|
34
|
+
| "too-many-elements"
|
|
35
|
+
| "missing-restrictions"
|
|
36
|
+
| "unknown-data-type"
|
|
37
|
+
| "other";
|
|
38
|
+
|
|
39
|
+
/** One attribute value that does not match its schema. */
|
|
40
|
+
export interface ValidationFailure {
|
|
41
|
+
/** Path of the node carrying the attribute. */
|
|
42
|
+
node: string;
|
|
43
|
+
/** Attribute id, which is also the schema id. */
|
|
44
|
+
attribute: string;
|
|
45
|
+
/** RFC 6901 JSON pointer into the value; `""` for the value itself. */
|
|
46
|
+
pointer: string;
|
|
47
|
+
kind: ValidationFailureKind;
|
|
48
|
+
message: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ValidationReport {
|
|
52
|
+
valid: boolean;
|
|
53
|
+
failures: ValidationFailure[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Compose layers, weakest first, into a tree of plain objects: the
|
|
58
|
+
* artificial root (path `""`) over every root node.
|
|
59
|
+
*/
|
|
60
|
+
export function compose(layers: IfcxInput | IfcxInput[], options?: LayerOptions): ComposedNode;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check the attributes of the layers, weakest first, against the schemas of
|
|
64
|
+
* every layer (and of resolved imports), after merging nodes that share a
|
|
65
|
+
* path.
|
|
66
|
+
*/
|
|
67
|
+
export function validate(layers: IfcxInput | IfcxInput[], options?: LayerOptions): ValidationReport;
|
|
68
|
+
|
|
69
|
+
/** Options for `exportGlb`. */
|
|
70
|
+
export interface GlbOptions extends LayerOptions {
|
|
71
|
+
/** Rotate IFCX's Z-up axes to glTF's Y-up. Default `true`. */
|
|
72
|
+
yUp?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Keep the model's coordinates by putting the scene origin on the root
|
|
75
|
+
* node. With `false` the model sits around the glTF origin, which is then
|
|
76
|
+
* only recorded in the root's `extras.ifcxOrigin`. Default `true`.
|
|
77
|
+
*/
|
|
78
|
+
originOnRoot?: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Compose layers, weakest first, and write their render scene as a binary
|
|
83
|
+
* glTF 2.0 (`.glb`) file: transformed, instanced meshes, lines and points
|
|
84
|
+
* with their materials, one glTF node per instance named by its IFCX path.
|
|
85
|
+
*/
|
|
86
|
+
export function exportGlb(layers: IfcxInput | IfcxInput[], options?: GlbOptions): Uint8Array;
|
|
87
|
+
|
|
88
|
+
/** The `code` of an `IfcxError`. */
|
|
89
|
+
export type IfcxErrorCode =
|
|
90
|
+
| "read"
|
|
91
|
+
| "write"
|
|
92
|
+
| "layer"
|
|
93
|
+
| "compose"
|
|
94
|
+
| "invalid-argument"
|
|
95
|
+
| "glb"
|
|
96
|
+
| "fetch";
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* One IFCX file. Writing it back is lossless: key order, unknown fields,
|
|
102
|
+
* `null` deletions and every number stay as read.
|
|
103
|
+
*
|
|
104
|
+
* A newtype because `wasm_bindgen` can only export a type defined in this
|
|
105
|
+
* crate; all state and behaviour are the core's.
|
|
106
|
+
*/
|
|
107
|
+
export class IfcxFile {
|
|
108
|
+
private constructor();
|
|
109
|
+
free(): void;
|
|
110
|
+
[Symbol.dispose](): void;
|
|
111
|
+
/**
|
|
112
|
+
* Read an IFCX file from its JSON text or UTF-8 bytes.
|
|
113
|
+
*/
|
|
114
|
+
static parse(input: IfcxInput): IfcxFile;
|
|
115
|
+
/**
|
|
116
|
+
* The file as a plain object, so `JSON.stringify(file)` works. Unlike
|
|
117
|
+
* `write`, a plain object may reorder integer-like keys and round
|
|
118
|
+
* integers beyond 2^53.
|
|
119
|
+
*/
|
|
120
|
+
toJSON(): Record<string, unknown>;
|
|
121
|
+
/**
|
|
122
|
+
* Check every attribute against this file's own `schemas`. Imports are
|
|
123
|
+
* not resolved; use the `validate` function to include them.
|
|
124
|
+
*/
|
|
125
|
+
validate(): ValidationReport;
|
|
126
|
+
/**
|
|
127
|
+
* The file as JSON text; `pretty` indents it by two spaces.
|
|
128
|
+
*/
|
|
129
|
+
write(pretty?: boolean | null): string;
|
|
130
|
+
/**
|
|
131
|
+
* The `header` object.
|
|
132
|
+
*/
|
|
133
|
+
readonly header: Record<string, unknown>;
|
|
134
|
+
/**
|
|
135
|
+
* Number of entries in `data`; several may share a path.
|
|
136
|
+
*/
|
|
137
|
+
readonly nodeCount: number;
|
|
138
|
+
}
|
|
139
|
+
export * from "./fetch-imports.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* @ts-self-types="./openbim_ifcx_wasm.d.ts" */
|
|
2
|
+
import * as wasm from "./openbim_ifcx_wasm_bg.wasm";
|
|
3
|
+
import { __wbg_set_wasm } from "./openbim_ifcx_wasm_bg.js";
|
|
4
|
+
|
|
5
|
+
__wbg_set_wasm(wasm);
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
IfcxFile, compose, exportGlb, validate
|
|
9
|
+
} from "./openbim_ifcx_wasm_bg.js";
|
|
10
|
+
export { fetchImports } from "./fetch-imports.js";
|