ldr 0.24.2 → 0.25.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 +4 -1
- package/dist/cli.js +63 -23
- package/dist/cull.d.ts +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +98 -31
- package/dist/load-worker.gen.d.ts +1 -1
- package/dist/model.d.ts +10 -0
- package/dist/node/index.js +114 -40
- package/dist/render.d.ts +3 -1
- package/dist/scene.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,10 +83,11 @@ 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. |
|
|
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. |
|
|
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). |
|
|
90
|
+
| `blendedColourHex(code)` | RGB of a blended (old dithered) code 256-511, for codes LDConfig doesn't define. |
|
|
90
91
|
| `parseColourLine`, `colourSurface`, `colourMaterialKey`, `makeFinishMaterial` | Spec finish → PBR surface (metalness/roughness/clearcoat/sheen; glitter/speckle use cheap procedural grain). |
|
|
91
92
|
| `fileKind(doc, name)` | Decides whether an MPD file is a part or a model: by its header type, otherwise by whether it has its own geometry. |
|
|
92
93
|
| `looseSteps` / `looseFile` / `looseSource` / `loosePartName` | A model's own type 2–5 geometry, per step, as synthetic parts named `<file>#<step>`. |
|
|
@@ -209,6 +210,8 @@ Reproduce: `cd bench && bun run setup && bun run perf` (regression gate) or `bun
|
|
|
209
210
|
- `0 STEP`, nested submodel step sequencing, `0 ROTSTEP` (`REL`/`ADD`/`ABS`/`END`), and `0 BUFEXCHG`.
|
|
210
211
|
- `!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.
|
|
211
212
|
- Colours: LDConfig `!COLOUR`, edge colours, main/edge colour inheritance (16/24), and direct colours (`0x2RRGGBB`).
|
|
213
|
+
- `!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. Colours hard-coded inside parts (printed parts, MPD-embedded parts) always come from LDConfig, because parts are precompiled.
|
|
214
|
+
- 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.
|
|
212
215
|
- Case-insensitive, backslash-normalized file lookup; `~Moved to` alias parts.
|
|
213
216
|
|
|
214
217
|
## Limitations
|
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-sxbVKM/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-sxbVKM/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-sxbVKM/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-sxbVKM/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))
|
|
@@ -705,7 +705,7 @@ function fileStepViews(f) {
|
|
|
705
705
|
return out;
|
|
706
706
|
}
|
|
707
707
|
|
|
708
|
-
// ../../../../../../../tmp/ldr-build-
|
|
708
|
+
// ../../../../../../../tmp/ldr-build-sxbVKM/src/model.ts
|
|
709
709
|
var CALLOUT_END = 999999;
|
|
710
710
|
var stepKey = (steps) => steps.map((x) => String(x).padStart(6, "0")).join(".") + "~";
|
|
711
711
|
function flatten(doc, opts = {}) {
|
|
@@ -724,17 +724,18 @@ function flatten(doc, opts = {}) {
|
|
|
724
724
|
}
|
|
725
725
|
return fn;
|
|
726
726
|
};
|
|
727
|
-
const walk = (d, fname, m, color, path, stepPath, depth, texIn, fixed = null, subPath = [], subMats = []) => {
|
|
727
|
+
const walk = (d, fname, m, color, path, stepPath, depth, texIn, fixed = null, subPath = [], subMats = [], colour = undefined, scope0 = null) => {
|
|
728
728
|
const f = d.files.get(fname);
|
|
729
729
|
if (!f || depth > maxDepth)
|
|
730
730
|
return;
|
|
731
|
+
let scope = scope0;
|
|
731
732
|
let s = 0;
|
|
732
733
|
const subId = depth > 0 ? ++subs : 0;
|
|
733
734
|
const sp0 = co && subId ? [...subPath, subId] : subPath;
|
|
734
735
|
const sm0 = co && subId ? [...subMats, m] : subMats;
|
|
735
736
|
const groups = co && !fixed ? new Map : null;
|
|
736
737
|
let ordStep = -1, ord = 0;
|
|
737
|
-
const sub = (sd, file, mm, col, sp, lt) => {
|
|
738
|
+
const sub = (sd, file, mm, col, sp, lt, cd) => {
|
|
738
739
|
if (ordStep !== s) {
|
|
739
740
|
ordStep = s;
|
|
740
741
|
ord = 0;
|
|
@@ -742,19 +743,21 @@ function flatten(doc, opts = {}) {
|
|
|
742
743
|
const g = groups?.get(s + "|" + file);
|
|
743
744
|
if (g) {
|
|
744
745
|
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
|
+
return walk(sd, sd === d ? file : sd.main, mm, col, path + "/" + file, [...sp, CALLOUT_END], depth + 1, lt, [...sp, CALLOUT_END], sp0, sm0, cd, scope);
|
|
746
747
|
}
|
|
747
748
|
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
|
+
walk(sd, sd === d ? file : sd.main, mm, col, path + "/" + file, spo, depth + 1, lt, fixed, sp0, sm0, cd, scope);
|
|
749
750
|
groups?.set(s + "|" + file, { n: 1, from, to: out.length });
|
|
750
751
|
};
|
|
751
752
|
const views = fileStepViews(f);
|
|
752
753
|
const ls = loose ? looseSteps(f) : [];
|
|
753
754
|
let li = 0;
|
|
754
|
-
const emit = (part, col, mm, sp, k, embedded, tex) => {
|
|
755
|
+
const emit = (part, col, mm, sp, k, embedded, tex, cd) => {
|
|
755
756
|
if (fixed)
|
|
756
757
|
sp = fixed;
|
|
757
758
|
const i = { part, color: col, m: mm, step: sp[0], steps: sp, localStep: k, path };
|
|
759
|
+
if (cd)
|
|
760
|
+
i.colour = cd;
|
|
758
761
|
if (subId) {
|
|
759
762
|
i.sub = subId;
|
|
760
763
|
if (co) {
|
|
@@ -778,7 +781,7 @@ function flatten(doc, opts = {}) {
|
|
|
778
781
|
const flushLoose = (upTo) => {
|
|
779
782
|
while (li < ls.length && ls[li] <= upTo) {
|
|
780
783
|
const k = ls[li++];
|
|
781
|
-
emit(loosePartName(f.name, k), color, m, [...stepPath, k], k, false, null).source = lazy(f, k);
|
|
784
|
+
emit(loosePartName(f.name, k), color, m, [...stepPath, k], k, false, null, colour).source = lazy(f, k);
|
|
782
785
|
}
|
|
783
786
|
};
|
|
784
787
|
for (const c0 of f.cmds) {
|
|
@@ -792,6 +795,12 @@ function flatten(doc, opts = {}) {
|
|
|
792
795
|
continue;
|
|
793
796
|
}
|
|
794
797
|
if (c.t === 0) {
|
|
798
|
+
if (c.meta === "!COLOUR") {
|
|
799
|
+
const def = parseColourLine(c.text);
|
|
800
|
+
if (def && def.code !== 16 && def.code !== 24)
|
|
801
|
+
(scope = new Map(scope ?? [])).set(def.code, def);
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
795
804
|
if (tx && c.geom?.t === 1)
|
|
796
805
|
c = c.geom;
|
|
797
806
|
else
|
|
@@ -801,24 +810,24 @@ function flatten(doc, opts = {}) {
|
|
|
801
810
|
continue;
|
|
802
811
|
if (tx && c.fallback)
|
|
803
812
|
continue;
|
|
804
|
-
const col = c.color === 16 ? color : c.color, mm = mul(m, c.m), sp = [...stepPath, s];
|
|
813
|
+
const col = c.color === 16 ? color : c.color, cd = c.color === 16 ? colour : scope?.get(c.color), mm = mul(m, c.m), sp = [...stepPath, s];
|
|
805
814
|
const tex = tx ? c.tex ?? texIn : null;
|
|
806
815
|
const local = tex ? invert(c.m) : null;
|
|
807
816
|
const lt = tex && local ? transformTexMap(tex, local) : null;
|
|
808
817
|
if (d.files.has(c.file)) {
|
|
809
818
|
if (fileKind(d, c.file) === "model") {
|
|
810
|
-
sub(d, c.file, mm, col, sp, lt);
|
|
819
|
+
sub(d, c.file, mm, col, sp, lt, cd);
|
|
811
820
|
continue;
|
|
812
821
|
}
|
|
813
|
-
emit(c.file, col, mm, sp, s, true, lt);
|
|
822
|
+
emit(c.file, col, mm, sp, s, true, lt, cd);
|
|
814
823
|
continue;
|
|
815
824
|
}
|
|
816
825
|
const ext = opts.resolveModel?.(c.file);
|
|
817
826
|
if (ext) {
|
|
818
|
-
sub(ext, c.file, mm, col, sp, lt);
|
|
827
|
+
sub(ext, c.file, mm, col, sp, lt, cd);
|
|
819
828
|
continue;
|
|
820
829
|
}
|
|
821
|
-
emit(c.file, col, mm, sp, s, false, lt);
|
|
830
|
+
emit(c.file, col, mm, sp, s, false, lt, cd);
|
|
822
831
|
}
|
|
823
832
|
flushLoose(Infinity);
|
|
824
833
|
if (groups) {
|
|
@@ -1054,8 +1063,32 @@ function parseLDConfig(text) {
|
|
|
1054
1063
|
}
|
|
1055
1064
|
return out;
|
|
1056
1065
|
}
|
|
1066
|
+
var LDRAW_EXE_16 = [
|
|
1067
|
+
[51, 51, 51],
|
|
1068
|
+
[0, 51, 178],
|
|
1069
|
+
[0, 127, 51],
|
|
1070
|
+
[0, 181, 166],
|
|
1071
|
+
[204, 0, 0],
|
|
1072
|
+
[255, 51, 153],
|
|
1073
|
+
[102, 51, 0],
|
|
1074
|
+
[153, 153, 153],
|
|
1075
|
+
[102, 102, 88],
|
|
1076
|
+
[0, 128, 255],
|
|
1077
|
+
[51, 255, 102],
|
|
1078
|
+
[171, 253, 249],
|
|
1079
|
+
[255, 0, 0],
|
|
1080
|
+
[255, 176, 204],
|
|
1081
|
+
[255, 229, 0],
|
|
1082
|
+
[255, 255, 255]
|
|
1083
|
+
];
|
|
1084
|
+
function blendedColourHex(code) {
|
|
1085
|
+
if (!Number.isInteger(code) || code < 256 || code > 511)
|
|
1086
|
+
return null;
|
|
1087
|
+
const a = LDRAW_EXE_16[code - 256 >> 4], b = LDRAW_EXE_16[code - 256 & 15];
|
|
1088
|
+
return "#" + [0, 1, 2].map((k) => (a[k] + b[k] >> 1).toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
1089
|
+
}
|
|
1057
1090
|
|
|
1058
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1091
|
+
// ../../../../../../../tmp/ldr-build-sxbVKM/src/node/resolver.ts
|
|
1059
1092
|
var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
|
|
1060
1093
|
var UNDER_STUD_RE = /^(stud3|stud3a|stud4.*|stud12|stud16.*|stud18a)\.dat$/i;
|
|
1061
1094
|
var norm2 = (ref) => ref.trim().replace(/\\/g, "/").toLowerCase();
|
|
@@ -1325,11 +1358,11 @@ function collectDeps(lib, refs) {
|
|
|
1325
1358
|
return out;
|
|
1326
1359
|
}
|
|
1327
1360
|
|
|
1328
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1361
|
+
// ../../../../../../../tmp/ldr-build-sxbVKM/src/node/compile.ts
|
|
1329
1362
|
import fs2 from "node:fs";
|
|
1330
1363
|
import path2 from "node:path";
|
|
1331
1364
|
|
|
1332
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1365
|
+
// ../../../../../../../tmp/ldr-build-sxbVKM/src/node/lod.ts
|
|
1333
1366
|
import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
|
|
1334
1367
|
await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
|
|
1335
1368
|
function reorderMesh(P, N, C, I) {
|
|
@@ -1521,7 +1554,7 @@ function lodChain(P, C, I) {
|
|
|
1521
1554
|
return out;
|
|
1522
1555
|
}
|
|
1523
1556
|
|
|
1524
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1557
|
+
// ../../../../../../../tmp/ldr-build-sxbVKM/src/node/compile.ts
|
|
1525
1558
|
function loadColors(root) {
|
|
1526
1559
|
const map = new Map;
|
|
1527
1560
|
for (const l of fs2.readFileSync(path2.join(root, "LDConfig.ldr"), "latin1").split(/\r?\n/)) {
|
|
@@ -1533,6 +1566,13 @@ function loadColors(root) {
|
|
|
1533
1566
|
}
|
|
1534
1567
|
return map;
|
|
1535
1568
|
}
|
|
1569
|
+
var blendedRgb = (code) => {
|
|
1570
|
+
const h = blendedColourHex(+code);
|
|
1571
|
+
if (!h)
|
|
1572
|
+
return null;
|
|
1573
|
+
const v = parseInt(h.slice(1), 16);
|
|
1574
|
+
return [(v >> 16) / 255, (v >> 8 & 255) / 255, (v & 255) / 255];
|
|
1575
|
+
};
|
|
1536
1576
|
var det3 = (m) => m[3] * (m[7] * m[11] - m[8] * m[10]) - m[4] * (m[6] * m[11] - m[8] * m[9]) + m[5] * (m[6] * m[10] - m[7] * m[9]);
|
|
1537
1577
|
|
|
1538
1578
|
class Compiler {
|
|
@@ -1570,7 +1610,7 @@ class Compiler {
|
|
|
1570
1610
|
if (this.cache.has(ck))
|
|
1571
1611
|
return this.cache.get(ck);
|
|
1572
1612
|
const out = { body: { pos: [], col: [] }, under: { pos: [], col: [] }, edges: [], condLines: 0, cond: this.opts.cond ? [] : undefined, condUnder: this.opts.cond ? [] : undefined, studs: [], bbox: [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity], fullBottom: false };
|
|
1573
|
-
const colorOf = (code) => code === "16" ? [1, 1, 1, 0] : [...this.colors.get(code) || (code.startsWith("0x2") ? [parseInt(code.slice(3, 5), 16) / 255, parseInt(code.slice(5, 7), 16) / 255, parseInt(code.slice(7, 9), 16) / 255] : [1, 0, 1]), 1];
|
|
1613
|
+
const colorOf = (code) => code === "16" ? [1, 1, 1, 0] : [...this.colors.get(code) || (code.startsWith("0x2") ? [parseInt(code.slice(3, 5), 16) / 255, parseInt(code.slice(5, 7), 16) / 255, parseInt(code.slice(7, 9), 16) / 255] : blendedRgb(code) ?? [1, 0, 1]), 1];
|
|
1574
1614
|
const walk = (fk, M, color, invert, target, depth) => {
|
|
1575
1615
|
if (depth > 40)
|
|
1576
1616
|
return;
|
|
@@ -2151,7 +2191,7 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
|
|
|
2151
2191
|
return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
|
|
2152
2192
|
}
|
|
2153
2193
|
|
|
2154
|
-
// ../../../../../../../tmp/ldr-build-
|
|
2194
|
+
// ../../../../../../../tmp/ldr-build-sxbVKM/src/node/build-pack.ts
|
|
2155
2195
|
function buildPack(o) {
|
|
2156
2196
|
const lib = new Library(o.ldrawRoot);
|
|
2157
2197
|
const refs = new Map;
|
|
@@ -2219,7 +2259,7 @@ function buildPack(o) {
|
|
|
2219
2259
|
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 } : {} };
|
|
2220
2260
|
}
|
|
2221
2261
|
|
|
2222
|
-
// ../../../../../../../tmp/ldr-build-
|
|
2262
|
+
// ../../../../../../../tmp/ldr-build-sxbVKM/src/cli.ts
|
|
2223
2263
|
var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
|
|
2224
2264
|
|
|
2225
2265
|
Usage:
|
package/dist/cull.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PartInstance } from './model.js';
|
|
1
|
+
import type { PartInstance, LDrawColor } from './model.js';
|
|
2
2
|
export type Inst = {
|
|
3
3
|
part: string;
|
|
4
4
|
color: string;
|
|
@@ -63,6 +63,7 @@ export declare function sceneInstances(instances: PartInstance[], H: any, useTex
|
|
|
63
63
|
}[];
|
|
64
64
|
stepOf: number[];
|
|
65
65
|
nSteps: number;
|
|
66
|
+
localColours: Map<string, LDrawColor>;
|
|
66
67
|
};
|
|
67
68
|
/** Culling for `createLdrObject(instances, pack, { textures })`, computable off the main thread and passed back as `precomputedCull`. */
|
|
68
69
|
export declare function precomputeCull(instances: PartInstance[], H: any, useTex?: boolean): CullResult;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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, stepKey, parseLDConfig, parseColourLine, colourSurface, colourMaterialKey, CALLOUT_END } from "./model.js";
|
|
3
|
+
export { flatten, parseModel, sequenceSteps, stepKey, parseLDConfig, parseColourLine, colourSurface, colourMaterialKey, blendedColourHex, CALLOUT_END } from "./model.js";
|
|
4
4
|
export { planStepViews, isolateMask, viewDir, instanceBoxes, MIN_VIS, REORIENT } from "./instructions.js";
|
|
5
5
|
export type { StepCameraView, StepCameraOptions, Vec3 } from "./instructions.js";
|
|
6
6
|
export type { PartInstance, FlattenOptions, LDrawColor, LDrawFinish, LDrawFinishType, LDrawMaterialGrain, ColourSurface } from "./model.js";
|