ldr 0.24.2 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -83,16 +83,17 @@ Light the scene (or set `scene.environment`) as you would for any `MeshStandardM
83
83
  | export | description |
84
84
  |---|---|
85
85
  | `parse(text, name?, { geometryText? }) → Doc` | Parses LDraw text into files and commands. MPD-aware: `0 FILE`/`0 NOFILE` and `0 !DATA` blocks. |
86
- | `flatten(doc, options?) → PartInstance[]` | Expands submodels into placed parts. Each instance has `part`, `color`, a 12-number matrix `m`, `step`, nested `steps`, `path` and the ROTSTEP `view` of its step. |
86
+ | `flatten(doc, options?) → PartInstance[]` | Expands submodels into placed parts. Each instance has `part`, `color`, a 12-number matrix `m`, `step`, nested `steps`, `path` and the ROTSTEP `view` of its step. `colour` (an `LDrawColor`) is set when a `0 !COLOUR` line in the model itself defines the instance's colour. |
87
87
  | `parseModel(text, name?, options?)` | `parse` followed by `flatten`. |
88
88
  | `sequenceSteps(instances) → { count, stepOf, views }` | Gives each instance a global build-step index, sequencing nested submodel steps. `views` holds the ROTSTEP view per global step. |
89
89
  | `parseLDConfig(text) → Map<code, LDrawColor>` | Reads a `!COLOUR` table into `{ code, name, hex, edge, alpha, luminance, finish }` (CHROME / PEARLESCENT / RUBBER / METAL / MATTE_METALLIC / MATERIAL GLITTER|SPECKLE|FABRIC). |
90
+ | `blendedColourHex(code)` | RGB of a blended (old dithered) code 256-511, for codes LDConfig doesn't define. |
90
91
  | `parseColourLine`, `colourSurface`, `colourMaterialKey`, `makeFinishMaterial` | Spec finish → PBR surface (metalness/roughness/clearcoat/sheen; glitter/speckle use cheap procedural grain). |
91
92
  | `fileKind(doc, name)` | Decides whether an MPD file is a part or a model: by its header type, otherwise by whether it has its own geometry. |
92
93
  | `looseSteps` / `looseFile` / `looseSource` / `loosePartName` | A model's own type 2–5 geometry, per step, as synthetic parts named `<file>#<step>`. |
93
94
  | `rotStepMatrix` / `stepViewMatrix` / `fileStepViews` | ROTSTEP views, following MLCad: `REL`, `ADD`, `ABS`, `END`. A ROTSTEP ends its step. |
94
95
  | `parseTexMap` / `texmapUV` / `texmapTriangleUV` / `transformTexMap` / `resolveTexture` / `texWrapper` | `!TEXMAP` support (`PLANAR`, `CYLINDRICAL`, `SPHERICAL`). UV conventions follow LDView: v = 0 is the image's top row. |
95
- | `parseColor`, `isDirectColor`, `colorToken`, `mul`, `invert`, `IDENTITY`, `norm`, `smoothNormals`, `hash32` | Low-level helpers. |
96
+ | `parseColor`, `isDirectColor`, `namedColorCode`, `isNamedColor`, `colorToken`, `mul`, `invert`, `IDENTITY`, `norm`, `smoothNormals`, `hash32` | Low-level helpers. |
96
97
 
97
98
  `FlattenOptions`:
98
99
 
@@ -209,6 +210,9 @@ Reproduce: `cd bench && bun run setup && bun run perf` (regression gate) or `bun
209
210
  - `0 STEP`, nested submodel step sequencing, `0 ROTSTEP` (`REL`/`ADD`/`ABS`/`END`), and `0 BUFEXCHG`.
210
211
  - `!TEXMAP` `START`/`NEXT`/`FALLBACK`/`END`, with `PLANAR`, `CYLINDRICAL` and `SPHERICAL` projections (orientation matches LDView), `GLOSSMAP` (alpha = gloss amount, same UVs), and model-level `!TEXMAP` around part references.
211
212
  - Colours: LDConfig `!COLOUR`, edge colours, main/edge colour inheritance (16/24), and direct colours (`0x2RRGGBB`).
213
+ - `!COLOUR` lines in a model follow the spec's scope rule. A definition applies from its line to the end of its file and to the files that file references, and it never leaks back to the parent. It sets the instance colour and finish. Colours hard-coded inside parts (printed parts, MPD-embedded parts) always come from LDConfig, because parts are precompiled.
214
+ - Named colour codes: a `!COLOUR` CODE may be an identifier instead of a number (`0 !COLOUR My_Red CODE MyRed VALUE #FF0000 EDGE #333333`, then `1 myred ...`), which the spec allows outside strict LDraw compatibility. Identifiers are case-insensitive and follow the same scope rule. `parseColor` turns one into a stable negative code (`namedColorCode`, `isNamedColor`), and `colorToken` turns it back into the identifier. A named code with no definition in scope draws the fallback colour.
215
+ - Blended colour codes 256-511 that LDConfig doesn't define (used by old models, e.g. 382 in Datsville) render as the spec's average of two ldraw.exe colours instead of the fallback grey.
212
216
  - Case-insensitive, backslash-normalized file lookup; `~Moved to` alias parts.
213
217
 
214
218
  ## Limitations
package/dist/cli.js CHANGED
@@ -1,20 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ../../../../../../../tmp/ldr-build-JiiKTe/src/cli.ts
3
+ // ../../../../../../../tmp/ldr-build-tYthc8/src/cli.ts
4
4
  import fs4 from "node:fs";
5
5
  import zlib from "node:zlib";
6
6
 
7
- // ../../../../../../../tmp/ldr-build-JiiKTe/src/node/build-pack.ts
7
+ // ../../../../../../../tmp/ldr-build-tYthc8/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-JiiKTe/src/node/resolver.ts
11
+ // ../../../../../../../tmp/ldr-build-tYthc8/src/node/resolver.ts
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
14
 
15
- // ../../../../../../../tmp/ldr-build-JiiKTe/src/spec.ts
15
+ // ../../../../../../../tmp/ldr-build-tYthc8/src/spec.ts
16
16
  var norm = (s) => s.trim().toLowerCase().replace(/\\/g, "/").replace(/\/+/g, "/");
17
+ var COLOUR_ID_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
18
+ var namedCodes = new Map;
19
+ function namedColorCode(id) {
20
+ const s = id.toLowerCase();
21
+ let h = 2166136261;
22
+ for (let i = 0;i < s.length; i++) {
23
+ h ^= s.charCodeAt(i);
24
+ h = Math.imul(h, 16777619);
25
+ }
26
+ const c = -1 - (h >>> 0 & 1073741823);
27
+ if (!namedCodes.has(c))
28
+ namedCodes.set(c, s);
29
+ return c;
30
+ }
31
+ var isNamedColor = (c) => c < 0 && Number.isInteger(c);
17
32
  function parseColor(tok) {
33
+ const c0 = tok.charCodeAt(0) | 32;
34
+ if (c0 >= 97 && c0 <= 122 && COLOUR_ID_RE.test(tok))
35
+ return namedColorCode(tok);
18
36
  if (/^0x/i.test(tok))
19
37
  return parseInt(tok, 16);
20
38
  return +tok;
@@ -590,7 +608,7 @@ function looseSteps(f) {
590
608
  looseMemo.set(f, r);
591
609
  return r;
592
610
  }
593
- var colorToken = (c) => c > 16777215 ? "0x" + c.toString(16).toUpperCase() : String(c);
611
+ var colorToken = (c) => c > 16777215 ? "0x" + c.toString(16).toUpperCase() : c < 0 ? namedCodes.get(c) ?? String(c) : String(c);
594
612
  var lineOf = (c) => c.t === 0 ? "0 " + c.text : c.t === 1 ? `1 ${colorToken(c.color)} ${c.m.join(" ")} ${c.file}` : c.src ? c.src : `${c.t} ${colorToken(c.color)} ${c.p.join(" ")}`;
595
613
  var loosePartName = (file, step) => `${norm(file)}#${step}`;
596
614
  function looseFile(f, step) {
@@ -705,7 +723,7 @@ function fileStepViews(f) {
705
723
  return out;
706
724
  }
707
725
 
708
- // ../../../../../../../tmp/ldr-build-JiiKTe/src/model.ts
726
+ // ../../../../../../../tmp/ldr-build-tYthc8/src/model.ts
709
727
  var CALLOUT_END = 999999;
710
728
  var stepKey = (steps) => steps.map((x) => String(x).padStart(6, "0")).join(".") + "~";
711
729
  function flatten(doc, opts = {}) {
@@ -724,17 +742,18 @@ function flatten(doc, opts = {}) {
724
742
  }
725
743
  return fn;
726
744
  };
727
- const walk = (d, fname, m, color, path, stepPath, depth, texIn, fixed = null, subPath = [], subMats = []) => {
745
+ const walk = (d, fname, m, color, path, stepPath, depth, texIn, fixed = null, subPath = [], subMats = [], colour = undefined, scope0 = null) => {
728
746
  const f = d.files.get(fname);
729
747
  if (!f || depth > maxDepth)
730
748
  return;
749
+ let scope = scope0;
731
750
  let s = 0;
732
751
  const subId = depth > 0 ? ++subs : 0;
733
752
  const sp0 = co && subId ? [...subPath, subId] : subPath;
734
753
  const sm0 = co && subId ? [...subMats, m] : subMats;
735
754
  const groups = co && !fixed ? new Map : null;
736
755
  let ordStep = -1, ord = 0;
737
- const sub = (sd, file, mm, col, sp, lt) => {
756
+ const sub = (sd, file, mm, col, sp, lt, cd) => {
738
757
  if (ordStep !== s) {
739
758
  ordStep = s;
740
759
  ord = 0;
@@ -742,19 +761,21 @@ function flatten(doc, opts = {}) {
742
761
  const g = groups?.get(s + "|" + file);
743
762
  if (g) {
744
763
  g.n++;
745
- return walk(sd, sd === d ? file : sd.main, mm, col, path + "/" + file, [...sp, CALLOUT_END], depth + 1, lt, [...sp, CALLOUT_END], sp0, sm0);
764
+ return walk(sd, sd === d ? file : sd.main, mm, col, path + "/" + file, [...sp, CALLOUT_END], depth + 1, lt, [...sp, CALLOUT_END], sp0, sm0, cd, scope);
746
765
  }
747
766
  const spo = [...sp, ord++], from = out.length;
748
- walk(sd, sd === d ? file : sd.main, mm, col, path + "/" + file, spo, depth + 1, lt, fixed, sp0, sm0);
767
+ walk(sd, sd === d ? file : sd.main, mm, col, path + "/" + file, spo, depth + 1, lt, fixed, sp0, sm0, cd, scope);
749
768
  groups?.set(s + "|" + file, { n: 1, from, to: out.length });
750
769
  };
751
770
  const views = fileStepViews(f);
752
771
  const ls = loose ? looseSteps(f) : [];
753
772
  let li = 0;
754
- const emit = (part, col, mm, sp, k, embedded, tex) => {
773
+ const emit = (part, col, mm, sp, k, embedded, tex, cd) => {
755
774
  if (fixed)
756
775
  sp = fixed;
757
776
  const i = { part, color: col, m: mm, step: sp[0], steps: sp, localStep: k, path };
777
+ if (cd)
778
+ i.colour = cd;
758
779
  if (subId) {
759
780
  i.sub = subId;
760
781
  if (co) {
@@ -778,7 +799,7 @@ function flatten(doc, opts = {}) {
778
799
  const flushLoose = (upTo) => {
779
800
  while (li < ls.length && ls[li] <= upTo) {
780
801
  const k = ls[li++];
781
- emit(loosePartName(f.name, k), color, m, [...stepPath, k], k, false, null).source = lazy(f, k);
802
+ emit(loosePartName(f.name, k), color, m, [...stepPath, k], k, false, null, colour).source = lazy(f, k);
782
803
  }
783
804
  };
784
805
  for (const c0 of f.cmds) {
@@ -792,6 +813,12 @@ function flatten(doc, opts = {}) {
792
813
  continue;
793
814
  }
794
815
  if (c.t === 0) {
816
+ if (c.meta === "!COLOUR") {
817
+ const def = parseColourLine(c.text);
818
+ if (def && def.code !== 16 && def.code !== 24)
819
+ (scope = new Map(scope ?? [])).set(def.code, def);
820
+ continue;
821
+ }
795
822
  if (tx && c.geom?.t === 1)
796
823
  c = c.geom;
797
824
  else
@@ -801,24 +828,24 @@ function flatten(doc, opts = {}) {
801
828
  continue;
802
829
  if (tx && c.fallback)
803
830
  continue;
804
- const col = c.color === 16 ? color : c.color, mm = mul(m, c.m), sp = [...stepPath, s];
831
+ const col = c.color === 16 ? color : c.color, cd = c.color === 16 ? colour : scope?.get(c.color), mm = mul(m, c.m), sp = [...stepPath, s];
805
832
  const tex = tx ? c.tex ?? texIn : null;
806
833
  const local = tex ? invert(c.m) : null;
807
834
  const lt = tex && local ? transformTexMap(tex, local) : null;
808
835
  if (d.files.has(c.file)) {
809
836
  if (fileKind(d, c.file) === "model") {
810
- sub(d, c.file, mm, col, sp, lt);
837
+ sub(d, c.file, mm, col, sp, lt, cd);
811
838
  continue;
812
839
  }
813
- emit(c.file, col, mm, sp, s, true, lt);
840
+ emit(c.file, col, mm, sp, s, true, lt, cd);
814
841
  continue;
815
842
  }
816
843
  const ext = opts.resolveModel?.(c.file);
817
844
  if (ext) {
818
- sub(ext, c.file, mm, col, sp, lt);
845
+ sub(ext, c.file, mm, col, sp, lt, cd);
819
846
  continue;
820
847
  }
821
- emit(c.file, col, mm, sp, s, false, lt);
848
+ emit(c.file, col, mm, sp, s, false, lt, cd);
822
849
  }
823
850
  flushLoose(Infinity);
824
851
  if (groups) {
@@ -908,14 +935,14 @@ function parseColourLine(line) {
908
935
  if (tokens.length < 2)
909
936
  return null;
910
937
  const name = tokens[0];
911
- let code = -1, hex = "#FF00FF", edge = "#333333", alpha = 255, luminance = 0;
938
+ let code = NaN, hex = "#FF00FF", edge = "#333333", alpha = 255, luminance = 0;
912
939
  let finish = { type: "default" };
913
940
  let i = 1;
914
941
  while (i < tokens.length) {
915
942
  const t = tokens[i].toUpperCase();
916
943
  const n = tokens[i + 1];
917
944
  if (t === "CODE") {
918
- code = +n;
945
+ code = n !== undefined && COLOUR_ID_RE.test(n) ? namedColorCode(n) : n !== undefined && /^\d+$/.test(n) ? +n : NaN;
919
946
  i += 2;
920
947
  } else if (t === "VALUE") {
921
948
  const h = parseHexColor(n);
@@ -970,7 +997,7 @@ function parseColourLine(line) {
970
997
  } else
971
998
  i += 1;
972
999
  }
973
- if (code < 0 || !hex)
1000
+ if (Number.isNaN(code) || !hex)
974
1001
  return null;
975
1002
  return { code, name, hex, edge, alpha: Number.isFinite(alpha) ? alpha : 255, luminance: Number.isFinite(luminance) ? luminance : 0, finish };
976
1003
  }
@@ -1054,8 +1081,32 @@ function parseLDConfig(text) {
1054
1081
  }
1055
1082
  return out;
1056
1083
  }
1084
+ var LDRAW_EXE_16 = [
1085
+ [51, 51, 51],
1086
+ [0, 51, 178],
1087
+ [0, 127, 51],
1088
+ [0, 181, 166],
1089
+ [204, 0, 0],
1090
+ [255, 51, 153],
1091
+ [102, 51, 0],
1092
+ [153, 153, 153],
1093
+ [102, 102, 88],
1094
+ [0, 128, 255],
1095
+ [51, 255, 102],
1096
+ [171, 253, 249],
1097
+ [255, 0, 0],
1098
+ [255, 176, 204],
1099
+ [255, 229, 0],
1100
+ [255, 255, 255]
1101
+ ];
1102
+ function blendedColourHex(code) {
1103
+ if (!Number.isInteger(code) || code < 256 || code > 511)
1104
+ return null;
1105
+ const a = LDRAW_EXE_16[code - 256 >> 4], b = LDRAW_EXE_16[code - 256 & 15];
1106
+ return "#" + [0, 1, 2].map((k) => (a[k] + b[k] >> 1).toString(16).padStart(2, "0")).join("").toUpperCase();
1107
+ }
1057
1108
 
1058
- // ../../../../../../../tmp/ldr-build-JiiKTe/src/node/resolver.ts
1109
+ // ../../../../../../../tmp/ldr-build-tYthc8/src/node/resolver.ts
1059
1110
  var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
1060
1111
  var UNDER_STUD_RE = /^(stud3|stud3a|stud4.*|stud12|stud16.*|stud18a)\.dat$/i;
1061
1112
  var norm2 = (ref) => ref.trim().replace(/\\/g, "/").toLowerCase();
@@ -1325,11 +1376,11 @@ function collectDeps(lib, refs) {
1325
1376
  return out;
1326
1377
  }
1327
1378
 
1328
- // ../../../../../../../tmp/ldr-build-JiiKTe/src/node/compile.ts
1379
+ // ../../../../../../../tmp/ldr-build-tYthc8/src/node/compile.ts
1329
1380
  import fs2 from "node:fs";
1330
1381
  import path2 from "node:path";
1331
1382
 
1332
- // ../../../../../../../tmp/ldr-build-JiiKTe/src/node/lod.ts
1383
+ // ../../../../../../../tmp/ldr-build-tYthc8/src/node/lod.ts
1333
1384
  import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
1334
1385
  await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
1335
1386
  function reorderMesh(P, N, C, I) {
@@ -1521,7 +1572,7 @@ function lodChain(P, C, I) {
1521
1572
  return out;
1522
1573
  }
1523
1574
 
1524
- // ../../../../../../../tmp/ldr-build-JiiKTe/src/node/compile.ts
1575
+ // ../../../../../../../tmp/ldr-build-tYthc8/src/node/compile.ts
1525
1576
  function loadColors(root) {
1526
1577
  const map = new Map;
1527
1578
  for (const l of fs2.readFileSync(path2.join(root, "LDConfig.ldr"), "latin1").split(/\r?\n/)) {
@@ -1533,6 +1584,13 @@ function loadColors(root) {
1533
1584
  }
1534
1585
  return map;
1535
1586
  }
1587
+ var blendedRgb = (code) => {
1588
+ const h = blendedColourHex(+code);
1589
+ if (!h)
1590
+ return null;
1591
+ const v = parseInt(h.slice(1), 16);
1592
+ return [(v >> 16) / 255, (v >> 8 & 255) / 255, (v & 255) / 255];
1593
+ };
1536
1594
  var det3 = (m) => m[3] * (m[7] * m[11] - m[8] * m[10]) - m[4] * (m[6] * m[11] - m[8] * m[9]) + m[5] * (m[6] * m[10] - m[7] * m[9]);
1537
1595
 
1538
1596
  class Compiler {
@@ -1570,7 +1628,7 @@ class Compiler {
1570
1628
  if (this.cache.has(ck))
1571
1629
  return this.cache.get(ck);
1572
1630
  const out = { body: { pos: [], col: [] }, under: { pos: [], col: [] }, edges: [], condLines: 0, cond: this.opts.cond ? [] : undefined, condUnder: this.opts.cond ? [] : undefined, studs: [], bbox: [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity], fullBottom: false };
1573
- const colorOf = (code) => code === "16" ? [1, 1, 1, 0] : [...this.colors.get(code) || (code.startsWith("0x2") ? [parseInt(code.slice(3, 5), 16) / 255, parseInt(code.slice(5, 7), 16) / 255, parseInt(code.slice(7, 9), 16) / 255] : [1, 0, 1]), 1];
1631
+ const colorOf = (code) => code === "16" ? [1, 1, 1, 0] : [...this.colors.get(code) || (code.startsWith("0x2") ? [parseInt(code.slice(3, 5), 16) / 255, parseInt(code.slice(5, 7), 16) / 255, parseInt(code.slice(7, 9), 16) / 255] : blendedRgb(code) ?? [1, 0, 1]), 1];
1574
1632
  const walk = (fk, M, color, invert, target, depth) => {
1575
1633
  if (depth > 40)
1576
1634
  return;
@@ -2151,7 +2209,7 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
2151
2209
  return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
2152
2210
  }
2153
2211
 
2154
- // ../../../../../../../tmp/ldr-build-JiiKTe/src/node/build-pack.ts
2212
+ // ../../../../../../../tmp/ldr-build-tYthc8/src/node/build-pack.ts
2155
2213
  function buildPack(o) {
2156
2214
  const lib = new Library(o.ldrawRoot);
2157
2215
  const refs = new Map;
@@ -2219,7 +2277,7 @@ function buildPack(o) {
2219
2277
  return { data: encodePack(parts, kinds, comp.opts.weld, extra, { texture, lod: o.lod !== false, reorder: !!o.reorder }), parts: parts.size, studKinds: kinds.size, missing: [...missing], ...warnings.length ? { warnings } : {} };
2220
2278
  }
2221
2279
 
2222
- // ../../../../../../../tmp/ldr-build-JiiKTe/src/cli.ts
2280
+ // ../../../../../../../tmp/ldr-build-tYthc8/src/cli.ts
2223
2281
  var HELP = `ldr pack — build a parts pack for the ldr three.js renderer
2224
2282
 
2225
2283
  Usage:
package/dist/cull.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PartInstance } from './model.js';
1
+ import type { PartInstance, LDrawColor } from './model.js';
2
2
  export type Inst = {
3
3
  part: string;
4
4
  color: string;
@@ -63,6 +63,7 @@ export declare function sceneInstances(instances: PartInstance[], H: any, useTex
63
63
  }[];
64
64
  stepOf: number[];
65
65
  nSteps: number;
66
+ localColours: Map<string, LDrawColor>;
66
67
  };
67
68
  /** Culling for `createLdrObject(instances, pack, { textures })`, computable off the main thread and passed back as `precomputedCull`. */
68
69
  export declare function precomputeCull(instances: PartInstance[], H: any, useTex?: boolean): CullResult;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { parse, parseColor, isDirectColor, mul, I as IDENTITY, norm, smoothNormals, parseTexMap, texmapUV, texmapTriangleUV, transformTexMap, resolveTexture, fileKind, looseSteps, looseSource, looseFile, loosePartName, texWrapper, invert, hash32, colorToken, rotStepMatrix, stepViewMatrix, fileStepViews } from "./spec.js";
1
+ export { parse, parseColor, isDirectColor, namedColorCode, isNamedColor, mul, I as IDENTITY, norm, smoothNormals, parseTexMap, texmapUV, texmapTriangleUV, transformTexMap, resolveTexture, fileKind, looseSteps, looseSource, looseFile, loosePartName, texWrapper, invert, hash32, colorToken, rotStepMatrix, stepViewMatrix, fileStepViews } from "./spec.js";
2
2
  export type { M as Matrix12, Doc, LFile, Cmd, RotStep, BufExchg, TexMap, TexMapCmd, TexMapMethod, TexAnno, DataBlock, PartMesh, FileKind, StepView } from "./spec.js";
3
- export { flatten, parseModel, sequenceSteps, stepKey, parseLDConfig, parseColourLine, colourSurface, colourMaterialKey, CALLOUT_END } from "./model.js";
3
+ export { flatten, parseModel, sequenceSteps, stepKey, parseLDConfig, parseColourLine, colourSurface, colourMaterialKey, blendedColourHex, CALLOUT_END } from "./model.js";
4
4
  export { planStepViews, isolateMask, viewDir, instanceBoxes, MIN_VIS, REORIENT } from "./instructions.js";
5
5
  export type { StepCameraView, StepCameraOptions, Vec3 } from "./instructions.js";
6
6
  export type { PartInstance, FlattenOptions, LDrawColor, LDrawFinish, LDrawFinishType, LDrawMaterialGrain, ColourSurface } from "./model.js";