ldr 0.33.2 → 0.33.4
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/dist/cli.js +294 -74
- package/dist/index.d.ts +2 -2
- package/dist/index.js +205 -65
- package/dist/load-worker.gen.d.ts +1 -1
- package/dist/model.d.ts +12 -1
- package/dist/node/build-pack.d.ts +4 -1
- package/dist/node/compile.d.ts +5 -0
- package/dist/node/index.js +415 -117
- package/dist/node/library.d.ts +9 -1
- package/dist/spec.d.ts +3 -0
- package/package.json +2 -1
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-fQmQmL/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-fQmQmL/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-fQmQmL/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-fQmQmL/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;
|
|
@@ -39,6 +39,8 @@ function parseColor(tok) {
|
|
|
39
39
|
}
|
|
40
40
|
var MOVED_RE = /^~?\s*moved\s+to\s+(.+)$/i;
|
|
41
41
|
var SPECIAL0 = new Set(["FILE", "NOFILE", "!DATA", "!:", "STEP", "BFC", "ROTSTEP", "BUFEXCHG", "!TEXMAP"]);
|
|
42
|
+
var BLOCK0 = new Set(["FILE", "NOFILE", "!DATA"]);
|
|
43
|
+
var MAX_COORD = 1000000000000000;
|
|
42
44
|
var PART_ORG = new Set(["part", "subpart", "primitive", "8_primitive", "48_primitive", "shortcut", "helper"]);
|
|
43
45
|
var MODEL_ORG = new Set(["model", "submodel", "configuration"]);
|
|
44
46
|
var ORG_RE = /^(?:!?LDRAW_ORG|Unofficial|Official\s+LCAD)\s+(\S+)/i;
|
|
@@ -89,7 +91,9 @@ function parseTexMap(tail, next = false) {
|
|
|
89
91
|
if (parts.length < n + 1)
|
|
90
92
|
return null;
|
|
91
93
|
const v = parts.slice(0, n).map(Number);
|
|
92
|
-
if (!v.every(Number.isFinite))
|
|
94
|
+
if (!v.every((x) => Number.isFinite(x) && Math.abs(x) <= 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000))
|
|
95
|
+
return null;
|
|
96
|
+
if (n >= 10 && !(Math.abs(v[9]) >= 0.000001) || n === 11 && !(Math.abs(v[10]) >= 0.000001))
|
|
93
97
|
return null;
|
|
94
98
|
let rest = m[2].trim();
|
|
95
99
|
for (let k = 0;k < n; k++)
|
|
@@ -110,7 +114,9 @@ function parseTexMap(tail, next = false) {
|
|
|
110
114
|
return t;
|
|
111
115
|
}
|
|
112
116
|
var b64 = (s) => {
|
|
113
|
-
|
|
117
|
+
let clean = s.replace(/[^A-Za-z0-9+/]/g, "");
|
|
118
|
+
if (clean.length % 4 === 1)
|
|
119
|
+
clean = clean.slice(0, -1);
|
|
114
120
|
const pad = clean + "=".repeat((4 - clean.length % 4) % 4);
|
|
115
121
|
const bin = atob(pad);
|
|
116
122
|
const out = new Uint8Array(bin.length);
|
|
@@ -138,7 +144,10 @@ function type1Fast(line) {
|
|
|
138
144
|
const t = next();
|
|
139
145
|
if (t === null)
|
|
140
146
|
return null;
|
|
141
|
-
|
|
147
|
+
const v = +t;
|
|
148
|
+
if (!Number.isFinite(v))
|
|
149
|
+
return null;
|
|
150
|
+
m[k] = v;
|
|
142
151
|
}
|
|
143
152
|
while (i < n && ws(line.charCodeAt(i)))
|
|
144
153
|
i++;
|
|
@@ -146,23 +155,37 @@ function type1Fast(line) {
|
|
|
146
155
|
return null;
|
|
147
156
|
const rest = line.slice(i);
|
|
148
157
|
const file = /[ \t]{2,}|\t/.test(rest) ? rest.split(/\s+/).join(" ") : rest;
|
|
149
|
-
|
|
158
|
+
const color = parseColor(ct);
|
|
159
|
+
if (!Number.isFinite(color))
|
|
160
|
+
return null;
|
|
161
|
+
return { t: 1, color, m, file: norm(file) };
|
|
150
162
|
}
|
|
163
|
+
var NUMS = [0, 12, 6, 9, 12, 12];
|
|
151
164
|
function geomLine(tk) {
|
|
152
165
|
const t = +tk[0];
|
|
153
|
-
if (!(t >= 1 && t <= 5) || tk.length < 2)
|
|
166
|
+
if (!(t >= 1 && t <= 5 && Number.isInteger(t)) || tk.length < 2)
|
|
167
|
+
return null;
|
|
168
|
+
const color = parseColor(tk[1]), n = NUMS[t];
|
|
169
|
+
if (!Number.isFinite(color) || tk.length < 2 + n + (t === 1 ? 1 : 0))
|
|
154
170
|
return null;
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
171
|
+
const nums = new Array(n);
|
|
172
|
+
for (let k = 0;k < n; k++) {
|
|
173
|
+
const v = +tk[2 + k];
|
|
174
|
+
if (!Number.isFinite(v))
|
|
175
|
+
return null;
|
|
176
|
+
nums[k] = v;
|
|
177
|
+
}
|
|
178
|
+
if (t === 1) {
|
|
179
|
+
const file = norm(tk.slice(14).join(" "));
|
|
180
|
+
return file ? { t: 1, color, m: nums, file } : null;
|
|
181
|
+
}
|
|
159
182
|
if (t === 2)
|
|
160
|
-
return { t: 2, color, p: nums
|
|
183
|
+
return { t: 2, color, p: nums };
|
|
161
184
|
if (t === 3)
|
|
162
|
-
return { t: 3, color, p: nums
|
|
185
|
+
return { t: 3, color, p: nums };
|
|
163
186
|
if (t === 4)
|
|
164
|
-
return { t: 4, color, p: nums
|
|
165
|
-
return { t: 5, color, p: nums
|
|
187
|
+
return { t: 4, color, p: nums };
|
|
188
|
+
return { t: 5, color, p: nums };
|
|
166
189
|
}
|
|
167
190
|
function parse(src, name = "main.ldr", popts = {}) {
|
|
168
191
|
const gtext = !!popts.geometryText;
|
|
@@ -242,7 +265,8 @@ function parse(src, name = "main.ldr", popts = {}) {
|
|
|
242
265
|
while (b < line.length && line.charCodeAt(b) !== 32 && line.charCodeAt(b) !== 9)
|
|
243
266
|
b++;
|
|
244
267
|
const kw = line.slice(a, b);
|
|
245
|
-
|
|
268
|
+
const k2 = /[^\x21-\x7e]/.test(kw) ? line.slice(a).trim().split(/\s+/)[0] : kw;
|
|
269
|
+
if (!SPECIAL0.has(k2) && !(k2.length >= 4 && k2.length <= 6 && BLOCK0.has(k2.toUpperCase()))) {
|
|
246
270
|
if (skip || dataCur)
|
|
247
271
|
continue;
|
|
248
272
|
if (!cur)
|
|
@@ -283,19 +307,23 @@ function parse(src, name = "main.ldr", popts = {}) {
|
|
|
283
307
|
const t = +tk[0];
|
|
284
308
|
if (t === 0) {
|
|
285
309
|
const text = line.slice(1).trim();
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
310
|
+
const k1 = tk.length > 1 && tk[1].length >= 4 && tk[1].length <= 6 ? tk[1].toUpperCase() : tk[1];
|
|
311
|
+
if (k1 === "FILE") {
|
|
312
|
+
const fn = text.slice(5).trimStart().split("\r")[0].trim();
|
|
313
|
+
if (fn) {
|
|
314
|
+
firstBlock();
|
|
315
|
+
skip = false;
|
|
316
|
+
start(fn);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
291
319
|
}
|
|
292
|
-
if (
|
|
320
|
+
if (k1 === "NOFILE") {
|
|
293
321
|
closeData();
|
|
294
322
|
cur = null;
|
|
295
323
|
skip = true;
|
|
296
324
|
continue;
|
|
297
325
|
}
|
|
298
|
-
if (
|
|
326
|
+
if (k1 === "!DATA") {
|
|
299
327
|
firstBlock();
|
|
300
328
|
closeData();
|
|
301
329
|
cur = null;
|
|
@@ -328,7 +356,11 @@ function parse(src, name = "main.ldr", popts = {}) {
|
|
|
328
356
|
c.rotstep = { x: 0, y: 0, z: 0, mode: "END" };
|
|
329
357
|
else {
|
|
330
358
|
const mode = (tk[5] ?? "REL").toUpperCase();
|
|
331
|
-
|
|
359
|
+
const ang = (s) => {
|
|
360
|
+
const v = Number(s);
|
|
361
|
+
return Number.isFinite(v) ? v : 0;
|
|
362
|
+
};
|
|
363
|
+
c.rotstep = { x: ang(tk[2]), y: ang(tk[3]), z: ang(tk[4]), mode: ["REL", "ABS", "ADD"].includes(mode) ? mode : "REL" };
|
|
332
364
|
}
|
|
333
365
|
} else if (tk[1] === "BUFEXCHG") {
|
|
334
366
|
c.meta = "BUFEXCHG";
|
|
@@ -387,8 +419,11 @@ function parse(src, name = "main.ldr", popts = {}) {
|
|
|
387
419
|
if (!cur)
|
|
388
420
|
start(name);
|
|
389
421
|
const g = geomLine(tk);
|
|
390
|
-
if (!g)
|
|
422
|
+
if (!g) {
|
|
423
|
+
if (/^[2-5][ \t]/.test(line))
|
|
424
|
+
inHeader = false;
|
|
391
425
|
continue;
|
|
426
|
+
}
|
|
392
427
|
annotate(g, false);
|
|
393
428
|
cur.cmds.push(g);
|
|
394
429
|
inHeader = false;
|
|
@@ -463,12 +498,18 @@ function texmapTriangleUV(map, pts) {
|
|
|
463
498
|
} else {
|
|
464
499
|
const pd = unit3(sub3(p, p1));
|
|
465
500
|
const c = dot3(pd, n);
|
|
501
|
+
const q = radial(p, p1, n);
|
|
466
502
|
if (Math.abs(c) > 1 - 0.000000001) {
|
|
467
503
|
miss.push(i);
|
|
468
504
|
out[i * 2 + 1] = 0.5 + (c > 0 ? -Math.PI / 2 : Math.PI / 2) / B;
|
|
469
505
|
return;
|
|
470
506
|
}
|
|
471
|
-
|
|
507
|
+
if (!q) {
|
|
508
|
+
miss.push(i);
|
|
509
|
+
out[i * 2 + 1] = 0.5;
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const a = sAng(q);
|
|
472
513
|
uSum += a;
|
|
473
514
|
out[i * 2] = 0.5 + a / A;
|
|
474
515
|
const th = -a, ct = Math.cos(th), st = Math.sin(th), cr = cross3(n, pd), k = dot3(n, pd) * (1 - ct);
|
|
@@ -543,18 +584,48 @@ function smoothNormals(pos, edges, cond, creaseDeg = 35) {
|
|
|
543
584
|
};
|
|
544
585
|
for (const [key, corners] of byV) {
|
|
545
586
|
const faces = corners.map((c) => c / 3 | 0), par = faces.map((_, i) => i);
|
|
546
|
-
const f = (i) =>
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
587
|
+
const f = (i) => {
|
|
588
|
+
let r = i;
|
|
589
|
+
while (par[r] !== r)
|
|
590
|
+
r = par[r];
|
|
591
|
+
while (par[i] !== r) {
|
|
592
|
+
const n = par[i];
|
|
593
|
+
par[i] = r;
|
|
594
|
+
i = n;
|
|
595
|
+
}
|
|
596
|
+
return r;
|
|
597
|
+
};
|
|
598
|
+
const byO = new Map;
|
|
599
|
+
for (let i = 0;i < faces.length; i++) {
|
|
600
|
+
const A = faces[i];
|
|
601
|
+
for (let k = 0;k < 3; k++) {
|
|
602
|
+
const o = vk[A * 3 + k];
|
|
603
|
+
if (o === key)
|
|
604
|
+
continue;
|
|
605
|
+
const l = byO.get(o);
|
|
606
|
+
if (!l)
|
|
607
|
+
byO.set(o, [i]);
|
|
608
|
+
else if (l[l.length - 1] !== i)
|
|
609
|
+
l.push(i);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
for (const [o, L] of byO) {
|
|
613
|
+
if (L.length < 2)
|
|
614
|
+
continue;
|
|
615
|
+
const e = ek(key, o);
|
|
616
|
+
if ((edgeFaces.get(e)?.length ?? 0) < 2)
|
|
617
|
+
continue;
|
|
618
|
+
if (L.length > 256) {
|
|
619
|
+
for (let a = 0;a + 1 < L.length; a++)
|
|
620
|
+
if (joinable(e, faces[L[a]], faces[L[a + 1]]))
|
|
621
|
+
par[f(L[a])] = f(L[a + 1]);
|
|
622
|
+
continue;
|
|
557
623
|
}
|
|
624
|
+
for (let a = 0;a < L.length; a++)
|
|
625
|
+
for (let b = a + 1;b < L.length; b++)
|
|
626
|
+
if (joinable(e, faces[L[a]], faces[L[b]]))
|
|
627
|
+
par[f(L[a])] = f(L[b]);
|
|
628
|
+
}
|
|
558
629
|
const acc = new Map;
|
|
559
630
|
faces.forEach((F, i) => {
|
|
560
631
|
const r = f(i), s = acc.get(r) ?? [0, 0, 0];
|
|
@@ -746,7 +817,22 @@ function fileStepViews(f) {
|
|
|
746
817
|
return out;
|
|
747
818
|
}
|
|
748
819
|
|
|
749
|
-
// ../../../../../../../tmp/ldr-build-
|
|
820
|
+
// ../../../../../../../tmp/ldr-build-fQmQmL/src/model.ts
|
|
821
|
+
class ModelLimitError extends Error {
|
|
822
|
+
limit;
|
|
823
|
+
code = "ldr-model-limit";
|
|
824
|
+
constructor(what, limit) {
|
|
825
|
+
super(`ldr: ${what} expands past ${limit.toLocaleString("en-US")} (see maxInstances)`);
|
|
826
|
+
this.limit = limit;
|
|
827
|
+
this.name = "ModelLimitError";
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
var inRange12 = (m) => {
|
|
831
|
+
for (let i = 0;i < 12; i++)
|
|
832
|
+
if (!(Math.abs(m[i]) <= MAX_COORD))
|
|
833
|
+
return false;
|
|
834
|
+
return true;
|
|
835
|
+
};
|
|
750
836
|
var CALLOUT_END = 999999;
|
|
751
837
|
var stepKey = (steps) => steps.map((x) => String(x).padStart(6, "0")).join(".") + "~";
|
|
752
838
|
var defLine = new WeakMap;
|
|
@@ -781,6 +867,9 @@ function flatten(doc, opts = {}) {
|
|
|
781
867
|
const loose = opts.looseGeometry !== false, tx = !!opts.textures, co = !!opts.callouts;
|
|
782
868
|
const src = new Map;
|
|
783
869
|
let subs = 0;
|
|
870
|
+
const open = new Set;
|
|
871
|
+
const maxN = opts.maxInstances ?? 2000000;
|
|
872
|
+
let work = 0;
|
|
784
873
|
const lazy = (f, k) => {
|
|
785
874
|
const key = f.name + "#" + k;
|
|
786
875
|
let fn = src.get(key);
|
|
@@ -793,8 +882,11 @@ function flatten(doc, opts = {}) {
|
|
|
793
882
|
};
|
|
794
883
|
const walk = (d, fname, m, color, path, stepPath, depth, texIn, fixed = null, subPath = [], subMats = [], colour = undefined, scope0 = null) => {
|
|
795
884
|
const f = d.files.get(fname);
|
|
796
|
-
if (!f || depth > maxDepth)
|
|
885
|
+
if (!f || depth > maxDepth || open.has(f))
|
|
797
886
|
return;
|
|
887
|
+
if (++work > maxN)
|
|
888
|
+
throw new ModelLimitError("model", maxN);
|
|
889
|
+
open.add(f);
|
|
798
890
|
let scope = scope0;
|
|
799
891
|
let s = 0;
|
|
800
892
|
const subId = depth > 0 ? ++subs : 0;
|
|
@@ -822,6 +914,8 @@ function flatten(doc, opts = {}) {
|
|
|
822
914
|
const emit = (part, col, mm, sp, k, embedded, tex, cd, sc) => {
|
|
823
915
|
if (fixed)
|
|
824
916
|
sp = fixed;
|
|
917
|
+
if (++work > maxN)
|
|
918
|
+
throw new ModelLimitError("model", maxN);
|
|
825
919
|
const i = { part, color: col, m: mm, step: sp[0], steps: sp, localStep: k, path };
|
|
826
920
|
if (cd)
|
|
827
921
|
i.colour = cd;
|
|
@@ -883,7 +977,10 @@ function flatten(doc, opts = {}) {
|
|
|
883
977
|
continue;
|
|
884
978
|
if (tx && c.fallback)
|
|
885
979
|
continue;
|
|
886
|
-
const
|
|
980
|
+
const mm = mul(m, c.m);
|
|
981
|
+
if (!inRange12(mm))
|
|
982
|
+
continue;
|
|
983
|
+
const col = c.color === 16 ? color : c.color, cd = c.color === 16 ? colour : scope?.get(c.color), sp = [...stepPath, s];
|
|
887
984
|
const tex = tx ? c.tex ?? texIn : null;
|
|
888
985
|
const local = tex ? invert(c.m) : null;
|
|
889
986
|
const lt = tex && local ? transformTexMap(tex, local) : null;
|
|
@@ -903,6 +1000,7 @@ function flatten(doc, opts = {}) {
|
|
|
903
1000
|
emit(c.file, col, mm, sp, s, false, lt, cd, scope);
|
|
904
1001
|
}
|
|
905
1002
|
flushLoose(Infinity);
|
|
1003
|
+
open.delete(f);
|
|
906
1004
|
if (groups) {
|
|
907
1005
|
for (const g of groups.values())
|
|
908
1006
|
if (g.n > 1)
|
|
@@ -1161,7 +1259,7 @@ function blendedColourHex(code) {
|
|
|
1161
1259
|
return "#" + [0, 1, 2].map((k) => (a[k] + b[k] >> 1).toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
1162
1260
|
}
|
|
1163
1261
|
|
|
1164
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1262
|
+
// ../../../../../../../tmp/ldr-build-fQmQmL/src/node/resolver.ts
|
|
1165
1263
|
var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
|
|
1166
1264
|
var UNDER_STUD_RE = /^(stud3|stud3a|stud4.*|stud12|stud16.*|stud18a)\.dat$/i;
|
|
1167
1265
|
var norm2 = (ref) => ref.trim().replace(/\\/g, "/").toLowerCase();
|
|
@@ -1378,7 +1476,7 @@ function splitModel(text, fallbackName = "main.ldr") {
|
|
|
1378
1476
|
main = null;
|
|
1379
1477
|
};
|
|
1380
1478
|
for (const line of text.split(/\r?\n/)) {
|
|
1381
|
-
const m = /^\s*0\s+FILE\s+(
|
|
1479
|
+
const m = /^\s*0\s+FILE\s+(\S[^\r]*)/i.exec(line);
|
|
1382
1480
|
if (m) {
|
|
1383
1481
|
firstBlock();
|
|
1384
1482
|
flush();
|
|
@@ -1488,11 +1586,11 @@ function collectDeps(lib, refs) {
|
|
|
1488
1586
|
return out;
|
|
1489
1587
|
}
|
|
1490
1588
|
|
|
1491
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1589
|
+
// ../../../../../../../tmp/ldr-build-fQmQmL/src/node/compile.ts
|
|
1492
1590
|
import fs2 from "node:fs";
|
|
1493
1591
|
import path2 from "node:path";
|
|
1494
1592
|
|
|
1495
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1593
|
+
// ../../../../../../../tmp/ldr-build-fQmQmL/src/node/lod.ts
|
|
1496
1594
|
import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
|
|
1497
1595
|
await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
|
|
1498
1596
|
function reorderMesh(P, N, C, I) {
|
|
@@ -1684,7 +1782,7 @@ function lodChain(P, C, I) {
|
|
|
1684
1782
|
return out;
|
|
1685
1783
|
}
|
|
1686
1784
|
|
|
1687
|
-
// ../../../../../../../tmp/ldr-build-
|
|
1785
|
+
// ../../../../../../../tmp/ldr-build-fQmQmL/src/node/compile.ts
|
|
1688
1786
|
class BfcState {
|
|
1689
1787
|
accum;
|
|
1690
1788
|
ccw = true;
|
|
@@ -1765,6 +1863,20 @@ var hexRgba = (hex) => {
|
|
|
1765
1863
|
return [(v >> 16) / 255, (v >> 8 & 255) / 255, (v & 255) / 255, 1];
|
|
1766
1864
|
};
|
|
1767
1865
|
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]);
|
|
1866
|
+
var inRangeM = (v) => {
|
|
1867
|
+
for (let i = 0;i < v.length; i++)
|
|
1868
|
+
if (!(Math.abs(v[i]) <= MAX_COORD))
|
|
1869
|
+
return false;
|
|
1870
|
+
return true;
|
|
1871
|
+
};
|
|
1872
|
+
var finiteAll = (v) => {
|
|
1873
|
+
for (let i = 0;i < v.length; i++)
|
|
1874
|
+
if (!Number.isFinite(v[i]))
|
|
1875
|
+
return false;
|
|
1876
|
+
return true;
|
|
1877
|
+
};
|
|
1878
|
+
var f32 = (p) => Math.abs(p[0]) <= MAX_COORD && Math.abs(p[1]) <= MAX_COORD && Math.abs(p[2]) <= MAX_COORD;
|
|
1879
|
+
var PART_WORK_LIMIT = 1e6;
|
|
1768
1880
|
|
|
1769
1881
|
class Compiler {
|
|
1770
1882
|
lib;
|
|
@@ -1781,15 +1893,18 @@ class Compiler {
|
|
|
1781
1893
|
const hit = this.flat.get(key);
|
|
1782
1894
|
if (hit !== undefined)
|
|
1783
1895
|
return hit;
|
|
1896
|
+
let budget = 1e5;
|
|
1784
1897
|
const walk = (fk, M, depth) => {
|
|
1785
1898
|
if (depth > 40)
|
|
1786
1899
|
return true;
|
|
1900
|
+
if (--budget < 0)
|
|
1901
|
+
return false;
|
|
1787
1902
|
for (const raw of this.lib.read(fk).split(/\r?\n/)) {
|
|
1788
1903
|
const t = raw.trim();
|
|
1789
1904
|
const c = t[0];
|
|
1790
1905
|
if (c === "1") {
|
|
1791
1906
|
const r = parseType1(t);
|
|
1792
|
-
if (!r)
|
|
1907
|
+
if (!r || !finiteAll(r.m))
|
|
1793
1908
|
continue;
|
|
1794
1909
|
const base = r.name.trim().replace(/\\/g, "/").split("/").pop().toLowerCase();
|
|
1795
1910
|
const k = this.resolve(r.name, TOP_STUD_RE.test(base));
|
|
@@ -1835,10 +1950,19 @@ class Compiler {
|
|
|
1835
1950
|
if (this.cache.has(ck))
|
|
1836
1951
|
return this.cache.get(ck);
|
|
1837
1952
|
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 };
|
|
1838
|
-
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];
|
|
1953
|
+
const colorOf = (code) => code === "16" ? [1, 1, 1, 0] : code.charCodeAt(1) === 120 && !/^0x2[0-9a-f]{6}/i.test(code) && String(parseColor(code)) !== code ? colorOf(String(parseColor(code))) : [...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];
|
|
1954
|
+
const open = new Set;
|
|
1955
|
+
let work = 0;
|
|
1956
|
+
const lim = this.opts.workLimit ?? PART_WORK_LIMIT;
|
|
1957
|
+
const step = () => {
|
|
1958
|
+
if (++work > lim)
|
|
1959
|
+
throw new ModelLimitError(`part ${key}`, lim);
|
|
1960
|
+
};
|
|
1839
1961
|
const walk = (fk, M, color, invert, target, depth, inh = null, scope0 = null, cull = true) => {
|
|
1840
|
-
if (depth > 40)
|
|
1962
|
+
if (depth > 40 || open.has(fk))
|
|
1841
1963
|
return;
|
|
1964
|
+
step();
|
|
1965
|
+
open.add(fk);
|
|
1842
1966
|
const bfc = new BfcState(cull);
|
|
1843
1967
|
let scope = scope0;
|
|
1844
1968
|
for (const raw of this.lib.read(fk).split(/\r?\n/)) {
|
|
@@ -1861,7 +1985,7 @@ class Compiler {
|
|
|
1861
1985
|
if (c === "1") {
|
|
1862
1986
|
const invertNext = bfc.takeInvert();
|
|
1863
1987
|
const r = parseType1(t);
|
|
1864
|
-
if (!r)
|
|
1988
|
+
if (!r || !finiteAll(r.m))
|
|
1865
1989
|
continue;
|
|
1866
1990
|
const base = r.name.trim().replace(/\\/g, "/").split("/").pop().toLowerCase();
|
|
1867
1991
|
const isTop = TOP_STUD_RE.test(base), isUnder = UNDER_STUD_RE.test(base);
|
|
@@ -1871,6 +1995,8 @@ class Compiler {
|
|
|
1871
1995
|
const childColor = r.color === "16" ? color : r.color, childInh = r.color === "16" ? inh : this.local(r.color, scope);
|
|
1872
1996
|
const fx = singularFix(r.m, () => this.flatY(ck2)), rm = fx ? fx.m : r.m, subCull = bfc.cull && (!fx || fx.cull);
|
|
1873
1997
|
const Mc = compose(M, rm);
|
|
1998
|
+
if (!inRangeM(Mc))
|
|
1999
|
+
continue;
|
|
1874
2000
|
const inv = invert !== invertNext !== det3(rm) < 0;
|
|
1875
2001
|
if (isTop && separateStuds && (!fx || fx.cull)) {
|
|
1876
2002
|
out.studs.push({ kind: ck2, color: childInh ? this.studToken(childColor, childInh) : childColor, m: Mc });
|
|
@@ -1884,7 +2010,11 @@ class Compiler {
|
|
|
1884
2010
|
const tok = t.split(/\s+/);
|
|
1885
2011
|
if (c === "2") {
|
|
1886
2012
|
const v = tok.slice(2, 8).map(Number);
|
|
1887
|
-
|
|
2013
|
+
if (v.length < 6 || !finiteAll(v))
|
|
2014
|
+
continue;
|
|
2015
|
+
const a = xf(M, v.slice(0, 3)), b = xf(M, v.slice(3, 6));
|
|
2016
|
+
if (f32(a) && f32(b))
|
|
2017
|
+
out.edges.push(...a, ...b);
|
|
1888
2018
|
continue;
|
|
1889
2019
|
}
|
|
1890
2020
|
if (c === "5") {
|
|
@@ -1892,18 +2022,26 @@ class Compiler {
|
|
|
1892
2022
|
const dst = target === "under" ? out.condUnder : out.cond;
|
|
1893
2023
|
if (dst) {
|
|
1894
2024
|
const v = t.split(/\s+/).slice(2, 14).map(Number);
|
|
1895
|
-
if (v.length === 12 && v.every(Number.isFinite))
|
|
1896
|
-
|
|
1897
|
-
|
|
2025
|
+
if (v.length === 12 && v.every(Number.isFinite)) {
|
|
2026
|
+
const q = [0, 3, 6, 9].map((i) => xf(M, v.slice(i, i + 3)));
|
|
2027
|
+
if (q.every(f32))
|
|
2028
|
+
for (const p of q)
|
|
2029
|
+
dst.push(...p);
|
|
2030
|
+
}
|
|
1898
2031
|
}
|
|
1899
2032
|
continue;
|
|
1900
2033
|
}
|
|
1901
2034
|
if (c === "3" || c === "4") {
|
|
1902
2035
|
const n = c === "3" ? 3 : 4;
|
|
1903
2036
|
const v = tok.slice(2, 2 + n * 3).map(Number);
|
|
2037
|
+
if (v.length < n * 3 || !finiteAll(v))
|
|
2038
|
+
continue;
|
|
1904
2039
|
const pts = [];
|
|
1905
2040
|
for (let i = 0;i < n; i++)
|
|
1906
2041
|
pts.push(xf(M, v.slice(i * 3, i * 3 + 3)));
|
|
2042
|
+
if (!pts.every(f32))
|
|
2043
|
+
continue;
|
|
2044
|
+
step();
|
|
1907
2045
|
for (const p of pts)
|
|
1908
2046
|
grow(out.bbox, p);
|
|
1909
2047
|
const flip = !bfc.ccw !== invert, rgba = this.rgbaAt(tok[1], color, inh, scope, colorOf), b = out[target];
|
|
@@ -1924,6 +2062,7 @@ class Compiler {
|
|
|
1924
2062
|
}
|
|
1925
2063
|
if (!bfc.certified)
|
|
1926
2064
|
out.uncertified = true;
|
|
2065
|
+
open.delete(fk);
|
|
1927
2066
|
};
|
|
1928
2067
|
const fullKind = new Map;
|
|
1929
2068
|
let culled = 0;
|
|
@@ -2024,9 +2163,11 @@ class Compiler {
|
|
|
2024
2163
|
}
|
|
2025
2164
|
texPass(key, out, separateStuds, colorOf) {
|
|
2026
2165
|
const tbody = { pos: [], col: [] }, texb = new Map;
|
|
2166
|
+
const open = new Set;
|
|
2027
2167
|
const walk = (fk, M, color, invert, target, depth, texIn, inh = null, scope0 = null, cull = true) => {
|
|
2028
|
-
if (depth > 40)
|
|
2168
|
+
if (depth > 40 || open.has(fk))
|
|
2029
2169
|
return;
|
|
2170
|
+
open.add(fk);
|
|
2030
2171
|
const bfc = new BfcState(cull);
|
|
2031
2172
|
let scope = scope0;
|
|
2032
2173
|
let stack = [];
|
|
@@ -2102,7 +2243,7 @@ class Compiler {
|
|
|
2102
2243
|
}
|
|
2103
2244
|
if (c === "1") {
|
|
2104
2245
|
const r = parseType1(t);
|
|
2105
|
-
if (!r)
|
|
2246
|
+
if (!r || !finiteAll(r.m))
|
|
2106
2247
|
continue;
|
|
2107
2248
|
const base = r.name.trim().replace(/\\/g, "/").split("/").pop().toLowerCase();
|
|
2108
2249
|
const isTop = TOP_STUD_RE.test(base), isUnder = UNDER_STUD_RE.test(base);
|
|
@@ -2113,7 +2254,10 @@ class Compiler {
|
|
|
2113
2254
|
const inv = invert !== invertNext !== det3(rm) < 0;
|
|
2114
2255
|
if (isTop && separateStuds && (!fx || fx.cull))
|
|
2115
2256
|
continue;
|
|
2116
|
-
|
|
2257
|
+
const Mc = compose(M, rm);
|
|
2258
|
+
if (!inRangeM(Mc))
|
|
2259
|
+
continue;
|
|
2260
|
+
walk(ck2, Mc, r.color === "16" ? color : r.color, inv, isUnder ? "under" : target, depth + 1, tex, r.color === "16" ? inh : this.local(r.color, scope), scope, subCull);
|
|
2117
2261
|
continue;
|
|
2118
2262
|
}
|
|
2119
2263
|
if (c !== "3" && c !== "4")
|
|
@@ -2121,11 +2265,17 @@ class Compiler {
|
|
|
2121
2265
|
const tok = t.split(/\s+/);
|
|
2122
2266
|
const n = c === "3" ? 3 : 4;
|
|
2123
2267
|
const v = tok.slice(2, 2 + n * 3).map(Number);
|
|
2268
|
+
if (v.length < n * 3 || !finiteAll(v))
|
|
2269
|
+
continue;
|
|
2124
2270
|
const pts = [];
|
|
2125
2271
|
for (let i = 0;i < n; i++)
|
|
2126
2272
|
pts.push(xf(M, v.slice(i * 3, i * 3 + 3)));
|
|
2273
|
+
if (!pts.every(f32))
|
|
2274
|
+
continue;
|
|
2127
2275
|
const order = windOrder(n, !bfc.ccw !== invert, bfc.cull || !!out.uncertified);
|
|
2128
2276
|
const rgba = this.rgbaAt(tok[1], color, inh, scope, colorOf);
|
|
2277
|
+
if (tex && !texmapTriangleUV(tex, pts).every((x) => Number.isFinite(Math.fround(x))))
|
|
2278
|
+
tex = null;
|
|
2129
2279
|
if (tex) {
|
|
2130
2280
|
const tk = tex.glossmap ? tex.texture + "\x00" + tex.glossmap : tex.texture;
|
|
2131
2281
|
let b = texb.get(tk);
|
|
@@ -2148,6 +2298,7 @@ class Compiler {
|
|
|
2148
2298
|
tbody.col.push(...rgba);
|
|
2149
2299
|
}
|
|
2150
2300
|
}
|
|
2301
|
+
open.delete(fk);
|
|
2151
2302
|
};
|
|
2152
2303
|
walk(key, IDENT, "16", false, "body", 0, null);
|
|
2153
2304
|
if (texb.size) {
|
|
@@ -2252,22 +2403,91 @@ function seatStuds(out, fullKind, kindBox) {
|
|
|
2252
2403
|
if (!out.studs.length)
|
|
2253
2404
|
return;
|
|
2254
2405
|
const tris = [out.body.pos, out.under.pos];
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2406
|
+
const nTri = (out.body.pos.length + out.under.pos.length) / 9;
|
|
2407
|
+
let nChk = 0;
|
|
2408
|
+
for (let i = 0;i < out.studs.length; i++) {
|
|
2409
|
+
const f = fullKind.get(i);
|
|
2410
|
+
if (f && f !== out.studs[i].kind)
|
|
2411
|
+
nChk++;
|
|
2412
|
+
}
|
|
2413
|
+
let grid = null;
|
|
2414
|
+
if (nChk * 9 * nTri > 4000000) {
|
|
2415
|
+
const box = [];
|
|
2416
|
+
const sz = [];
|
|
2417
|
+
const seen = new Set;
|
|
2418
|
+
for (let t = 0;t < 2; t++) {
|
|
2419
|
+
const p = tris[t];
|
|
2257
2420
|
for (let i = 0;i < p.length; i += 9) {
|
|
2258
|
-
const
|
|
2259
|
-
|
|
2260
|
-
if (nl < 0.000000001)
|
|
2261
|
-
continue;
|
|
2262
|
-
if (!out.uncertified && nx * up[0] + ny * up[1] + nz * up[2] <= 0)
|
|
2421
|
+
const dk = p[i] + "," + p[i + 1] + "," + p[i + 2] + "," + p[i + 3] + "," + p[i + 4] + "," + p[i + 5] + "," + p[i + 6] + "," + p[i + 7] + "," + p[i + 8];
|
|
2422
|
+
if (seen.has(dk))
|
|
2263
2423
|
continue;
|
|
2264
|
-
|
|
2265
|
-
|
|
2424
|
+
seen.add(dk);
|
|
2425
|
+
const L = Math.max(Math.hypot(p[i + 3] - p[i], p[i + 4] - p[i + 1], p[i + 5] - p[i + 2]), Math.hypot(p[i + 6] - p[i], p[i + 7] - p[i + 1], p[i + 8] - p[i + 2]), Math.hypot(p[i + 6] - p[i + 3], p[i + 7] - p[i + 4], p[i + 8] - p[i + 5]));
|
|
2426
|
+
const g = 0.06 + 0.0004 * L;
|
|
2427
|
+
const b = [Math.min(p[i], p[i + 3], p[i + 6]) - g, Math.min(p[i + 1], p[i + 4], p[i + 7]) - g, Math.min(p[i + 2], p[i + 5], p[i + 8]) - g, Math.max(p[i], p[i + 3], p[i + 6]) + g, Math.max(p[i + 1], p[i + 4], p[i + 7]) + g, Math.max(p[i + 2], p[i + 5], p[i + 8]) + g, t, i];
|
|
2428
|
+
box.push(b);
|
|
2429
|
+
sz.push(Math.max(b[3] - b[0], b[4] - b[1], b[5] - b[2]));
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
sz.sort((x, y) => x - y);
|
|
2433
|
+
const h = Math.max(2 * (sz[sz.length >> 1] ?? 1), 0.5);
|
|
2434
|
+
grid = { levels: [], big: [] };
|
|
2435
|
+
const G = grid;
|
|
2436
|
+
for (const b of box) {
|
|
2437
|
+
const ref = b[6] * 1073741824 + b[7];
|
|
2438
|
+
let placed = false;
|
|
2439
|
+
for (let lv = 0, hl = h;lv < 8 && !placed; lv++, hl *= 8) {
|
|
2440
|
+
const ax = Math.floor(b[0] / hl), bx = Math.floor(b[3] / hl), ay = Math.floor(b[1] / hl), by = Math.floor(b[4] / hl), az = Math.floor(b[2] / hl), bz = Math.floor(b[5] / hl);
|
|
2441
|
+
if (!((bx - ax + 1) * (by - ay + 1) * (bz - az + 1) <= 512))
|
|
2266
2442
|
continue;
|
|
2267
|
-
const
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2443
|
+
const L = G.levels[lv] ??= { h: hl, cells: new Map };
|
|
2444
|
+
placed = true;
|
|
2445
|
+
for (let x = ax;x <= bx; x++)
|
|
2446
|
+
for (let y = ay;y <= by; y++)
|
|
2447
|
+
for (let z = az;z <= bz; z++) {
|
|
2448
|
+
const key = x + "," + y + "," + z;
|
|
2449
|
+
let l = L.cells.get(key);
|
|
2450
|
+
if (!l)
|
|
2451
|
+
L.cells.set(key, l = []);
|
|
2452
|
+
l.push(ref);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
if (!placed)
|
|
2456
|
+
G.big.push(ref);
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
const hit = (p, i, c, up) => {
|
|
2460
|
+
const ax = p[i], ay = p[i + 1], az = p[i + 2], ux = p[i + 3] - ax, uy = p[i + 4] - ay, uz = p[i + 5] - az, vx = p[i + 6] - ax, vy = p[i + 7] - ay, vz = p[i + 8] - az;
|
|
2461
|
+
const nx = uy * vz - uz * vy, ny = uz * vx - ux * vz, nz = ux * vy - uy * vx, nl = Math.hypot(nx, ny, nz);
|
|
2462
|
+
if (nl < 0.000000001)
|
|
2463
|
+
return false;
|
|
2464
|
+
if (!out.uncertified && nx * up[0] + ny * up[1] + nz * up[2] <= 0)
|
|
2465
|
+
return false;
|
|
2466
|
+
const wx = c[0] - ax, wy = c[1] - ay, wz = c[2] - az;
|
|
2467
|
+
if (Math.abs((wx * nx + wy * ny + wz * nz) / nl) > 0.05)
|
|
2468
|
+
return false;
|
|
2469
|
+
const uu = ux * ux + uy * uy + uz * uz, vv = vx * vx + vy * vy + vz * vz, uv = ux * vx + uy * vy + uz * vz, wu = wx * ux + wy * uy + wz * uz, wv = wx * vx + wy * vy + wz * vz;
|
|
2470
|
+
const d = uv * uv - uu * vv, s = (uv * wv - vv * wu) / d, t = (uv * wu - uu * wv) / d, e = 0.0001;
|
|
2471
|
+
return s >= -e && t >= -e && s + t <= 1 + e;
|
|
2472
|
+
};
|
|
2473
|
+
const covered = (c, up) => {
|
|
2474
|
+
if (!grid) {
|
|
2475
|
+
for (const p of tris)
|
|
2476
|
+
for (let i = 0;i < p.length; i += 9)
|
|
2477
|
+
if (hit(p, i, c, up))
|
|
2478
|
+
return true;
|
|
2479
|
+
return false;
|
|
2480
|
+
}
|
|
2481
|
+
const G = grid;
|
|
2482
|
+
const at = (ref) => hit(tris[ref >= 1073741824 ? 1 : 0], ref % 1073741824, c, up);
|
|
2483
|
+
for (const r of G.big)
|
|
2484
|
+
if (at(r))
|
|
2485
|
+
return true;
|
|
2486
|
+
for (const L of G.levels)
|
|
2487
|
+
if (L) {
|
|
2488
|
+
for (const r of L.cells.get(Math.floor(c[0] / L.h) + "," + Math.floor(c[1] / L.h) + "," + Math.floor(c[2] / L.h)) ?? [])
|
|
2489
|
+
if (at(r))
|
|
2490
|
+
return true;
|
|
2271
2491
|
}
|
|
2272
2492
|
return false;
|
|
2273
2493
|
};
|
|
@@ -2494,9 +2714,9 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
|
|
|
2494
2714
|
return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
|
|
2495
2715
|
}
|
|
2496
2716
|
|
|
2497
|
-
// ../../../../../../../tmp/ldr-build-
|
|
2717
|
+
// ../../../../../../../tmp/ldr-build-fQmQmL/src/node/build-pack.ts
|
|
2498
2718
|
function packCompilerOpts(o) {
|
|
2499
|
-
return { studs: o.studs ?? "low", prims: o.primitives ?? "full", weld: o.weld ?? true, separateStuds: true, cond: !!o.cond, tex: !!o.textures };
|
|
2719
|
+
return { studs: o.studs ?? "low", prims: o.primitives ?? "full", weld: o.weld ?? true, separateStuds: true, cond: !!o.cond, tex: !!o.textures, ...o.partWorkLimit ? { workLimit: o.partWorkLimit } : {} };
|
|
2500
2720
|
}
|
|
2501
2721
|
function buildPack(o) {
|
|
2502
2722
|
const lib = new Library(o.ldrawRoot);
|
|
@@ -2567,7 +2787,7 @@ function buildPack(o) {
|
|
|
2567
2787
|
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 } : {} };
|
|
2568
2788
|
}
|
|
2569
2789
|
|
|
2570
|
-
// ../../../../../../../tmp/ldr-build-
|
|
2790
|
+
// ../../../../../../../tmp/ldr-build-fQmQmL/src/cli.ts
|
|
2571
2791
|
var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
|
|
2572
2792
|
|
|
2573
2793
|
Usage:
|