ldr 0.3.0 → 0.4.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 +7 -5
- package/dist/cli.js +817 -490
- package/dist/index.d.ts +2 -2
- package/dist/index.js +323 -12
- package/dist/model.d.ts +30 -3
- package/dist/node/build-pack.d.ts +5 -0
- package/dist/node/index.js +435 -66
- package/dist/node/library.d.ts +10 -2
- package/dist/node/resolver.d.ts +28 -1
- package/dist/render.d.ts +1 -1
- package/dist/scene.d.ts +4 -0
- package/dist/spec.d.ts +83 -2
- package/package.json +1 -1
package/dist/node/index.js
CHANGED
|
@@ -6,6 +6,17 @@ function parseColor(tok) {
|
|
|
6
6
|
return +tok;
|
|
7
7
|
}
|
|
8
8
|
var MOVED_RE = /^~?\s*moved\s+to\s+(.+)$/i;
|
|
9
|
+
var SPECIAL0 = new Set(["FILE", "NOFILE", "!DATA", "!:", "STEP", "BFC", "ROTSTEP", "BUFEXCHG", "!TEXMAP"]);
|
|
10
|
+
var PART_ORG = new Set(["part", "subpart", "primitive", "8_primitive", "48_primitive", "shortcut", "helper"]);
|
|
11
|
+
var MODEL_ORG = new Set(["model", "submodel", "configuration"]);
|
|
12
|
+
var ORG_RE = /^(?:!?LDRAW_ORG|Unofficial|Official\s+LCAD)\s+(\S+)/i;
|
|
13
|
+
function orgType(text) {
|
|
14
|
+
const m = ORG_RE.exec(text);
|
|
15
|
+
if (!m)
|
|
16
|
+
return;
|
|
17
|
+
const t = m[1].toLowerCase().replace(/^unofficial_/, "");
|
|
18
|
+
return PART_ORG.has(t) || MODEL_ORG.has(t) ? t : undefined;
|
|
19
|
+
}
|
|
9
20
|
function texNames(rest) {
|
|
10
21
|
const out = [];
|
|
11
22
|
let i = 0;
|
|
@@ -91,7 +102,8 @@ function geomLine(tk) {
|
|
|
91
102
|
return { t: 4, color, p: nums.slice(0, 12) };
|
|
92
103
|
return { t: 5, color, p: nums.slice(0, 12) };
|
|
93
104
|
}
|
|
94
|
-
function parse(src, name = "main.ldr") {
|
|
105
|
+
function parse(src, name = "main.ldr", popts = {}) {
|
|
106
|
+
const gtext = !!popts.geometryText;
|
|
95
107
|
const files = new Map;
|
|
96
108
|
let cur = null;
|
|
97
109
|
let main = "";
|
|
@@ -107,6 +119,7 @@ function parse(src, name = "main.ldr") {
|
|
|
107
119
|
(data ??= new Map).set(norm(dataCur.name), { name: dataCur.name, bytes, ...png ? { mime: "image/png" } : {} });
|
|
108
120
|
dataCur = null;
|
|
109
121
|
};
|
|
122
|
+
let inHeader = true;
|
|
110
123
|
const start = (n) => {
|
|
111
124
|
closeData();
|
|
112
125
|
cur = { name: norm(n), cmds: [] };
|
|
@@ -115,6 +128,7 @@ function parse(src, name = "main.ldr") {
|
|
|
115
128
|
main = cur.name;
|
|
116
129
|
texStack = [];
|
|
117
130
|
texNext = null;
|
|
131
|
+
inHeader = true;
|
|
118
132
|
};
|
|
119
133
|
let skip = false, sawBlock = false;
|
|
120
134
|
const firstBlock = () => {
|
|
@@ -144,6 +158,53 @@ function parse(src, name = "main.ldr") {
|
|
|
144
158
|
const line = raw.trim();
|
|
145
159
|
if (!line)
|
|
146
160
|
continue;
|
|
161
|
+
if (gtext && line.charCodeAt(0) >= 50 && line.charCodeAt(0) <= 53 && (line.charCodeAt(1) === 32 || line.charCodeAt(1) === 9)) {
|
|
162
|
+
if (dataCur || skip)
|
|
163
|
+
continue;
|
|
164
|
+
if (!cur)
|
|
165
|
+
start(name);
|
|
166
|
+
const sp = line.slice(2).trimStart();
|
|
167
|
+
const e = sp.search(/\s/);
|
|
168
|
+
const g = { t: line.charCodeAt(0) - 48, color: parseColor(e < 0 ? sp : sp.slice(0, e)), p: [], src: line };
|
|
169
|
+
annotate(g, false);
|
|
170
|
+
cur.cmds.push(g);
|
|
171
|
+
inHeader = false;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (line.charCodeAt(0) === 48 && (line.charCodeAt(1) === 32 || line.charCodeAt(1) === 9)) {
|
|
175
|
+
let a = 2;
|
|
176
|
+
while (line.charCodeAt(a) === 32 || line.charCodeAt(a) === 9)
|
|
177
|
+
a++;
|
|
178
|
+
let b = a;
|
|
179
|
+
while (b < line.length && line.charCodeAt(b) !== 32 && line.charCodeAt(b) !== 9)
|
|
180
|
+
b++;
|
|
181
|
+
const kw = line.slice(a, b);
|
|
182
|
+
if (!SPECIAL0.has(kw)) {
|
|
183
|
+
if (skip || dataCur)
|
|
184
|
+
continue;
|
|
185
|
+
if (!cur)
|
|
186
|
+
start(name);
|
|
187
|
+
const text = line.slice(a);
|
|
188
|
+
const c = { t: 0, text };
|
|
189
|
+
if (kw.charCodeAt(0) === 33)
|
|
190
|
+
c.meta = kw;
|
|
191
|
+
texNext = null;
|
|
192
|
+
if (inHeader && !cur.org) {
|
|
193
|
+
const o = orgType(text);
|
|
194
|
+
if (o)
|
|
195
|
+
cur.org = o;
|
|
196
|
+
}
|
|
197
|
+
const moved = text.match(MOVED_RE);
|
|
198
|
+
if (moved && !cur.movedTo) {
|
|
199
|
+
let target = moved[1].trim().replace(/\\/g, "/");
|
|
200
|
+
if (!/\.(dat|ldr|mpd)$/i.test(target))
|
|
201
|
+
target += ".dat";
|
|
202
|
+
cur.movedTo = norm(target);
|
|
203
|
+
}
|
|
204
|
+
cur.cmds.push(c);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
147
208
|
const tk = line.split(/\s+/);
|
|
148
209
|
const t = +tk[0];
|
|
149
210
|
if (t === 0) {
|
|
@@ -186,6 +247,9 @@ function parse(src, name = "main.ldr") {
|
|
|
186
247
|
c.bfc = tk.slice(2).join(" ");
|
|
187
248
|
else if (tk[1] === "ROTSTEP") {
|
|
188
249
|
c.meta = "ROTSTEP";
|
|
250
|
+
c.step = true;
|
|
251
|
+
texStack = [];
|
|
252
|
+
texNext = null;
|
|
189
253
|
if ((tk[2] ?? "").toUpperCase() === "END")
|
|
190
254
|
c.rotstep = { x: 0, y: 0, z: 0, mode: "END" };
|
|
191
255
|
else {
|
|
@@ -198,6 +262,11 @@ function parse(src, name = "main.ldr") {
|
|
|
198
262
|
c.bufexchg = { op: op === "RETRIEVE" ? "RETRIEVE" : "STORE", buf: tk[3] ?? "A" };
|
|
199
263
|
} else if (tk[1]?.startsWith("!"))
|
|
200
264
|
c.meta = tk[1];
|
|
265
|
+
if (inHeader && !cur.org) {
|
|
266
|
+
const o = orgType(text);
|
|
267
|
+
if (o)
|
|
268
|
+
cur.org = o;
|
|
269
|
+
}
|
|
201
270
|
if (tk[1] === "!TEXMAP") {
|
|
202
271
|
const op = (tk[2] ?? "").toUpperCase();
|
|
203
272
|
texNext = null;
|
|
@@ -248,6 +317,7 @@ function parse(src, name = "main.ldr") {
|
|
|
248
317
|
continue;
|
|
249
318
|
annotate(g, false);
|
|
250
319
|
cur.cmds.push(g);
|
|
320
|
+
inHeader = false;
|
|
251
321
|
}
|
|
252
322
|
closeData();
|
|
253
323
|
return data ? { main, files, data } : { main, files };
|
|
@@ -427,34 +497,249 @@ function smoothNormals(pos, edges, cond, creaseDeg = 35) {
|
|
|
427
497
|
}
|
|
428
498
|
return out;
|
|
429
499
|
}
|
|
500
|
+
var kindMemo = new WeakMap;
|
|
501
|
+
var SUBREF_RE = /^(s|8|48)\//;
|
|
502
|
+
function fileKind(doc, name) {
|
|
503
|
+
const f = doc.files.get(norm(name));
|
|
504
|
+
if (!f)
|
|
505
|
+
return "part";
|
|
506
|
+
let k = kindMemo.get(f);
|
|
507
|
+
if (k)
|
|
508
|
+
return k;
|
|
509
|
+
if (f.org)
|
|
510
|
+
k = PART_ORG.has(f.org) ? "part" : "model";
|
|
511
|
+
else if (f.name === doc.main)
|
|
512
|
+
k = "model";
|
|
513
|
+
else
|
|
514
|
+
k = f.cmds.some((c) => c.t >= 2 || c.t === 0 && !!c.geom || c.t === 1 && SUBREF_RE.test(c.file)) ? "part" : "model";
|
|
515
|
+
kindMemo.set(f, k);
|
|
516
|
+
return k;
|
|
517
|
+
}
|
|
518
|
+
var looseMemo = new WeakMap;
|
|
519
|
+
function looseSteps(f) {
|
|
520
|
+
let r = looseMemo.get(f);
|
|
521
|
+
if (r)
|
|
522
|
+
return r;
|
|
523
|
+
r = [];
|
|
524
|
+
let s = 0;
|
|
525
|
+
for (const c of f.cmds) {
|
|
526
|
+
if (c.t === 0 && c.step) {
|
|
527
|
+
s++;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if ((c.t >= 2 || c.t === 0 && c.geom && c.geom.t >= 2) && r[r.length - 1] !== s)
|
|
531
|
+
r.push(s);
|
|
532
|
+
}
|
|
533
|
+
looseMemo.set(f, r);
|
|
534
|
+
return r;
|
|
535
|
+
}
|
|
536
|
+
var colorToken = (c) => c > 16777215 ? "0x" + c.toString(16).toUpperCase() : String(c);
|
|
537
|
+
var lineOf = (c) => c.t === 0 ? "0 " + c.text : c.t === 1 ? `1 ${colorToken(c.color)} ${c.m.join(" ")} ${c.file}` : c.src ? c.src : `${c.t} ${colorToken(c.color)} ${c.p.join(" ")}`;
|
|
538
|
+
var loosePartName = (file, step) => `${norm(file)}#${step}`;
|
|
539
|
+
function looseFile(f, step) {
|
|
540
|
+
const out = [];
|
|
541
|
+
let s = 0;
|
|
542
|
+
const isNext = (c) => !!c && c.t === 0 && c.texmap?.op === "NEXT";
|
|
543
|
+
for (const c of f.cmds) {
|
|
544
|
+
if (c.t === 0) {
|
|
545
|
+
if (c.step) {
|
|
546
|
+
s++;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (c.bfc !== undefined) {
|
|
550
|
+
out.push(c);
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (s !== step)
|
|
554
|
+
continue;
|
|
555
|
+
if (c.texmap)
|
|
556
|
+
out.push(c);
|
|
557
|
+
else if (c.geom) {
|
|
558
|
+
if (c.geom.t >= 2)
|
|
559
|
+
out.push(c);
|
|
560
|
+
else if (isNext(out[out.length - 1]))
|
|
561
|
+
out.pop();
|
|
562
|
+
}
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (s !== step)
|
|
566
|
+
continue;
|
|
567
|
+
if (c.t === 1) {
|
|
568
|
+
if (isNext(out[out.length - 1]))
|
|
569
|
+
out.pop();
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
out.push(c);
|
|
573
|
+
}
|
|
574
|
+
return { name: loosePartName(f.name, step), cmds: out };
|
|
575
|
+
}
|
|
576
|
+
function looseSource(f, step) {
|
|
577
|
+
return [`0 ldr: own geometry of ${f.name}, step ${step}`, ...looseFile(f, step).cmds.map(lineOf)].join(`
|
|
578
|
+
`) + `
|
|
579
|
+
`;
|
|
580
|
+
}
|
|
581
|
+
function hash32(s) {
|
|
582
|
+
let h = 2166136261;
|
|
583
|
+
for (let i = 0;i < s.length; i++) {
|
|
584
|
+
h ^= s.charCodeAt(i);
|
|
585
|
+
h = Math.imul(h, 16777619);
|
|
586
|
+
}
|
|
587
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
588
|
+
}
|
|
589
|
+
function invert(m) {
|
|
590
|
+
const [x, y, z, a, b, c, d, e, f, g, h, i] = m;
|
|
591
|
+
const A = e * i - f * h, B = -(d * i - f * g), C = d * h - e * g;
|
|
592
|
+
const det = a * A + b * B + c * C;
|
|
593
|
+
if (!Number.isFinite(det) || Math.abs(det) < 0.000000000001)
|
|
594
|
+
return null;
|
|
595
|
+
const k = 1 / det;
|
|
596
|
+
const r = [A * k, -(b * i - c * h) * k, (b * f - c * e) * k, B * k, (a * i - c * g) * k, -(a * f - c * d) * k, C * k, -(a * h - b * g) * k, (a * e - b * d) * k];
|
|
597
|
+
return [-(r[0] * x + r[1] * y + r[2] * z), -(r[3] * x + r[4] * y + r[5] * z), -(r[6] * x + r[7] * y + r[8] * z), ...r];
|
|
598
|
+
}
|
|
599
|
+
var num = (x) => String(+x.toFixed(6) || 0);
|
|
600
|
+
var quote = (s) => '"' + s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"';
|
|
601
|
+
function texWrapper(part, map) {
|
|
602
|
+
const v = [...map.p1, ...map.p2, ...map.p3, ...map.method !== "PLANAR" ? [map.a ?? 360] : [], ...map.method === "SPHERICAL" ? [map.b ?? 180] : []].map(num).join(" ");
|
|
603
|
+
const source = `0 ldr: ${part} with a model-level !TEXMAP
|
|
604
|
+
0 BFC CERTIFY CCW
|
|
605
|
+
0 !TEXMAP START ${map.method} ${v} ${quote(map.texture)}${map.glossmap ? " GLOSSMAP " + quote(map.glossmap) : ""}
|
|
606
|
+
1 16 0 0 0 1 0 0 0 1 0 0 0 1 ${norm(part)}
|
|
607
|
+
0 !TEXMAP END
|
|
608
|
+
`;
|
|
609
|
+
return { name: `${norm(part)}#tex-${hash32(source)}`, source };
|
|
610
|
+
}
|
|
611
|
+
var mul3 = (a, b) => {
|
|
612
|
+
const r = new Array(9);
|
|
613
|
+
for (let i = 0;i < 3; i++)
|
|
614
|
+
for (let j = 0;j < 3; j++)
|
|
615
|
+
r[i * 3 + j] = a[i * 3] * b[j] + a[i * 3 + 1] * b[3 + j] + a[i * 3 + 2] * b[6 + j];
|
|
616
|
+
return r;
|
|
617
|
+
};
|
|
618
|
+
function rotStepMatrix(x, y, z) {
|
|
619
|
+
const D = Math.PI / 180, s1 = Math.sin(x * D), c1 = Math.cos(x * D), s2 = Math.sin(y * D), c2 = Math.cos(y * D), s3 = Math.sin(z * D), c3 = Math.cos(z * D);
|
|
620
|
+
return [c2 * c3, -c2 * s3, s2, c1 * s3 + s1 * s2 * c3, c1 * c3 - s1 * s2 * s3, -s1 * c2, s1 * s3 - c1 * s2 * c3, s1 * c3 + c1 * s2 * s3, c1 * c2];
|
|
621
|
+
}
|
|
622
|
+
function stepViewMatrix(view, defaultView = [1, 0, 0, 0, 1, 0, 0, 0, 1]) {
|
|
623
|
+
return !view ? [...defaultView] : view.abs ? [...view.m] : mul3(defaultView, view.m);
|
|
624
|
+
}
|
|
625
|
+
var viewMemo = new WeakMap;
|
|
626
|
+
function fileStepViews(f) {
|
|
627
|
+
const hit = viewMemo.get(f);
|
|
628
|
+
if (hit)
|
|
629
|
+
return hit;
|
|
630
|
+
const out = [];
|
|
631
|
+
let cur = null;
|
|
632
|
+
for (const c of f.cmds) {
|
|
633
|
+
if (c.t !== 0 || !c.step)
|
|
634
|
+
continue;
|
|
635
|
+
const r = c.rotstep;
|
|
636
|
+
if (r) {
|
|
637
|
+
if (r.mode === "END")
|
|
638
|
+
cur = null;
|
|
639
|
+
else {
|
|
640
|
+
const R = rotStepMatrix(r.x, r.y, r.z);
|
|
641
|
+
cur = r.mode === "ABS" ? { abs: true, m: R, rotstep: r } : r.mode === "ADD" && cur ? { abs: cur.abs, m: mul3(cur.m, R), rotstep: r } : { abs: false, m: R, rotstep: r };
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
out.push(cur);
|
|
645
|
+
}
|
|
646
|
+
out.push(cur);
|
|
647
|
+
viewMemo.set(f, out);
|
|
648
|
+
return out;
|
|
649
|
+
}
|
|
430
650
|
// src/model.ts
|
|
431
651
|
function flatten(doc, opts = {}) {
|
|
432
652
|
const out = [];
|
|
433
653
|
const maxDepth = opts.maxDepth ?? 64;
|
|
434
|
-
const
|
|
654
|
+
const loose = opts.looseGeometry !== false, tx = !!opts.textures;
|
|
655
|
+
const src = new Map;
|
|
656
|
+
const lazy = (f, k) => {
|
|
657
|
+
const key = f.name + "#" + k;
|
|
658
|
+
let fn = src.get(key);
|
|
659
|
+
if (!fn) {
|
|
660
|
+
let s;
|
|
661
|
+
fn = Object.assign(() => s ??= looseSource(f, k), { file: f, step: k });
|
|
662
|
+
src.set(key, fn);
|
|
663
|
+
}
|
|
664
|
+
return fn;
|
|
665
|
+
};
|
|
666
|
+
const walk = (d, fname, m, color, path, stepPath, depth, texIn) => {
|
|
435
667
|
const f = d.files.get(fname);
|
|
436
668
|
if (!f || depth > maxDepth)
|
|
437
669
|
return;
|
|
438
670
|
let s = 0;
|
|
439
|
-
|
|
440
|
-
|
|
671
|
+
const views = fileStepViews(f);
|
|
672
|
+
const ls = loose ? looseSteps(f) : [];
|
|
673
|
+
let li = 0;
|
|
674
|
+
const emit = (part, col, mm, sp, k, embedded, tex) => {
|
|
675
|
+
const i = { part, color: col, m: mm, step: sp[0], steps: sp, localStep: k, path };
|
|
676
|
+
if (embedded)
|
|
677
|
+
i.embedded = true;
|
|
678
|
+
if (tex) {
|
|
679
|
+
const w = texWrapper(part, tex);
|
|
680
|
+
i.texPart = w.name;
|
|
681
|
+
i.texSource = w.source;
|
|
682
|
+
}
|
|
683
|
+
const v = views[k];
|
|
684
|
+
if (v)
|
|
685
|
+
i.view = v;
|
|
686
|
+
out.push(i);
|
|
687
|
+
return i;
|
|
688
|
+
};
|
|
689
|
+
const flushLoose = (upTo) => {
|
|
690
|
+
while (li < ls.length && ls[li] <= upTo) {
|
|
691
|
+
const k = ls[li++];
|
|
692
|
+
emit(loosePartName(f.name, k), color, m, [...stepPath, k], k, false, null).source = lazy(f, k);
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
for (const c0 of f.cmds) {
|
|
696
|
+
if (c0.t === 0 && c0.step) {
|
|
441
697
|
s++;
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
let c = c0;
|
|
701
|
+
if (c.t >= 2 || c.t === 0 && c.geom && c.geom.t >= 2) {
|
|
702
|
+
flushLoose(s);
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
if (c.t === 0) {
|
|
706
|
+
if (tx && c.geom?.t === 1)
|
|
707
|
+
c = c.geom;
|
|
708
|
+
else
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
442
711
|
if (c.t !== 1)
|
|
443
712
|
continue;
|
|
713
|
+
if (tx && c.fallback)
|
|
714
|
+
continue;
|
|
444
715
|
const col = c.color === 16 ? color : c.color, mm = mul(m, c.m), sp = [...stepPath, s];
|
|
716
|
+
const tex = tx ? c.tex ?? texIn : null;
|
|
717
|
+
const local = tex ? invert(c.m) : null;
|
|
718
|
+
const lt = tex && local ? transformTexMap(tex, local) : null;
|
|
445
719
|
if (d.files.has(c.file)) {
|
|
446
|
-
|
|
720
|
+
if (fileKind(d, c.file) === "model") {
|
|
721
|
+
walk(d, c.file, mm, col, path + "/" + c.file, sp, depth + 1, lt);
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
emit(c.file, col, mm, sp, s, true, lt);
|
|
447
725
|
continue;
|
|
448
726
|
}
|
|
449
727
|
const ext = opts.resolveModel?.(c.file);
|
|
450
728
|
if (ext) {
|
|
451
|
-
walk(ext, ext.main, mm, col, path + "/" + c.file, sp, depth + 1);
|
|
729
|
+
walk(ext, ext.main, mm, col, path + "/" + c.file, sp, depth + 1, lt);
|
|
452
730
|
continue;
|
|
453
731
|
}
|
|
454
|
-
|
|
732
|
+
emit(c.file, col, mm, sp, s, false, lt);
|
|
455
733
|
}
|
|
734
|
+
flushLoose(Infinity);
|
|
456
735
|
};
|
|
457
|
-
|
|
736
|
+
if (fileKind(doc, doc.main) === "part") {
|
|
737
|
+
const f = doc.files.get(doc.main);
|
|
738
|
+
if (f)
|
|
739
|
+
out.push({ part: doc.main, color: 16, m: [...I], step: 0, steps: [0], localStep: 0, path: doc.main, embedded: true });
|
|
740
|
+
return out;
|
|
741
|
+
}
|
|
742
|
+
walk(doc, doc.main, I, 16, doc.main, [], 0, null);
|
|
458
743
|
return out;
|
|
459
744
|
}
|
|
460
745
|
function parseModel(text, name = "main.ldr", opts) {
|
|
@@ -465,8 +750,14 @@ function sequenceSteps(insts) {
|
|
|
465
750
|
const uniq = [...new Set(insts.map(key))].sort();
|
|
466
751
|
const rank = new Map(uniq.map((k, j) => [k, j]));
|
|
467
752
|
const stepOf = new Int32Array(insts.length);
|
|
468
|
-
|
|
469
|
-
|
|
753
|
+
const views = new Array(uniq.length).fill(null);
|
|
754
|
+
insts.forEach((i, k) => {
|
|
755
|
+
const r = rank.get(key(i));
|
|
756
|
+
stepOf[k] = r;
|
|
757
|
+
if (i.view)
|
|
758
|
+
views[r] = i.view;
|
|
759
|
+
});
|
|
760
|
+
return { count: uniq.length, stepOf, views };
|
|
470
761
|
}
|
|
471
762
|
function parseLDConfig(text) {
|
|
472
763
|
const out = new Map;
|
|
@@ -830,7 +1121,7 @@ function buildLdrScene(insts, pack, P, color, mark) {
|
|
|
830
1121
|
mark("culling");
|
|
831
1122
|
const edgeObjs = [];
|
|
832
1123
|
const condObjs = [];
|
|
833
|
-
const wantCond = P.cond !== false && P.edges === "idx";
|
|
1124
|
+
const wantCond = P.cond !== false && (P.edges === "idx" || P.edges === true);
|
|
834
1125
|
let condMat = null;
|
|
835
1126
|
const cmat = () => condMat ??= condLineMat();
|
|
836
1127
|
const condCache = new Map;
|
|
@@ -1098,11 +1389,18 @@ function toPlastic(old) {
|
|
|
1098
1389
|
function createLdrObject(instances, pack, options = {}) {
|
|
1099
1390
|
const o = { edges: true, conditionalLines: true, cullStuds: true, cullUndersides: true, side: "front", material: "plastic", ...options };
|
|
1100
1391
|
const color = colorFn(o.colors, pack, new THREE2.Color(o.fallbackColor ?? 10528169));
|
|
1101
|
-
const
|
|
1392
|
+
const useTex = o.textures !== false;
|
|
1393
|
+
const pick = (i) => useTex && i.texPart && pack.header.parts[i.texPart] ? i.texPart : i.part;
|
|
1394
|
+
const all = instances.map((i) => ({ part: pick(i), color: String(i.color), m: i.m, steps: i.steps }));
|
|
1102
1395
|
const key = (i) => i.steps.map((x) => String(x).padStart(6, "0")).join(".");
|
|
1103
1396
|
const uniq = [...new Set(all.map(key))].sort();
|
|
1104
1397
|
const rank = new Map(uniq.map((k, j) => [k, j]));
|
|
1105
1398
|
const stepOf = all.map((i) => rank.get(key(i)));
|
|
1399
|
+
const stepViews = new Array(uniq.length).fill(null);
|
|
1400
|
+
instances.forEach((i, k) => {
|
|
1401
|
+
if (i.view)
|
|
1402
|
+
stepViews[stepOf[k]] = i.view;
|
|
1403
|
+
});
|
|
1106
1404
|
const missing = [...new Set(all.filter((i) => !pack.header.parts[i.part]).map((i) => i.part))];
|
|
1107
1405
|
const object = new THREE2.Group;
|
|
1108
1406
|
object.name = "ldr";
|
|
@@ -1160,6 +1458,7 @@ function createLdrObject(instances, pack, options = {}) {
|
|
|
1160
1458
|
object,
|
|
1161
1459
|
stats,
|
|
1162
1460
|
stepCount: uniq.length,
|
|
1461
|
+
stepViews,
|
|
1163
1462
|
setStep: (s) => build(s),
|
|
1164
1463
|
setEdges: (v) => {
|
|
1165
1464
|
edges = v;
|
|
@@ -1219,6 +1518,16 @@ class Library {
|
|
|
1219
1518
|
unofficialRoot;
|
|
1220
1519
|
cache = new Map;
|
|
1221
1520
|
meshCache = new Map;
|
|
1521
|
+
embedded = new Map;
|
|
1522
|
+
register(name, doc, file = doc.main) {
|
|
1523
|
+
name = norm(name);
|
|
1524
|
+
const prev = this.embedded.get(name);
|
|
1525
|
+
if (prev && prev.files === doc.files && prev.main === file)
|
|
1526
|
+
return;
|
|
1527
|
+
if (prev || this.lookup(name))
|
|
1528
|
+
this.meshCache.clear();
|
|
1529
|
+
this.embedded.set(name, file === doc.main ? doc : { main: file, files: doc.files, ...doc.data ? { data: doc.data } : {} });
|
|
1530
|
+
}
|
|
1222
1531
|
constructor(root, unofficialRoot) {
|
|
1223
1532
|
this.root = root;
|
|
1224
1533
|
this.unofficialRoot = unofficialRoot;
|
|
@@ -1229,6 +1538,9 @@ class Library {
|
|
|
1229
1538
|
if (seen.has(name))
|
|
1230
1539
|
return null;
|
|
1231
1540
|
seen.add(name);
|
|
1541
|
+
const emb = this.embedded.get(name);
|
|
1542
|
+
if (emb)
|
|
1543
|
+
return { kind: "part", doc: emb };
|
|
1232
1544
|
const hit = this.lookup(name);
|
|
1233
1545
|
if (!hit)
|
|
1234
1546
|
return null;
|
|
@@ -1403,34 +1715,48 @@ class Library {
|
|
|
1403
1715
|
emitFace([...ordered].reverse());
|
|
1404
1716
|
}
|
|
1405
1717
|
}
|
|
1406
|
-
flatten(doc) {
|
|
1407
|
-
const
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1718
|
+
flatten(doc, opts = {}) {
|
|
1719
|
+
for (const f of doc.files.keys())
|
|
1720
|
+
this.register(f, doc, f);
|
|
1721
|
+
const memo = new Map;
|
|
1722
|
+
const at = (n) => {
|
|
1723
|
+
if (!memo.has(n))
|
|
1724
|
+
memo.set(n, this.where(n));
|
|
1725
|
+
return memo.get(n);
|
|
1726
|
+
};
|
|
1727
|
+
const insts = flatten(doc, { ...opts, resolveModel: (n) => {
|
|
1728
|
+
const r = at(n);
|
|
1729
|
+
return r?.kind === "model" ? r.doc : null;
|
|
1730
|
+
} });
|
|
1731
|
+
const synth = new Map;
|
|
1732
|
+
for (const i of insts) {
|
|
1733
|
+
if (i.texPart && !synth.has(i.texPart)) {
|
|
1734
|
+
synth.set(i.texPart, i.texSource);
|
|
1735
|
+
this.register(i.texPart, parse(i.texSource, i.texPart));
|
|
1736
|
+
}
|
|
1737
|
+
if (i.embedded) {
|
|
1738
|
+
i.missing = false;
|
|
1739
|
+
continue;
|
|
1740
|
+
}
|
|
1741
|
+
if (i.source) {
|
|
1742
|
+
if (!synth.has(i.part)) {
|
|
1743
|
+
synth.set(i.part, "");
|
|
1744
|
+
const s = i.source;
|
|
1745
|
+
const lf = s.file ? looseFile(s.file, s.step) : parse(s(), i.part).files.get(i.part);
|
|
1746
|
+
this.register(i.part, { main: i.part, files: new Map([[i.part, lf]]) });
|
|
1429
1747
|
}
|
|
1748
|
+
i.missing = false;
|
|
1749
|
+
continue;
|
|
1430
1750
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1751
|
+
const r = at(i.part);
|
|
1752
|
+
i.missing = !r;
|
|
1753
|
+
if (r) {
|
|
1754
|
+
i.part = r.doc.main;
|
|
1755
|
+
if (r.aliasOf)
|
|
1756
|
+
i.aliasOf = r.aliasOf;
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return insts;
|
|
1434
1760
|
}
|
|
1435
1761
|
}
|
|
1436
1762
|
// src/node/build-pack.ts
|
|
@@ -1496,8 +1822,22 @@ class Library2 {
|
|
|
1496
1822
|
this.index.set(r.toLowerCase(), path.join(dir, e.name));
|
|
1497
1823
|
}
|
|
1498
1824
|
}
|
|
1825
|
+
virtual = new Map;
|
|
1826
|
+
conflicts = new Set;
|
|
1827
|
+
addVirtual(name, text) {
|
|
1828
|
+
const n = norm(name), key = "mpd/" + n, prev = this.virtual.get(n);
|
|
1829
|
+
if (prev === undefined) {
|
|
1830
|
+
this.virtual.set(n, text);
|
|
1831
|
+
this.text.set(key, text);
|
|
1832
|
+
this.index.set(key, "(mpd) " + n);
|
|
1833
|
+
} else if (prev !== text)
|
|
1834
|
+
this.conflicts.add(n);
|
|
1835
|
+
return key;
|
|
1836
|
+
}
|
|
1499
1837
|
resolve(ref) {
|
|
1500
1838
|
const n = norm2(ref);
|
|
1839
|
+
if (this.virtual.size && this.virtual.has(n))
|
|
1840
|
+
return "mpd/" + n;
|
|
1501
1841
|
for (const pre of ["parts/", "p/", "models/"])
|
|
1502
1842
|
if (this.index.has(pre + n))
|
|
1503
1843
|
return pre + n;
|
|
@@ -1636,32 +1976,42 @@ function splitModel(text, fallbackName = "main.ldr") {
|
|
|
1636
1976
|
flush();
|
|
1637
1977
|
return { files, main, data };
|
|
1638
1978
|
}
|
|
1639
|
-
function
|
|
1979
|
+
function modelDoc(model) {
|
|
1980
|
+
const files = new Map;
|
|
1981
|
+
for (const [n, t] of model.files) {
|
|
1982
|
+
const f = parse(t, n, { geometryText: true }).files.get(norm(n)) ?? { name: norm(n), cmds: [] };
|
|
1983
|
+
files.set(norm(n), f);
|
|
1984
|
+
}
|
|
1985
|
+
return { main: norm(model.main ?? ""), files };
|
|
1986
|
+
}
|
|
1987
|
+
function flatten2(model, lib, onInstance, opts = {}) {
|
|
1640
1988
|
const missing = new Map;
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
if (
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
}
|
|
1656
|
-
const key = lib.resolve(r.name);
|
|
1657
|
-
if (!key) {
|
|
1658
|
-
missing.set(n, (missing.get(n) || 0) + 1);
|
|
1659
|
-
continue;
|
|
1660
|
-
}
|
|
1661
|
-
onInstance({ part: key, ref: n, color, m });
|
|
1989
|
+
for (const [n, t] of model.files)
|
|
1990
|
+
lib.addVirtual(n, t);
|
|
1991
|
+
const doc = modelDoc(model);
|
|
1992
|
+
const at = new Map;
|
|
1993
|
+
for (const i of flatten(doc, opts)) {
|
|
1994
|
+
let key;
|
|
1995
|
+
if (i.embedded)
|
|
1996
|
+
key = "mpd/" + i.part;
|
|
1997
|
+
else if (i.source)
|
|
1998
|
+
key = lib.addVirtual(i.part, i.source());
|
|
1999
|
+
else {
|
|
2000
|
+
if (!at.has(i.part))
|
|
2001
|
+
at.set(i.part, lib.resolve(i.part));
|
|
2002
|
+
key = at.get(i.part);
|
|
1662
2003
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
2004
|
+
if (!key) {
|
|
2005
|
+
missing.set(i.part, (missing.get(i.part) || 0) + 1);
|
|
2006
|
+
continue;
|
|
2007
|
+
}
|
|
2008
|
+
const r = { part: key, ref: i.part, color: colorToken(i.color), m: i.m, inst: i };
|
|
2009
|
+
if (i.texPart) {
|
|
2010
|
+
r.texRef = i.texPart;
|
|
2011
|
+
r.texPart = lib.addVirtual(i.texPart, i.texSource);
|
|
2012
|
+
}
|
|
2013
|
+
onInstance(r);
|
|
2014
|
+
}
|
|
1665
2015
|
return { missing };
|
|
1666
2016
|
}
|
|
1667
2017
|
function collectDeps(lib, refs) {
|
|
@@ -2270,9 +2620,15 @@ function buildPack(o) {
|
|
|
2270
2620
|
for (const [k, v] of sm.data)
|
|
2271
2621
|
if (!data.has(k))
|
|
2272
2622
|
data.set(k, v);
|
|
2273
|
-
const
|
|
2274
|
-
|
|
2275
|
-
|
|
2623
|
+
for (const textures of o.textures ? [false, true] : [false]) {
|
|
2624
|
+
const r = flatten2(sm, lib, (i) => {
|
|
2625
|
+
refs.set(i.ref, i.part);
|
|
2626
|
+
if (i.texRef)
|
|
2627
|
+
refs.set(i.texRef, i.texPart);
|
|
2628
|
+
}, { textures, looseGeometry: o.looseGeometry });
|
|
2629
|
+
for (const k of r.missing.keys())
|
|
2630
|
+
missing.add(k);
|
|
2631
|
+
}
|
|
2276
2632
|
}
|
|
2277
2633
|
for (const p of o.parts ?? []) {
|
|
2278
2634
|
const k = lib.resolve(p);
|
|
@@ -2311,7 +2667,8 @@ function buildPack(o) {
|
|
|
2311
2667
|
const k = norm2(n), b = data.get(k) ?? data.get(k.replace(/^textures\//, "")) ?? data.get("textures/" + k);
|
|
2312
2668
|
return b ? new Uint8Array(Buffer.from(b, "base64")) : comp.texture(n);
|
|
2313
2669
|
};
|
|
2314
|
-
|
|
2670
|
+
const warnings = [...lib.conflicts].map((n) => `${n}: defined differently by two models; the first definition is used`);
|
|
2671
|
+
return { data: encodePack(parts, kinds, comp.opts.weld, extra, { texture }), parts: parts.size, studKinds: kinds.size, missing: [...missing], ...warnings.length ? { warnings } : {} };
|
|
2315
2672
|
}
|
|
2316
2673
|
export {
|
|
2317
2674
|
Compiler,
|
|
@@ -2321,6 +2678,7 @@ export {
|
|
|
2321
2678
|
buildLdrScene,
|
|
2322
2679
|
buildPack,
|
|
2323
2680
|
collectDeps,
|
|
2681
|
+
colorToken,
|
|
2324
2682
|
computeCulling,
|
|
2325
2683
|
conditionalLineVisible,
|
|
2326
2684
|
conditionalLines,
|
|
@@ -2330,11 +2688,19 @@ export {
|
|
|
2330
2688
|
decodePack,
|
|
2331
2689
|
decompressPack,
|
|
2332
2690
|
encodePack,
|
|
2691
|
+
fileKind,
|
|
2692
|
+
fileStepViews,
|
|
2333
2693
|
flatten,
|
|
2334
2694
|
flatten2 as flattenWithLibrary,
|
|
2695
|
+
hash32,
|
|
2696
|
+
invert,
|
|
2335
2697
|
isDirectColor,
|
|
2336
2698
|
loadColors,
|
|
2337
2699
|
loadPack,
|
|
2700
|
+
looseFile,
|
|
2701
|
+
loosePartName,
|
|
2702
|
+
looseSource,
|
|
2703
|
+
looseSteps,
|
|
2338
2704
|
makeMaterial,
|
|
2339
2705
|
mul,
|
|
2340
2706
|
norm,
|
|
@@ -2344,9 +2710,12 @@ export {
|
|
|
2344
2710
|
parseModel,
|
|
2345
2711
|
parseTexMap,
|
|
2346
2712
|
resolveTexture,
|
|
2713
|
+
rotStepMatrix,
|
|
2347
2714
|
sequenceSteps,
|
|
2348
2715
|
smoothNormals,
|
|
2349
2716
|
splitModel,
|
|
2717
|
+
stepViewMatrix,
|
|
2718
|
+
texWrapper,
|
|
2350
2719
|
texmapTriangleUV,
|
|
2351
2720
|
texmapUV,
|
|
2352
2721
|
transformTexMap,
|