ldr 0.33.1 → 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-78cdvu/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-78cdvu/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-78cdvu/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-78cdvu/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;
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);
557
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];
@@ -671,6 +742,29 @@ function invert(m) {
671
742
  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];
672
743
  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];
673
744
  }
745
+ function singularFix(m, flatY) {
746
+ const det = (q) => q[3] * (q[7] * q[11] - q[8] * q[10]) - q[4] * (q[6] * q[11] - q[8] * q[9]) + q[5] * (q[6] * q[10] - q[7] * q[9]);
747
+ const rows = Math.hypot(m[3], m[4], m[5]) * Math.hypot(m[6], m[7], m[8]) * Math.hypot(m[9], m[10], m[11]);
748
+ if (!(Math.abs(det(m)) <= 0.000000001 * rows))
749
+ return null;
750
+ if (!flatY())
751
+ return { m, cull: false };
752
+ const q = [...m];
753
+ const ok = () => Math.abs(det(q)) > 0.000000001 * Math.hypot(q[3], q[4], q[5]) * Math.hypot(q[6], q[7], q[8]) * Math.hypot(q[9], q[10], q[11]);
754
+ for (let i = 0;i < 3; i++)
755
+ if (q[3 + 3 * i] === 0 && q[4 + 3 * i] === 0 && q[5 + 3 * i] === 0) {
756
+ q[4 + 3 * i] = 1;
757
+ if (ok())
758
+ return { m: q, cull: true };
759
+ }
760
+ if (q[4] === 0 && q[7] === 0 && q[10] === 0)
761
+ for (let i = 0;i < 3; i++) {
762
+ q[4 + 3 * i] = 1;
763
+ if (ok())
764
+ return { m: q, cull: true };
765
+ }
766
+ return { m, cull: false };
767
+ }
674
768
  var num = (x) => String(+x.toFixed(6) || 0);
675
769
  var quote = (s) => '"' + s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"';
676
770
  function texWrapper(part, map) {
@@ -723,7 +817,22 @@ function fileStepViews(f) {
723
817
  return out;
724
818
  }
725
819
 
726
- // ../../../../../../../tmp/ldr-build-78cdvu/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
+ };
727
836
  var CALLOUT_END = 999999;
728
837
  var stepKey = (steps) => steps.map((x) => String(x).padStart(6, "0")).join(".") + "~";
729
838
  var defLine = new WeakMap;
@@ -758,6 +867,9 @@ function flatten(doc, opts = {}) {
758
867
  const loose = opts.looseGeometry !== false, tx = !!opts.textures, co = !!opts.callouts;
759
868
  const src = new Map;
760
869
  let subs = 0;
870
+ const open = new Set;
871
+ const maxN = opts.maxInstances ?? 2000000;
872
+ let work = 0;
761
873
  const lazy = (f, k) => {
762
874
  const key = f.name + "#" + k;
763
875
  let fn = src.get(key);
@@ -770,8 +882,11 @@ function flatten(doc, opts = {}) {
770
882
  };
771
883
  const walk = (d, fname, m, color, path, stepPath, depth, texIn, fixed = null, subPath = [], subMats = [], colour = undefined, scope0 = null) => {
772
884
  const f = d.files.get(fname);
773
- if (!f || depth > maxDepth)
885
+ if (!f || depth > maxDepth || open.has(f))
774
886
  return;
887
+ if (++work > maxN)
888
+ throw new ModelLimitError("model", maxN);
889
+ open.add(f);
775
890
  let scope = scope0;
776
891
  let s = 0;
777
892
  const subId = depth > 0 ? ++subs : 0;
@@ -799,6 +914,8 @@ function flatten(doc, opts = {}) {
799
914
  const emit = (part, col, mm, sp, k, embedded, tex, cd, sc) => {
800
915
  if (fixed)
801
916
  sp = fixed;
917
+ if (++work > maxN)
918
+ throw new ModelLimitError("model", maxN);
802
919
  const i = { part, color: col, m: mm, step: sp[0], steps: sp, localStep: k, path };
803
920
  if (cd)
804
921
  i.colour = cd;
@@ -860,7 +977,10 @@ function flatten(doc, opts = {}) {
860
977
  continue;
861
978
  if (tx && c.fallback)
862
979
  continue;
863
- 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];
864
984
  const tex = tx ? c.tex ?? texIn : null;
865
985
  const local = tex ? invert(c.m) : null;
866
986
  const lt = tex && local ? transformTexMap(tex, local) : null;
@@ -880,6 +1000,7 @@ function flatten(doc, opts = {}) {
880
1000
  emit(c.file, col, mm, sp, s, false, lt, cd, scope);
881
1001
  }
882
1002
  flushLoose(Infinity);
1003
+ open.delete(f);
883
1004
  if (groups) {
884
1005
  for (const g of groups.values())
885
1006
  if (g.n > 1)
@@ -1138,7 +1259,7 @@ function blendedColourHex(code) {
1138
1259
  return "#" + [0, 1, 2].map((k) => (a[k] + b[k] >> 1).toString(16).padStart(2, "0")).join("").toUpperCase();
1139
1260
  }
1140
1261
 
1141
- // ../../../../../../../tmp/ldr-build-78cdvu/src/node/resolver.ts
1262
+ // ../../../../../../../tmp/ldr-build-5tVh0D/src/node/resolver.ts
1142
1263
  var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
1143
1264
  var UNDER_STUD_RE = /^(stud3|stud3a|stud4.*|stud12|stud16.*|stud18a)\.dat$/i;
1144
1265
  var norm2 = (ref) => ref.trim().replace(/\\/g, "/").toLowerCase();
@@ -1355,7 +1476,7 @@ function splitModel(text, fallbackName = "main.ldr") {
1355
1476
  main = null;
1356
1477
  };
1357
1478
  for (const line of text.split(/\r?\n/)) {
1358
- const m = /^\s*0\s+FILE\s+(.+)$/i.exec(line);
1479
+ const m = /^\s*0\s+FILE\s+(\S[^\r]*)/i.exec(line);
1359
1480
  if (m) {
1360
1481
  firstBlock();
1361
1482
  flush();
@@ -1465,11 +1586,11 @@ function collectDeps(lib, refs) {
1465
1586
  return out;
1466
1587
  }
1467
1588
 
1468
- // ../../../../../../../tmp/ldr-build-78cdvu/src/node/compile.ts
1589
+ // ../../../../../../../tmp/ldr-build-5tVh0D/src/node/compile.ts
1469
1590
  import fs2 from "node:fs";
1470
1591
  import path2 from "node:path";
1471
1592
 
1472
- // ../../../../../../../tmp/ldr-build-78cdvu/src/node/lod.ts
1593
+ // ../../../../../../../tmp/ldr-build-5tVh0D/src/node/lod.ts
1473
1594
  import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
1474
1595
  await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
1475
1596
  function reorderMesh(P, N, C, I) {
@@ -1661,7 +1782,7 @@ function lodChain(P, C, I) {
1661
1782
  return out;
1662
1783
  }
1663
1784
 
1664
- // ../../../../../../../tmp/ldr-build-78cdvu/src/node/compile.ts
1785
+ // ../../../../../../../tmp/ldr-build-5tVh0D/src/node/compile.ts
1665
1786
  class BfcState {
1666
1787
  accum;
1667
1788
  ccw = true;
@@ -1742,6 +1863,20 @@ var hexRgba = (hex) => {
1742
1863
  return [(v >> 16) / 255, (v >> 8 & 255) / 255, (v & 255) / 255, 1];
1743
1864
  };
1744
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;
1745
1880
 
1746
1881
  class Compiler {
1747
1882
  lib;
@@ -1753,6 +1888,43 @@ class Compiler {
1753
1888
  this.opts = opts;
1754
1889
  this.colors = loadColors(lib.root);
1755
1890
  }
1891
+ flat = new Map;
1892
+ flatY(key) {
1893
+ const hit = this.flat.get(key);
1894
+ if (hit !== undefined)
1895
+ return hit;
1896
+ let budget = 1e5;
1897
+ const walk = (fk, M, depth) => {
1898
+ if (depth > 40)
1899
+ return true;
1900
+ if (--budget < 0)
1901
+ return false;
1902
+ for (const raw of this.lib.read(fk).split(/\r?\n/)) {
1903
+ const t = raw.trim();
1904
+ const c = t[0];
1905
+ if (c === "1") {
1906
+ const r = parseType1(t);
1907
+ if (!r || !finiteAll(r.m))
1908
+ continue;
1909
+ const base = r.name.trim().replace(/\\/g, "/").split("/").pop().toLowerCase();
1910
+ const k = this.resolve(r.name, TOP_STUD_RE.test(base));
1911
+ if (k && !walk(k, compose(M, r.m), depth + 1))
1912
+ return false;
1913
+ continue;
1914
+ }
1915
+ if (c !== "2" && c !== "3" && c !== "4" && c !== "5")
1916
+ continue;
1917
+ const v = t.split(/\s+/).slice(2).map(Number), n = c === "2" ? 2 : c === "3" ? 3 : 4;
1918
+ for (let i = 0;i < n * 3; i += 3)
1919
+ if (Math.abs(M[1] + M[6] * v[i] + M[7] * v[i + 1] + M[8] * v[i + 2]) > 0.000001)
1920
+ return false;
1921
+ }
1922
+ return true;
1923
+ };
1924
+ const r = walk(key, IDENT, 0);
1925
+ this.flat.set(key, r);
1926
+ return r;
1927
+ }
1756
1928
  resolve(ref, isStud) {
1757
1929
  const key = this.lib.resolve(ref);
1758
1930
  if (!key)
@@ -1778,10 +1950,19 @@ class Compiler {
1778
1950
  if (this.cache.has(ck))
1779
1951
  return this.cache.get(ck);
1780
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 };
1781
- 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
+ };
1782
1961
  const walk = (fk, M, color, invert, target, depth, inh = null, scope0 = null, cull = true) => {
1783
- if (depth > 40)
1962
+ if (depth > 40 || open.has(fk))
1784
1963
  return;
1964
+ step();
1965
+ open.add(fk);
1785
1966
  const bfc = new BfcState(cull);
1786
1967
  let scope = scope0;
1787
1968
  for (const raw of this.lib.read(fk).split(/\r?\n/)) {
@@ -1804,7 +1985,7 @@ class Compiler {
1804
1985
  if (c === "1") {
1805
1986
  const invertNext = bfc.takeInvert();
1806
1987
  const r = parseType1(t);
1807
- if (!r)
1988
+ if (!r || !finiteAll(r.m))
1808
1989
  continue;
1809
1990
  const base = r.name.trim().replace(/\\/g, "/").split("/").pop().toLowerCase();
1810
1991
  const isTop = TOP_STUD_RE.test(base), isUnder = UNDER_STUD_RE.test(base);
@@ -1812,21 +1993,28 @@ class Compiler {
1812
1993
  if (!ck2)
1813
1994
  continue;
1814
1995
  const childColor = r.color === "16" ? color : r.color, childInh = r.color === "16" ? inh : this.local(r.color, scope);
1815
- const Mc = compose(M, r.m);
1816
- const inv = invert !== invertNext !== det3(r.m) < 0;
1817
- if (isTop && separateStuds) {
1996
+ const fx = singularFix(r.m, () => this.flatY(ck2)), rm = fx ? fx.m : r.m, subCull = bfc.cull && (!fx || fx.cull);
1997
+ const Mc = compose(M, rm);
1998
+ if (!inRangeM(Mc))
1999
+ continue;
2000
+ const inv = invert !== invertNext !== det3(rm) < 0;
2001
+ if (isTop && separateStuds && (!fx || fx.cull)) {
1818
2002
  out.studs.push({ kind: ck2, color: childInh ? this.studToken(childColor, childInh) : childColor, m: Mc });
1819
2003
  fullKind.set(out.studs.length - 1, this.lib.resolve(r.name));
1820
2004
  growBox(out.bbox, Mc, this.compile(ck2, false).bbox);
1821
2005
  continue;
1822
2006
  }
1823
- walk(ck2, Mc, childColor, inv, isUnder ? "under" : target, depth + 1, childInh, scope, bfc.cull);
2007
+ walk(ck2, Mc, childColor, inv, isUnder ? "under" : target, depth + 1, childInh, scope, subCull);
1824
2008
  continue;
1825
2009
  }
1826
2010
  const tok = t.split(/\s+/);
1827
2011
  if (c === "2") {
1828
2012
  const v = tok.slice(2, 8).map(Number);
1829
- 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);
1830
2018
  continue;
1831
2019
  }
1832
2020
  if (c === "5") {
@@ -1834,18 +2022,26 @@ class Compiler {
1834
2022
  const dst = target === "under" ? out.condUnder : out.cond;
1835
2023
  if (dst) {
1836
2024
  const v = t.split(/\s+/).slice(2, 14).map(Number);
1837
- if (v.length === 12 && v.every(Number.isFinite))
1838
- for (let i = 0;i < 12; i += 3)
1839
- 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
+ }
1840
2031
  }
1841
2032
  continue;
1842
2033
  }
1843
2034
  if (c === "3" || c === "4") {
1844
2035
  const n = c === "3" ? 3 : 4;
1845
2036
  const v = tok.slice(2, 2 + n * 3).map(Number);
2037
+ if (v.length < n * 3 || !finiteAll(v))
2038
+ continue;
1846
2039
  const pts = [];
1847
2040
  for (let i = 0;i < n; i++)
1848
2041
  pts.push(xf(M, v.slice(i * 3, i * 3 + 3)));
2042
+ if (!pts.every(f32))
2043
+ continue;
2044
+ step();
1849
2045
  for (const p of pts)
1850
2046
  grow(out.bbox, p);
1851
2047
  const flip = !bfc.ccw !== invert, rgba = this.rgbaAt(tok[1], color, inh, scope, colorOf), b = out[target];
@@ -1866,6 +2062,7 @@ class Compiler {
1866
2062
  }
1867
2063
  if (!bfc.certified)
1868
2064
  out.uncertified = true;
2065
+ open.delete(fk);
1869
2066
  };
1870
2067
  const fullKind = new Map;
1871
2068
  let culled = 0;
@@ -1966,9 +2163,11 @@ class Compiler {
1966
2163
  }
1967
2164
  texPass(key, out, separateStuds, colorOf) {
1968
2165
  const tbody = { pos: [], col: [] }, texb = new Map;
2166
+ const open = new Set;
1969
2167
  const walk = (fk, M, color, invert, target, depth, texIn, inh = null, scope0 = null, cull = true) => {
1970
- if (depth > 40)
2168
+ if (depth > 40 || open.has(fk))
1971
2169
  return;
2170
+ open.add(fk);
1972
2171
  const bfc = new BfcState(cull);
1973
2172
  let scope = scope0;
1974
2173
  let stack = [];
@@ -2044,17 +2243,21 @@ class Compiler {
2044
2243
  }
2045
2244
  if (c === "1") {
2046
2245
  const r = parseType1(t);
2047
- if (!r)
2246
+ if (!r || !finiteAll(r.m))
2048
2247
  continue;
2049
2248
  const base = r.name.trim().replace(/\\/g, "/").split("/").pop().toLowerCase();
2050
2249
  const isTop = TOP_STUD_RE.test(base), isUnder = UNDER_STUD_RE.test(base);
2051
2250
  const ck2 = this.resolve(r.name, isTop);
2052
2251
  if (!ck2)
2053
2252
  continue;
2054
- const inv = invert !== invertNext !== det3(r.m) < 0;
2055
- if (isTop && separateStuds)
2253
+ const fx = singularFix(r.m, () => this.flatY(ck2)), rm = fx ? fx.m : r.m, subCull = bfc.cull && (!fx || fx.cull);
2254
+ const inv = invert !== invertNext !== det3(rm) < 0;
2255
+ if (isTop && separateStuds && (!fx || fx.cull))
2256
+ continue;
2257
+ const Mc = compose(M, rm);
2258
+ if (!inRangeM(Mc))
2056
2259
  continue;
2057
- walk(ck2, compose(M, r.m), r.color === "16" ? color : r.color, inv, isUnder ? "under" : target, depth + 1, tex, r.color === "16" ? inh : this.local(r.color, scope), scope, bfc.cull);
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);
2058
2261
  continue;
2059
2262
  }
2060
2263
  if (c !== "3" && c !== "4")
@@ -2062,11 +2265,17 @@ class Compiler {
2062
2265
  const tok = t.split(/\s+/);
2063
2266
  const n = c === "3" ? 3 : 4;
2064
2267
  const v = tok.slice(2, 2 + n * 3).map(Number);
2268
+ if (v.length < n * 3 || !finiteAll(v))
2269
+ continue;
2065
2270
  const pts = [];
2066
2271
  for (let i = 0;i < n; i++)
2067
2272
  pts.push(xf(M, v.slice(i * 3, i * 3 + 3)));
2273
+ if (!pts.every(f32))
2274
+ continue;
2068
2275
  const order = windOrder(n, !bfc.ccw !== invert, bfc.cull || !!out.uncertified);
2069
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;
2070
2279
  if (tex) {
2071
2280
  const tk = tex.glossmap ? tex.texture + "\x00" + tex.glossmap : tex.texture;
2072
2281
  let b = texb.get(tk);
@@ -2089,6 +2298,7 @@ class Compiler {
2089
2298
  tbody.col.push(...rgba);
2090
2299
  }
2091
2300
  }
2301
+ open.delete(fk);
2092
2302
  };
2093
2303
  walk(key, IDENT, "16", false, "body", 0, null);
2094
2304
  if (texb.size) {
@@ -2193,23 +2403,79 @@ function seatStuds(out, fullKind, kindBox) {
2193
2403
  if (!out.studs.length)
2194
2404
  return;
2195
2405
  const tris = [out.body.pos, out.under.pos];
2196
- const covered = (c, up) => {
2197
- 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];
2198
2419
  for (let i = 0;i < p.length; i += 9) {
2199
- 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;
2200
- const nx = uy * vz - uz * vy, ny = uz * vx - ux * vz, nz = ux * vy - uy * vx, nl = Math.hypot(nx, ny, nz);
2201
- if (nl < 0.000000001)
2202
- continue;
2203
- if (!out.uncertified && nx * up[0] + ny * up[1] + nz * up[2] <= 0)
2204
- continue;
2205
- const wx = c[0] - ax, wy = c[1] - ay, wz = c[2] - az;
2206
- if (Math.abs((wx * nx + wy * ny + wz * nz) / nl) > 0.05)
2207
- continue;
2208
- 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;
2209
- const d = uv * uv - uu * vv, s = (uv * wv - vv * wu) / d, t = (uv * wu - uu * wv) / d, e = 0.0001;
2210
- if (s >= -e && t >= -e && s + t <= 1 + e)
2211
- 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]));
2212
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;
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;
2213
2479
  return false;
2214
2480
  };
2215
2481
  out.studs.forEach((s, i) => {
@@ -2435,9 +2701,9 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
2435
2701
  return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
2436
2702
  }
2437
2703
 
2438
- // ../../../../../../../tmp/ldr-build-78cdvu/src/node/build-pack.ts
2704
+ // ../../../../../../../tmp/ldr-build-5tVh0D/src/node/build-pack.ts
2439
2705
  function packCompilerOpts(o) {
2440
- 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 } : {} };
2441
2707
  }
2442
2708
  function buildPack(o) {
2443
2709
  const lib = new Library(o.ldrawRoot);
@@ -2508,7 +2774,7 @@ function buildPack(o) {
2508
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 } : {} };
2509
2775
  }
2510
2776
 
2511
- // ../../../../../../../tmp/ldr-build-78cdvu/src/cli.ts
2777
+ // ../../../../../../../tmp/ldr-build-5tVh0D/src/cli.ts
2512
2778
  var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
2513
2779
 
2514
2780
  Usage: