ldr 0.29.0 → 0.30.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 +2 -2
- package/dist/cli.js +107 -16
- package/dist/index.js +52 -20
- package/dist/load-worker.gen.d.ts +1 -1
- package/dist/model.d.ts +17 -0
- package/dist/node/index.js +126 -31
- package/dist/node/library.d.ts +2 -1
- package/dist/node/resolver.d.ts +12 -1
- package/dist/spec.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ Light the scene (or set `scene.environment`) as you would for any `MeshStandardM
|
|
|
83
83
|
| export | description |
|
|
84
84
|
|---|---|
|
|
85
85
|
| `parse(text, name?, { geometryText? }) → Doc` | Parses LDraw text into files and commands. MPD-aware: `0 FILE`/`0 NOFILE` and `0 !DATA` blocks. |
|
|
86
|
-
| `flatten(doc, options?) → PartInstance[]` | Expands submodels into placed parts. Each instance has `part`, `color`, a 12-number matrix `m`, `step`, nested `steps`, `path` and the ROTSTEP `view` of its step. `colour` (an `LDrawColor`) is set when a `0 !COLOUR` line in the model itself defines the instance's colour. |
|
|
86
|
+
| `flatten(doc, options?) → PartInstance[]` | Expands submodels into placed parts. Each instance has `part`, `color`, a 12-number matrix `m`, `step`, nested `steps`, `path` and the ROTSTEP `view` of its step. `colour` (an `LDrawColor`) is set when a `0 !COLOUR` line in the model itself defines the instance's colour. `colPart` / `colSource` name the part with the model's definitions in scope applied inside it (a synthetic part, `<part>#col-<hash>`; packs from `buildPack` have it only when it changes a colour, and the renderer falls back to `part`). |
|
|
87
87
|
| `parseModel(text, name?, options?)` | `parse` followed by `flatten`. |
|
|
88
88
|
| `sequenceSteps(instances) → { count, stepOf, views }` | Gives each instance a global build-step index, sequencing nested submodel steps. `views` holds the ROTSTEP view per global step. |
|
|
89
89
|
| `parseLDConfig(text) → Map<code, LDrawColor>` | Reads a `!COLOUR` table into `{ code, name, hex, edge, alpha, luminance, finish }` (CHROME / PEARLESCENT / RUBBER / METAL / MATTE_METALLIC / MATERIAL GLITTER|SPECKLE|FABRIC). |
|
|
@@ -213,7 +213,7 @@ Reproduce: `cd bench && bun run setup && bun run perf` (regression gate) or `bun
|
|
|
213
213
|
- `0 STEP`, nested submodel step sequencing, `0 ROTSTEP` (`REL`/`ADD`/`ABS`/`END`), and `0 BUFEXCHG`.
|
|
214
214
|
- `!TEXMAP` `START`/`NEXT`/`FALLBACK`/`END`, with `PLANAR`, `CYLINDRICAL` and `SPHERICAL` projections (orientation matches LDView), `GLOSSMAP` (alpha = gloss amount, same UVs), and model-level `!TEXMAP` around part references.
|
|
215
215
|
- Colours: LDConfig `!COLOUR`, edge colours, main/edge colour inheritance (16/24), and direct colours (`0x2RRGGBB`).
|
|
216
|
-
- `!COLOUR` lines in a model follow the spec's scope rule. A definition applies from its line to the end of its file and to the files that file references, and it never leaks back to the parent. It sets the instance colour and finish. `!COLOUR` lines inside a part (an MPD-embedded part and its subparts) follow the same rule when the part is compiled, and a model file's own loose geometry sees that file's definitions: its triangles and studs take the part's own definitions (RGB; baked part colours are opaque, as for LDConfig codes). A model's definitions
|
|
216
|
+
- `!COLOUR` lines in a model follow the spec's scope rule. A definition applies from its line to the end of its file and to the files that file references, and it never leaks back to the parent. It sets the instance colour and finish. `!COLOUR` lines inside a part (an MPD-embedded part and its subparts) follow the same rule when the part is compiled, and a model file's own loose geometry sees that file's definitions: its triangles and studs take the part's own definitions (RGB; baked part colours are opaque, as for LDConfig codes). A model's definitions also reach the colours hard-coded inside the parts it places ("passed to subfiles"): `buildPack` compiles a variant of such a part with those definitions applied (only where one changes a colour the part uses), and the renderer draws it; the part's own later definitions still win inside it. Parts from the hosted parts CDN (`loadModel` / `prepareModel`) don't have these variants yet, so there the colours inside parts come from LDConfig or the part's own definitions.
|
|
217
217
|
- Named colour codes: a `!COLOUR` CODE may be an identifier instead of a number (`0 !COLOUR My_Red CODE MyRed VALUE #FF0000 EDGE #333333`, then `1 myred ...`), which the spec allows outside strict LDraw compatibility. Identifiers are case-insensitive and follow the same scope rule. `parseColor` turns one into a stable negative code (`namedColorCode`, `isNamedColor`), and `colorToken` turns it back into the identifier. A named code with no definition in scope draws the fallback colour.
|
|
218
218
|
- Blended colour codes 256-511 that LDConfig doesn't define (used by old models, e.g. 382 in Datsville) render as the spec's average of two ldraw.exe colours instead of the fallback grey.
|
|
219
219
|
- Case-insensitive, backslash-normalized file lookup; `~Moved to` alias parts.
|
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-P0dzRz/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-P0dzRz/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-P0dzRz/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-P0dzRz/src/spec.ts
|
|
16
16
|
var norm = (s) => s.trim().toLowerCase().replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
17
17
|
var COLOUR_ID_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
18
18
|
var namedCodes = new Map;
|
|
@@ -723,9 +723,35 @@ function fileStepViews(f) {
|
|
|
723
723
|
return out;
|
|
724
724
|
}
|
|
725
725
|
|
|
726
|
-
// ../../../../../../../tmp/ldr-build-
|
|
726
|
+
// ../../../../../../../tmp/ldr-build-P0dzRz/src/model.ts
|
|
727
727
|
var CALLOUT_END = 999999;
|
|
728
728
|
var stepKey = (steps) => steps.map((x) => String(x).padStart(6, "0")).join(".") + "~";
|
|
729
|
+
var defLine = new WeakMap;
|
|
730
|
+
var wrapMemo = new WeakMap;
|
|
731
|
+
function scopeWrapper(part, scope) {
|
|
732
|
+
let w = wrapMemo.get(scope);
|
|
733
|
+
if (!w) {
|
|
734
|
+
const lines = [...scope.values()].map((d) => {
|
|
735
|
+
const t = defLine.get(d) ?? "";
|
|
736
|
+
const k = t.indexOf("!COLOUR");
|
|
737
|
+
return k < 0 ? "" : "0 " + t.slice(k).trim().replace(/\s+/g, " ");
|
|
738
|
+
}).filter(Boolean);
|
|
739
|
+
w = { head: lines.join(`
|
|
740
|
+
`), parts: new Map };
|
|
741
|
+
wrapMemo.set(scope, w);
|
|
742
|
+
}
|
|
743
|
+
let r = w.parts.get(part);
|
|
744
|
+
if (!r) {
|
|
745
|
+
const n = norm(part), source = `0 ldr: ${n} placed in a model's !COLOUR scope
|
|
746
|
+
0 BFC CERTIFY CCW
|
|
747
|
+
${w.head}
|
|
748
|
+
1 16 0 0 0 1 0 0 0 1 0 0 0 1 ${n}
|
|
749
|
+
`;
|
|
750
|
+
r = { name: `${n}#col-${hash32(source)}`, source };
|
|
751
|
+
w.parts.set(part, r);
|
|
752
|
+
}
|
|
753
|
+
return r;
|
|
754
|
+
}
|
|
729
755
|
function flatten(doc, opts = {}) {
|
|
730
756
|
const out = [];
|
|
731
757
|
const maxDepth = opts.maxDepth ?? 64;
|
|
@@ -770,7 +796,7 @@ function flatten(doc, opts = {}) {
|
|
|
770
796
|
const views = fileStepViews(f);
|
|
771
797
|
const ls = loose ? looseSteps(f) : [];
|
|
772
798
|
let li = 0;
|
|
773
|
-
const emit = (part, col, mm, sp, k, embedded, tex, cd) => {
|
|
799
|
+
const emit = (part, col, mm, sp, k, embedded, tex, cd, sc) => {
|
|
774
800
|
if (fixed)
|
|
775
801
|
sp = fixed;
|
|
776
802
|
const i = { part, color: col, m: mm, step: sp[0], steps: sp, localStep: k, path };
|
|
@@ -789,6 +815,10 @@ function flatten(doc, opts = {}) {
|
|
|
789
815
|
const w = texWrapper(part, tex);
|
|
790
816
|
i.texPart = w.name;
|
|
791
817
|
i.texSource = w.source;
|
|
818
|
+
} else if (sc) {
|
|
819
|
+
const w = scopeWrapper(part, sc);
|
|
820
|
+
i.colPart = w.name;
|
|
821
|
+
i.colSource = w.source;
|
|
792
822
|
}
|
|
793
823
|
const v = views[k];
|
|
794
824
|
if (v)
|
|
@@ -799,7 +829,7 @@ function flatten(doc, opts = {}) {
|
|
|
799
829
|
const flushLoose = (upTo) => {
|
|
800
830
|
while (li < ls.length && ls[li] <= upTo) {
|
|
801
831
|
const k = ls[li++];
|
|
802
|
-
emit(loosePartName(f.name, k), color, m, [...stepPath, k], k, false, null, colour).source = lazy(f, k);
|
|
832
|
+
emit(loosePartName(f.name, k), color, m, [...stepPath, k], k, false, null, colour, scope0).source = lazy(f, k);
|
|
803
833
|
}
|
|
804
834
|
};
|
|
805
835
|
for (const c0 of f.cmds) {
|
|
@@ -815,8 +845,10 @@ function flatten(doc, opts = {}) {
|
|
|
815
845
|
if (c.t === 0) {
|
|
816
846
|
if (c.meta === "!COLOUR") {
|
|
817
847
|
const def = parseColourLine(c.text);
|
|
818
|
-
if (def && def.code !== 16 && def.code !== 24)
|
|
848
|
+
if (def && def.code !== 16 && def.code !== 24) {
|
|
849
|
+
defLine.set(def, c.text);
|
|
819
850
|
(scope = new Map(scope ?? [])).set(def.code, def);
|
|
851
|
+
}
|
|
820
852
|
continue;
|
|
821
853
|
}
|
|
822
854
|
if (tx && c.geom?.t === 1)
|
|
@@ -837,7 +869,7 @@ function flatten(doc, opts = {}) {
|
|
|
837
869
|
sub(d, c.file, mm, col, sp, lt, cd);
|
|
838
870
|
continue;
|
|
839
871
|
}
|
|
840
|
-
emit(c.file, col, mm, sp, s, true, lt, cd);
|
|
872
|
+
emit(c.file, col, mm, sp, s, true, lt, cd, scope);
|
|
841
873
|
continue;
|
|
842
874
|
}
|
|
843
875
|
const ext = opts.resolveModel?.(c.file);
|
|
@@ -845,7 +877,7 @@ function flatten(doc, opts = {}) {
|
|
|
845
877
|
sub(ext, c.file, mm, col, sp, lt, cd);
|
|
846
878
|
continue;
|
|
847
879
|
}
|
|
848
|
-
emit(c.file, col, mm, sp, s, false, lt, cd);
|
|
880
|
+
emit(c.file, col, mm, sp, s, false, lt, cd, scope);
|
|
849
881
|
}
|
|
850
882
|
flushLoose(Infinity);
|
|
851
883
|
if (groups) {
|
|
@@ -1106,7 +1138,7 @@ function blendedColourHex(code) {
|
|
|
1106
1138
|
return "#" + [0, 1, 2].map((k) => (a[k] + b[k] >> 1).toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
1107
1139
|
}
|
|
1108
1140
|
|
|
1109
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1141
|
+
// ../../../../../../../tmp/ldr-build-P0dzRz/src/node/resolver.ts
|
|
1110
1142
|
var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
|
|
1111
1143
|
var UNDER_STUD_RE = /^(stud3|stud3a|stud4.*|stud12|stud16.*|stud18a)\.dat$/i;
|
|
1112
1144
|
var norm2 = (ref) => ref.trim().replace(/\\/g, "/").toLowerCase();
|
|
@@ -1192,6 +1224,53 @@ class Library {
|
|
|
1192
1224
|
isPart(key) {
|
|
1193
1225
|
return key.startsWith("parts/") && !key.startsWith("parts/s/");
|
|
1194
1226
|
}
|
|
1227
|
+
#codes = new Map;
|
|
1228
|
+
#ldconfig;
|
|
1229
|
+
colourCodes(key) {
|
|
1230
|
+
let s = this.#codes.get(key);
|
|
1231
|
+
if (s)
|
|
1232
|
+
return s;
|
|
1233
|
+
s = new Set;
|
|
1234
|
+
this.#codes.set(key, s);
|
|
1235
|
+
for (const line of this.read(key).split(/\r?\n/)) {
|
|
1236
|
+
const t = line.trim(), c = t[0];
|
|
1237
|
+
if (c !== "1" && c !== "3" && c !== "4")
|
|
1238
|
+
continue;
|
|
1239
|
+
const tok = t.split(/\s+/);
|
|
1240
|
+
if (tok.length < 2)
|
|
1241
|
+
continue;
|
|
1242
|
+
const code = parseColor(tok[1]);
|
|
1243
|
+
if (code !== 16 && code !== 24 && !Number.isNaN(code))
|
|
1244
|
+
s.add(code);
|
|
1245
|
+
if (c === "1") {
|
|
1246
|
+
const r = parseType1(t);
|
|
1247
|
+
const ck = r && this.resolve(r.name);
|
|
1248
|
+
if (ck)
|
|
1249
|
+
for (const x of this.colourCodes(ck))
|
|
1250
|
+
s.add(x);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return s;
|
|
1254
|
+
}
|
|
1255
|
+
scopeChanges(key, wrapperSource) {
|
|
1256
|
+
if (!this.#ldconfig) {
|
|
1257
|
+
const f = path.join(this.root, "LDConfig.ldr");
|
|
1258
|
+
this.#ldconfig = fs.existsSync(f) ? parseLDConfig(fs.readFileSync(f, "latin1")) : new Map;
|
|
1259
|
+
}
|
|
1260
|
+
const used = this.colourCodes(key);
|
|
1261
|
+
for (const line of wrapperSource.split(`
|
|
1262
|
+
`)) {
|
|
1263
|
+
if (!line.startsWith("0 !COLOUR"))
|
|
1264
|
+
continue;
|
|
1265
|
+
const d = parseColourLine(line);
|
|
1266
|
+
if (!d || !used.has(d.code))
|
|
1267
|
+
continue;
|
|
1268
|
+
const g = this.#ldconfig.get(d.code);
|
|
1269
|
+
if (!g || g.hex.toUpperCase() !== d.hex.toUpperCase())
|
|
1270
|
+
return true;
|
|
1271
|
+
}
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1195
1274
|
fileStats(key) {
|
|
1196
1275
|
if (this.stats.has(key))
|
|
1197
1276
|
return this.stats.get(key);
|
|
@@ -1331,6 +1410,7 @@ function flatten2(model, lib, onInstance, opts = {}) {
|
|
|
1331
1410
|
lib.addVirtual(n, t);
|
|
1332
1411
|
const doc = modelDoc(model);
|
|
1333
1412
|
const at = new Map;
|
|
1413
|
+
const changes = new Map;
|
|
1334
1414
|
for (const i of flatten(doc, opts)) {
|
|
1335
1415
|
let key;
|
|
1336
1416
|
if (i.embedded)
|
|
@@ -1351,6 +1431,15 @@ function flatten2(model, lib, onInstance, opts = {}) {
|
|
|
1351
1431
|
r.texRef = i.texPart;
|
|
1352
1432
|
r.texPart = lib.addVirtual(i.texPart, i.texSource);
|
|
1353
1433
|
}
|
|
1434
|
+
if (i.colPart) {
|
|
1435
|
+
let ch = changes.get(i.colPart);
|
|
1436
|
+
if (ch === undefined)
|
|
1437
|
+
changes.set(i.colPart, ch = lib.scopeChanges(key, i.colSource));
|
|
1438
|
+
if (ch) {
|
|
1439
|
+
r.colRef = i.colPart;
|
|
1440
|
+
r.colPart = lib.addVirtual(i.colPart, i.colSource);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1354
1443
|
onInstance(r);
|
|
1355
1444
|
}
|
|
1356
1445
|
return { missing };
|
|
@@ -1376,11 +1465,11 @@ function collectDeps(lib, refs) {
|
|
|
1376
1465
|
return out;
|
|
1377
1466
|
}
|
|
1378
1467
|
|
|
1379
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1468
|
+
// ../../../../../../../tmp/ldr-build-P0dzRz/src/node/compile.ts
|
|
1380
1469
|
import fs2 from "node:fs";
|
|
1381
1470
|
import path2 from "node:path";
|
|
1382
1471
|
|
|
1383
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1472
|
+
// ../../../../../../../tmp/ldr-build-P0dzRz/src/node/lod.ts
|
|
1384
1473
|
import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
|
|
1385
1474
|
await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
|
|
1386
1475
|
function reorderMesh(P, N, C, I) {
|
|
@@ -1572,7 +1661,7 @@ function lodChain(P, C, I) {
|
|
|
1572
1661
|
return out;
|
|
1573
1662
|
}
|
|
1574
1663
|
|
|
1575
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1664
|
+
// ../../../../../../../tmp/ldr-build-P0dzRz/src/node/compile.ts
|
|
1576
1665
|
function loadColors(root) {
|
|
1577
1666
|
const map = new Map;
|
|
1578
1667
|
for (const l of fs2.readFileSync(path2.join(root, "LDConfig.ldr"), "latin1").split(/\r?\n/)) {
|
|
@@ -2287,7 +2376,7 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
|
|
|
2287
2376
|
return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
|
|
2288
2377
|
}
|
|
2289
2378
|
|
|
2290
|
-
// ../../../../../../../tmp/ldr-build-
|
|
2379
|
+
// ../../../../../../../tmp/ldr-build-P0dzRz/src/node/build-pack.ts
|
|
2291
2380
|
function packCompilerOpts(o) {
|
|
2292
2381
|
return { studs: o.studs ?? "low", prims: o.primitives ?? "full", weld: o.weld ?? true, separateStuds: true, cond: !!o.cond, tex: !!o.textures };
|
|
2293
2382
|
}
|
|
@@ -2307,6 +2396,8 @@ function buildPack(o) {
|
|
|
2307
2396
|
refs.set(i.ref, i.part);
|
|
2308
2397
|
if (i.texRef)
|
|
2309
2398
|
refs.set(i.texRef, i.texPart);
|
|
2399
|
+
if (i.colRef)
|
|
2400
|
+
refs.set(i.colRef, i.colPart);
|
|
2310
2401
|
}, { textures, looseGeometry: o.looseGeometry });
|
|
2311
2402
|
for (const k of r.missing.keys())
|
|
2312
2403
|
missing.add(k);
|
|
@@ -2358,7 +2449,7 @@ function buildPack(o) {
|
|
|
2358
2449
|
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 } : {} };
|
|
2359
2450
|
}
|
|
2360
2451
|
|
|
2361
|
-
// ../../../../../../../tmp/ldr-build-
|
|
2452
|
+
// ../../../../../../../tmp/ldr-build-P0dzRz/src/cli.ts
|
|
2362
2453
|
var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
|
|
2363
2454
|
|
|
2364
2455
|
Usage:
|