ldr 0.33.2 → 0.33.3

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 CHANGED
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ../../../../../../../tmp/ldr-build-RWoXTz/src/cli.ts
3
+ // ../../../../../../../tmp/ldr-build-5tVh0D/src/cli.ts
4
4
  import fs4 from "node:fs";
5
5
  import zlib from "node:zlib";
6
6
 
7
- // ../../../../../../../tmp/ldr-build-RWoXTz/src/node/build-pack.ts
7
+ // ../../../../../../../tmp/ldr-build-5tVh0D/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-RWoXTz/src/node/resolver.ts
11
+ // ../../../../../../../tmp/ldr-build-5tVh0D/src/node/resolver.ts
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
14
 
15
- // ../../../../../../../tmp/ldr-build-RWoXTz/src/spec.ts
15
+ // ../../../../../../../tmp/ldr-build-5tVh0D/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
- const clean = s.replace(/[^A-Za-z0-9+/]/g, "");
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
- m[k] = +t;
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
- return { t: 1, color: parseColor(ct), m, file: norm(file) };
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)
154
167
  return null;
155
- const color = parseColor(tk[1]);
156
- const nums = tk.slice(2).map(Number);
157
- if (t === 1)
158
- return { t: 1, color, m: nums.slice(0, 12), file: norm(tk.slice(14).join(" ")) };
168
+ const color = parseColor(tk[1]), n = NUMS[t];
169
+ if (!Number.isFinite(color) || tk.length < 2 + n + (t === 1 ? 1 : 0))
170
+ return null;
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.slice(0, 6) };
183
+ return { t: 2, color, p: nums };
161
184
  if (t === 3)
162
- return { t: 3, color, p: nums.slice(0, 9) };
185
+ return { t: 3, color, p: nums };
163
186
  if (t === 4)
164
- return { t: 4, color, p: nums.slice(0, 12) };
165
- return { t: 5, color, p: nums.slice(0, 12) };
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
- if (!SPECIAL0.has(kw)) {
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
- if (tk[1] === "FILE") {
287
- firstBlock();
288
- skip = false;
289
- start(text.slice(5));
290
- continue;
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 (tk[1] === "NOFILE") {
320
+ if (k1 === "NOFILE") {
293
321
  closeData();
294
322
  cur = null;
295
323
  skip = true;
296
324
  continue;
297
325
  }
298
- if (tk[1] === "!DATA") {
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
- c.rotstep = { x: +tk[2] || 0, y: +tk[3] || 0, z: +tk[4] || 0, mode: ["REL", "ABS", "ADD"].includes(mode) ? mode : "REL" };
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
- const a = sAng(radial(p, p1, n));
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) => par[i] === i ? i : par[i] = f(par[i]);
547
- for (let i = 0;i < faces.length; i++)
548
- for (let j = i + 1;j < faces.length; j++) {
549
- const A = faces[i], B = faces[j];
550
- const va = [0, 1, 2].map((k) => vk[A * 3 + k]), vb = new Set([0, 1, 2].map((k) => vk[B * 3 + k]));
551
- for (const o of va)
552
- if (o !== key && vb.has(o)) {
553
- const e = ek(key, o);
554
- if ((edgeFaces.get(e)?.length ?? 0) >= 2 && joinable(e, A, B))
555
- par[f(i)] = f(j);
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;
557
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;
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-RWoXTz/src/model.ts
820
+ // ../../../../../../../tmp/ldr-build-5tVh0D/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 col = c.color === 16 ? color : c.color, cd = c.color === 16 ? colour : scope?.get(c.color), mm = mul(m, c.m), sp = [...stepPath, s];
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-RWoXTz/src/node/resolver.ts
1262
+ // ../../../../../../../tmp/ldr-build-5tVh0D/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+(.+)$/i.exec(line);
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-RWoXTz/src/node/compile.ts
1589
+ // ../../../../../../../tmp/ldr-build-5tVh0D/src/node/compile.ts
1492
1590
  import fs2 from "node:fs";
1493
1591
  import path2 from "node:path";
1494
1592
 
1495
- // ../../../../../../../tmp/ldr-build-RWoXTz/src/node/lod.ts
1593
+ // ../../../../../../../tmp/ldr-build-5tVh0D/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-RWoXTz/src/node/compile.ts
1785
+ // ../../../../../../../tmp/ldr-build-5tVh0D/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
- out.edges.push(...xf(M, v.slice(0, 3)), ...xf(M, v.slice(3, 6)));
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
- for (let i = 0;i < 12; i += 3)
1897
- dst.push(...xf(M, v.slice(i, i + 3)));
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
- walk(ck2, compose(M, rm), r.color === "16" ? color : r.color, inv, isUnder ? "under" : target, depth + 1, tex, r.color === "16" ? inh : this.local(r.color, scope), scope, subCull);
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,23 +2403,79 @@ 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 covered = (c, up) => {
2256
- for (const p of tris)
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
+ for (let t = 0;t < 2; t++) {
2418
+ const p = tris[t];
2257
2419
  for (let i = 0;i < p.length; i += 9) {
2258
- 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;
2259
- const nx = uy * vz - uz * vy, ny = uz * vx - ux * vz, nz = ux * vy - uy * vx, nl = Math.hypot(nx, ny, nz);
2260
- if (nl < 0.000000001)
2261
- continue;
2262
- if (!out.uncertified && nx * up[0] + ny * up[1] + nz * up[2] <= 0)
2263
- continue;
2264
- const wx = c[0] - ax, wy = c[1] - ay, wz = c[2] - az;
2265
- if (Math.abs((wx * nx + wy * ny + wz * nz) / nl) > 0.05)
2266
- continue;
2267
- 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;
2268
- const d = uv * uv - uu * vv, s = (uv * wv - vv * wu) / d, t = (uv * wu - uu * wv) / d, e = 0.0001;
2269
- if (s >= -e && t >= -e && s + t <= 1 + e)
2270
- return true;
2420
+ 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]));
2421
+ const g = 0.06 + 0.0004 * L;
2422
+ 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];
2423
+ box.push(b);
2424
+ sz.push(Math.max(b[3] - b[0], b[4] - b[1], b[5] - b[2]));
2425
+ }
2426
+ }
2427
+ sz.sort((x, y) => x - y);
2428
+ const h = Math.max(2 * (sz[sz.length >> 1] ?? 1), 0.5);
2429
+ grid = { h, cells: new Map, big: [] };
2430
+ const G = grid;
2431
+ for (const b of box) {
2432
+ const ref = b[6] * 1073741824 + b[7];
2433
+ const ax = Math.floor(b[0] / h), bx = Math.floor(b[3] / h), ay = Math.floor(b[1] / h), by = Math.floor(b[4] / h), az = Math.floor(b[2] / h), bz = Math.floor(b[5] / h);
2434
+ if (!((bx - ax + 1) * (by - ay + 1) * (bz - az + 1) <= 512)) {
2435
+ G.big.push(ref);
2436
+ continue;
2271
2437
  }
2438
+ for (let x = ax;x <= bx; x++)
2439
+ for (let y = ay;y <= by; y++)
2440
+ for (let z = az;z <= bz; z++) {
2441
+ const key = x + "," + y + "," + z;
2442
+ let l = G.cells.get(key);
2443
+ if (!l)
2444
+ G.cells.set(key, l = []);
2445
+ l.push(ref);
2446
+ }
2447
+ }
2448
+ }
2449
+ const hit = (p, i, c, up) => {
2450
+ 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;
2451
+ const nx = uy * vz - uz * vy, ny = uz * vx - ux * vz, nz = ux * vy - uy * vx, nl = Math.hypot(nx, ny, nz);
2452
+ if (nl < 0.000000001)
2453
+ return false;
2454
+ if (!out.uncertified && nx * up[0] + ny * up[1] + nz * up[2] <= 0)
2455
+ return false;
2456
+ const wx = c[0] - ax, wy = c[1] - ay, wz = c[2] - az;
2457
+ if (Math.abs((wx * nx + wy * ny + wz * nz) / nl) > 0.05)
2458
+ return false;
2459
+ 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;
2460
+ const d = uv * uv - uu * vv, s = (uv * wv - vv * wu) / d, t = (uv * wu - uu * wv) / d, e = 0.0001;
2461
+ return s >= -e && t >= -e && s + t <= 1 + e;
2462
+ };
2463
+ const covered = (c, up) => {
2464
+ if (!grid) {
2465
+ for (const p of tris)
2466
+ for (let i = 0;i < p.length; i += 9)
2467
+ if (hit(p, i, c, up))
2468
+ return true;
2469
+ return false;
2470
+ }
2471
+ const G = grid;
2472
+ const at = (ref) => hit(tris[ref >= 1073741824 ? 1 : 0], ref % 1073741824, c, up);
2473
+ for (const r of G.big)
2474
+ if (at(r))
2475
+ return true;
2476
+ for (const r of G.cells.get(Math.floor(c[0] / G.h) + "," + Math.floor(c[1] / G.h) + "," + Math.floor(c[2] / G.h)) ?? [])
2477
+ if (at(r))
2478
+ return true;
2272
2479
  return false;
2273
2480
  };
2274
2481
  out.studs.forEach((s, i) => {
@@ -2494,9 +2701,9 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
2494
2701
  return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
2495
2702
  }
2496
2703
 
2497
- // ../../../../../../../tmp/ldr-build-RWoXTz/src/node/build-pack.ts
2704
+ // ../../../../../../../tmp/ldr-build-5tVh0D/src/node/build-pack.ts
2498
2705
  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 };
2706
+ 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
2707
  }
2501
2708
  function buildPack(o) {
2502
2709
  const lib = new Library(o.ldrawRoot);
@@ -2567,7 +2774,7 @@ function buildPack(o) {
2567
2774
  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
2775
  }
2569
2776
 
2570
- // ../../../../../../../tmp/ldr-build-RWoXTz/src/cli.ts
2777
+ // ../../../../../../../tmp/ldr-build-5tVh0D/src/cli.ts
2571
2778
  var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
2572
2779
 
2573
2780
  Usage: