ldr 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -3
- package/dist/cdn.d.ts +2 -1
- package/dist/cli.js +55 -17
- package/dist/embedded.d.ts +2 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +539 -25
- package/dist/instructions.d.ts +80 -0
- package/dist/load-worker.gen.d.ts +1 -1
- package/dist/model.d.ts +23 -1
- package/dist/node/index.js +556 -34
- package/dist/parts-cdn.d.ts +2 -0
- package/dist/render.d.ts +1 -1
- package/dist/scene.d.ts +3 -2
- package/dist/spec.d.ts +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ Light the scene (or set `scene.environment`) as you would for any `MeshStandardM
|
|
|
102
102
|
| `resolveModel(name)` | – | Resolves an external reference to a `Doc`, such as a model loaded separately. Return `null` to treat it as a part. |
|
|
103
103
|
| `maxDepth` | `64` | Submodel nesting limit (cycle guard). |
|
|
104
104
|
|
|
105
|
-
MPD-embedded parts, such as Studio/OMR unofficial parts or stickers, become part instances (`embedded: true`) and override library parts of the same name.
|
|
105
|
+
MPD-embedded parts, such as Studio/OMR unofficial parts or stickers, become part instances (`embedded: true`) and override library parts of the same name. `loadModel` asks the parts CDN for them by a hash of their definition (`embeddedKeys`), so a CDN built with `--embedded <model dir>` serves the embedded parts of those models (the demo CDN carries the ones in its 1,773 catalog models).
|
|
106
106
|
|
|
107
107
|
**Packs and rendering**
|
|
108
108
|
|
|
@@ -143,6 +143,15 @@ MPD-embedded parts, such as Studio/OMR unofficial parts or stickers, become part
|
|
|
143
143
|
|
|
144
144
|
`setStep` doesn't rebuild anything: it compacts instance buffers, and culling is step-aware (a stud covered only by a later step's part stays visible). `stats` reflect the shown step.
|
|
145
145
|
|
|
146
|
+
**Building instructions.** `parseModel(text, name, { callouts: true })` (or `loadModel(url, { callouts: true })`) sequences a
|
|
147
|
+
submodel placed N times in one step like a booklet: the first copy is built step by step (its instances get `mult = N`, the "xN"),
|
|
148
|
+
the other copies appear finished at the end of that step. `planStepViews(instances, pack.header.parts, stepOf, stepCount)` plans a
|
|
149
|
+
camera per step (`view(step)` → `{ dir, target, radius, minVis, meanVis, below, isolate }`, LDraw space): the booklet view (30° up,
|
|
150
|
+
45° round) while it shows the new parts at least half visible on average, otherwise the best of 24 views from above (or from below for
|
|
151
|
+
parts added underneath), preferring front three-quarter views and small turns (thresholds from Agrawala et al., SIGGRAPH 2003).
|
|
152
|
+
`isolate` is set while a sub-assembly is being built: show it on its own with `setStep(step, isolateMask(instances, view.isolate))`.
|
|
153
|
+
Visibility is measured on part bounding boxes, so it needs no GPU and also runs in Node.
|
|
154
|
+
|
|
146
155
|
### `ldr/node` (Node / Bun only)
|
|
147
156
|
|
|
148
157
|
Everything above, plus:
|
|
@@ -202,7 +211,7 @@ Reproduce: `cd bench && bun run setup && bun run perf` (regression gate) or `bun
|
|
|
202
211
|
|
|
203
212
|
- **Transparency:** transparent colours render opaque.
|
|
204
213
|
- **Opt-in pack features:** conditional lines need packs built with `cond`. Printed parts show their images only from `textures` packs (the embedded PNGs dominate the pack size); default packs draw the FALLBACK geometry.
|
|
205
|
-
- **ROTSTEP** is reported (`stepViews`, `instance.view`), not applied
|
|
214
|
+
- **ROTSTEP** is reported (`stepViews`, `instance.view`), not applied; `planStepViews` takes a `fixed(step)` override if you want to honour it.
|
|
206
215
|
- **Packs are static:** they hold only the parts they were built for. A model that uses parts missing from the pack lists them in `stats.missing`.
|
|
207
216
|
- **Occlusion culling:** beyond hidden studs and undersides there is no occlusion culling (bricks enclosed by other bricks are still drawn). Frustum culling and LOD apply per spatial cluster.
|
|
208
217
|
- **LOD and edges:** edge LOD drops sub-pixel edge segments and hides edges where bricks are a fraction of a pixel, which visibly lightens far-away views drawn with edges (up to 4.7% of pixels on Datsville overviews). Set `lodEdgeMinPx: 0, lodEdgePx: 0` to keep every edge (much slower on large models), or `lod: false` to turn LOD off entirely.
|
|
@@ -212,4 +221,4 @@ Reproduce: `cd bench && bun run setup && bun run perf` (regression gate) or `bun
|
|
|
212
221
|
- **Code:** MIT © Evan Bacon.
|
|
213
222
|
- **LDraw parts:** packs you build contain geometry from the **LDraw.org Parts Library**, which is licensed **CC BY 2.0** (newer files CC BY 4.0, per file header). If you ship packs, credit "LDraw.org Parts Library (CC BY 2.0)" in your app. See <https://www.ldraw.org/legal-info>.
|
|
214
223
|
- **Datsville:** the benchmark model is by [mjhorvath/Datsville](https://github.com/mjhorvath/Datsville) (CC BY-SA 4.0). It is used for benchmarking only and is not redistributed.
|
|
215
|
-
- **Trademarks:** LEGO® is a trademark of the LEGO Group, which does not sponsor, authorize or endorse this project. LDraw™ is a trademark of the Estate of James Jessiman.
|
|
224
|
+
- **Trademarks:** LEGO® is a trademark of the LEGO Group, which does not sponsor, authorize or endorse this project; ldr is not affiliated with the LEGO Group. LDraw™ is a trademark of the Estate of James Jessiman.
|
package/dist/cdn.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface LoadedModel extends LdrObject {
|
|
|
24
24
|
/**
|
|
25
25
|
* One call from an `.ldr` / `.mpd` URL (or text) to a three.js object: fetches the model, fetches just the parts it uses from the
|
|
26
26
|
* parts CDN and builds an instanced `LdrObject`. No LDraw library or parts pack needed.
|
|
27
|
-
*
|
|
27
|
+
* MPD-embedded parts are fetched by content key (`emb/<hash>.dat`, see embedded.ts): a CDN built with `--embedded` serves the ones in
|
|
28
|
+
* known models. Embedded parts the CDN lacks, and loose geometry in model files, are skipped (listed in `stats.missing`).
|
|
28
29
|
*/
|
|
29
30
|
export declare function loadModel(source: string, o?: LoadModelOptions): Promise<LoadedModel>;
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// ../../../../../../../tmp/ldr-build-
|
|
3
|
+
// ../../../../../../../tmp/ldr-build-eZnTdy/src/cli.ts
|
|
4
4
|
import fs4 from "node:fs";
|
|
5
5
|
import zlib from "node:zlib";
|
|
6
6
|
|
|
7
|
-
// ../../../../../../../tmp/ldr-build-
|
|
7
|
+
// ../../../../../../../tmp/ldr-build-eZnTdy/src/node/build-pack.ts
|
|
8
8
|
import fs3 from "node:fs";
|
|
9
9
|
import path3 from "node:path";
|
|
10
10
|
|
|
11
|
-
// ../../../../../../../tmp/ldr-build-
|
|
11
|
+
// ../../../../../../../tmp/ldr-build-eZnTdy/src/node/resolver.ts
|
|
12
12
|
import fs from "node:fs";
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
|
|
15
|
-
// ../../../../../../../tmp/ldr-build-
|
|
15
|
+
// ../../../../../../../tmp/ldr-build-eZnTdy/src/spec.ts
|
|
16
16
|
var norm = (s) => s.trim().toLowerCase().replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
17
17
|
function parseColor(tok) {
|
|
18
18
|
if (/^0x/i.test(tok))
|
|
@@ -469,6 +469,7 @@ function transformTexMap(map, m) {
|
|
|
469
469
|
const x = (q) => [m[3] * q[0] + m[4] * q[1] + m[5] * q[2] + m[0], m[6] * q[0] + m[7] * q[1] + m[8] * q[2] + m[1], m[9] * q[0] + m[10] * q[1] + m[11] * q[2] + m[2]];
|
|
470
470
|
return { ...map, p1: x(map.p1), p2: x(map.p2), p3: x(map.p3) };
|
|
471
471
|
}
|
|
472
|
+
var STUB_OVERRIDES = { "32531.dat": "32531a.dat" };
|
|
472
473
|
function mul(a, b) {
|
|
473
474
|
const [ax, ay, az, a0, a1, a2, a3, a4, a5, a6, a7, a8] = a, [bx, by, bz, b0, b1, b2, b3, b4, b5, b6, b7, b8] = b;
|
|
474
475
|
return [
|
|
@@ -704,12 +705,15 @@ function fileStepViews(f) {
|
|
|
704
705
|
return out;
|
|
705
706
|
}
|
|
706
707
|
|
|
707
|
-
// ../../../../../../../tmp/ldr-build-
|
|
708
|
+
// ../../../../../../../tmp/ldr-build-eZnTdy/src/model.ts
|
|
709
|
+
var CALLOUT_END = 999999;
|
|
710
|
+
var stepKey = (steps) => steps.map((x) => String(x).padStart(6, "0")).join(".") + "~";
|
|
708
711
|
function flatten(doc, opts = {}) {
|
|
709
712
|
const out = [];
|
|
710
713
|
const maxDepth = opts.maxDepth ?? 64;
|
|
711
|
-
const loose = opts.looseGeometry !== false, tx = !!opts.textures;
|
|
714
|
+
const loose = opts.looseGeometry !== false, tx = !!opts.textures, co = !!opts.callouts;
|
|
712
715
|
const src = new Map;
|
|
716
|
+
let subs = 0;
|
|
713
717
|
const lazy = (f, k) => {
|
|
714
718
|
const key = f.name + "#" + k;
|
|
715
719
|
let fn = src.get(key);
|
|
@@ -720,16 +724,44 @@ function flatten(doc, opts = {}) {
|
|
|
720
724
|
}
|
|
721
725
|
return fn;
|
|
722
726
|
};
|
|
723
|
-
const walk = (d, fname, m, color, path, stepPath, depth, texIn) => {
|
|
727
|
+
const walk = (d, fname, m, color, path, stepPath, depth, texIn, fixed = null, subPath = [], subMats = []) => {
|
|
724
728
|
const f = d.files.get(fname);
|
|
725
729
|
if (!f || depth > maxDepth)
|
|
726
730
|
return;
|
|
727
731
|
let s = 0;
|
|
732
|
+
const subId = depth > 0 ? ++subs : 0;
|
|
733
|
+
const sp0 = co && subId ? [...subPath, subId] : subPath;
|
|
734
|
+
const sm0 = co && subId ? [...subMats, m] : subMats;
|
|
735
|
+
const groups = co && !fixed ? new Map : null;
|
|
736
|
+
let ordStep = -1, ord = 0;
|
|
737
|
+
const sub = (sd, file, mm, col, sp, lt) => {
|
|
738
|
+
if (ordStep !== s) {
|
|
739
|
+
ordStep = s;
|
|
740
|
+
ord = 0;
|
|
741
|
+
}
|
|
742
|
+
const g = groups?.get(s + "|" + file);
|
|
743
|
+
if (g) {
|
|
744
|
+
g.n++;
|
|
745
|
+
return walk(sd, sd === d ? file : sd.main, mm, col, path + "/" + file, [...sp, CALLOUT_END], depth + 1, lt, [...sp, CALLOUT_END], sp0, sm0);
|
|
746
|
+
}
|
|
747
|
+
const spo = [...sp, ord++], from = out.length;
|
|
748
|
+
walk(sd, sd === d ? file : sd.main, mm, col, path + "/" + file, spo, depth + 1, lt, fixed, sp0, sm0);
|
|
749
|
+
groups?.set(s + "|" + file, { n: 1, from, to: out.length });
|
|
750
|
+
};
|
|
728
751
|
const views = fileStepViews(f);
|
|
729
752
|
const ls = loose ? looseSteps(f) : [];
|
|
730
753
|
let li = 0;
|
|
731
754
|
const emit = (part, col, mm, sp, k, embedded, tex) => {
|
|
755
|
+
if (fixed)
|
|
756
|
+
sp = fixed;
|
|
732
757
|
const i = { part, color: col, m: mm, step: sp[0], steps: sp, localStep: k, path };
|
|
758
|
+
if (subId) {
|
|
759
|
+
i.sub = subId;
|
|
760
|
+
if (co) {
|
|
761
|
+
i.subPath = sp0;
|
|
762
|
+
i.subMats = sm0;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
733
765
|
if (embedded)
|
|
734
766
|
i.embedded = true;
|
|
735
767
|
if (tex) {
|
|
@@ -775,7 +807,7 @@ function flatten(doc, opts = {}) {
|
|
|
775
807
|
const lt = tex && local ? transformTexMap(tex, local) : null;
|
|
776
808
|
if (d.files.has(c.file)) {
|
|
777
809
|
if (fileKind(d, c.file) === "model") {
|
|
778
|
-
|
|
810
|
+
sub(d, c.file, mm, col, sp, lt);
|
|
779
811
|
continue;
|
|
780
812
|
}
|
|
781
813
|
emit(c.file, col, mm, sp, s, true, lt);
|
|
@@ -783,12 +815,18 @@ function flatten(doc, opts = {}) {
|
|
|
783
815
|
}
|
|
784
816
|
const ext = opts.resolveModel?.(c.file);
|
|
785
817
|
if (ext) {
|
|
786
|
-
|
|
818
|
+
sub(ext, c.file, mm, col, sp, lt);
|
|
787
819
|
continue;
|
|
788
820
|
}
|
|
789
821
|
emit(c.file, col, mm, sp, s, false, lt);
|
|
790
822
|
}
|
|
791
823
|
flushLoose(Infinity);
|
|
824
|
+
if (groups) {
|
|
825
|
+
for (const g of groups.values())
|
|
826
|
+
if (g.n > 1)
|
|
827
|
+
for (let j = g.from;j < g.to; j++)
|
|
828
|
+
out[j].mult ??= g.n;
|
|
829
|
+
}
|
|
792
830
|
};
|
|
793
831
|
if (fileKind(doc, doc.main) === "part") {
|
|
794
832
|
const f = doc.files.get(doc.main);
|
|
@@ -803,7 +841,7 @@ function parseModel(text, name = "main.ldr", opts) {
|
|
|
803
841
|
return flatten(parse(text, name, { geometryText: true }), opts);
|
|
804
842
|
}
|
|
805
843
|
function sequenceSteps(insts) {
|
|
806
|
-
const key = (i) => i.steps
|
|
844
|
+
const key = (i) => stepKey(i.steps);
|
|
807
845
|
const uniq = [...new Set(insts.map(key))].sort();
|
|
808
846
|
const rank = new Map(uniq.map((k, j) => [k, j]));
|
|
809
847
|
const stepOf = new Int32Array(insts.length);
|
|
@@ -826,7 +864,7 @@ function parseLDConfig(text) {
|
|
|
826
864
|
return out;
|
|
827
865
|
}
|
|
828
866
|
|
|
829
|
-
// ../../../../../../../tmp/ldr-build-
|
|
867
|
+
// ../../../../../../../tmp/ldr-build-eZnTdy/src/node/resolver.ts
|
|
830
868
|
var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
|
|
831
869
|
var UNDER_STUD_RE = /^(stud3|stud3a|stud4.*|stud12|stud16.*|stud18a)\.dat$/i;
|
|
832
870
|
var norm2 = (ref) => ref.trim().replace(/\\/g, "/").toLowerCase();
|
|
@@ -896,7 +934,7 @@ class Library {
|
|
|
896
934
|
return key;
|
|
897
935
|
}
|
|
898
936
|
resolve(ref) {
|
|
899
|
-
const
|
|
937
|
+
const n0 = norm2(ref), n = STUB_OVERRIDES[n0] ?? n0;
|
|
900
938
|
if (this.virtual.size && this.virtual.has(n))
|
|
901
939
|
return "mpd/" + n;
|
|
902
940
|
for (const pre of ["parts/", "p/", "models/"])
|
|
@@ -1096,11 +1134,11 @@ function collectDeps(lib, refs) {
|
|
|
1096
1134
|
return out;
|
|
1097
1135
|
}
|
|
1098
1136
|
|
|
1099
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1137
|
+
// ../../../../../../../tmp/ldr-build-eZnTdy/src/node/compile.ts
|
|
1100
1138
|
import fs2 from "node:fs";
|
|
1101
1139
|
import path2 from "node:path";
|
|
1102
1140
|
|
|
1103
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1141
|
+
// ../../../../../../../tmp/ldr-build-eZnTdy/src/node/lod.ts
|
|
1104
1142
|
import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
|
|
1105
1143
|
await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
|
|
1106
1144
|
function reorderMesh(P, N, C, I) {
|
|
@@ -1292,7 +1330,7 @@ function lodChain(P, C, I) {
|
|
|
1292
1330
|
return out;
|
|
1293
1331
|
}
|
|
1294
1332
|
|
|
1295
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1333
|
+
// ../../../../../../../tmp/ldr-build-eZnTdy/src/node/compile.ts
|
|
1296
1334
|
function loadColors(root) {
|
|
1297
1335
|
const map = new Map;
|
|
1298
1336
|
for (const l of fs2.readFileSync(path2.join(root, "LDConfig.ldr"), "latin1").split(/\r?\n/)) {
|
|
@@ -1875,7 +1913,7 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
|
|
|
1875
1913
|
return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
|
|
1876
1914
|
}
|
|
1877
1915
|
|
|
1878
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1916
|
+
// ../../../../../../../tmp/ldr-build-eZnTdy/src/node/build-pack.ts
|
|
1879
1917
|
function buildPack(o) {
|
|
1880
1918
|
const lib = new Library(o.ldrawRoot);
|
|
1881
1919
|
const refs = new Map;
|
|
@@ -1938,7 +1976,7 @@ function buildPack(o) {
|
|
|
1938
1976
|
return { data: encodePack(parts, kinds, comp.opts.weld, extra, { texture, lod: o.lod !== false, reorder: !!o.reorder }), parts: parts.size, studKinds: kinds.size, missing: [...missing], ...warnings.length ? { warnings } : {} };
|
|
1939
1977
|
}
|
|
1940
1978
|
|
|
1941
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1979
|
+
// ../../../../../../../tmp/ldr-build-eZnTdy/src/cli.ts
|
|
1942
1980
|
var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
|
|
1943
1981
|
|
|
1944
1982
|
Usage:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { parse, parseColor, isDirectColor, mul, I as IDENTITY, norm, smoothNormals, parseTexMap, texmapUV, texmapTriangleUV, transformTexMap, resolveTexture, fileKind, looseSteps, looseSource, looseFile, loosePartName, texWrapper, invert, hash32, colorToken, rotStepMatrix, stepViewMatrix, fileStepViews } from "./spec.js";
|
|
2
2
|
export type { M as Matrix12, Doc, LFile, Cmd, RotStep, BufExchg, TexMap, TexMapCmd, TexMapMethod, TexAnno, DataBlock, PartMesh, FileKind, StepView } from "./spec.js";
|
|
3
|
-
export { flatten, parseModel, sequenceSteps, parseLDConfig } from "./model.js";
|
|
3
|
+
export { flatten, parseModel, sequenceSteps, stepKey, parseLDConfig, CALLOUT_END } from "./model.js";
|
|
4
|
+
export { planStepViews, isolateMask, viewDir, instanceBoxes, MIN_VIS, REORIENT } from "./instructions.js";
|
|
5
|
+
export type { StepCameraView, StepCameraOptions, Vec3 } from "./instructions.js";
|
|
4
6
|
export type { PartInstance, FlattenOptions, LDrawColor } from "./model.js";
|
|
5
7
|
export { loadPack, decodePack, buildLdrScene, makeMaterial, createInstancedEdges, computeCulling, cullCore, forEachStud, conditionalLines, createInstancedConditionalEdges, conditionalLineVisible, warmPackGeometry } from "./render.js";
|
|
6
8
|
export { precomputeCull, sceneInstances } from "./cull.js";
|
|
@@ -10,3 +12,4 @@ export type { LdrObject, LdrObjectOptions, LdrStats, ColorSource } from "./scene
|
|
|
10
12
|
export { decompressPack } from "./pack-io.js";
|
|
11
13
|
export { loadModel, loadParts, loadPartsIndex, mergePacks, prepareModel, DEFAULT_PARTS_CDN } from "./cdn.js";
|
|
12
14
|
export type { LoadModelOptions, LoadedModel, LoadPartsOptions, PartsCdnIndex, PrepareOptions, PreparedModel } from "./cdn.js";
|
|
15
|
+
export { embeddedKeys } from "./embedded.js";
|