ldr 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,15 @@
1
+ import { type FlattenOptions } from "../model.js";
1
2
  import { type Doc, type PartMesh, type Instance } from "../spec.js";
2
3
  export declare class Library {
3
4
  root: string;
4
5
  unofficialRoot?: string | undefined;
5
6
  cache: Map<string, Doc | null>;
6
7
  meshCache: Map<string, PartMesh>;
8
+ /** Files registered from flattened documents (MPD files and synthetic parts), searched before the library (MPD spec: a document's
9
+ * files win over library files of the same name). The latest `flatten` wins; replacing or shadowing an entry clears the mesh cache. */
10
+ embedded: Map<string, Doc>;
11
+ /** Register `name` as a part whose source is file `file` of `doc` (or a parsed synthetic source). */
12
+ register(name: string, doc: Doc, file?: string): void;
7
13
  /** Optional second root for unofficial parts (e.g. Unofficial/ next to the official tree). */
8
14
  constructor(root: string, unofficialRoot?: string | undefined);
9
15
  /**
@@ -37,6 +43,8 @@ export declare class Library {
37
43
  * `textures/` prefix (parts/, p/, models/, official then unofficial), then without it. Case-insensitive. */
38
44
  texture(name: string, doc?: Doc): Uint8Array | null;
39
45
  private emit;
40
- /** Flatten a model into part instances. 24 handled at render (edge color). Alias parts resolve to canonical names. */
41
- flatten(doc: Doc): Instance[];
46
+ /** Flatten a model into part instances (see `flatten` in the browser entry for the rules; `models/` library files are walked
47
+ * as submodels). 24 handled at render (edge color). Alias parts resolve to canonical names. MPD files and synthetic parts
48
+ * (loose model geometry, model-level !TEXMAP wrappers) are registered so `mesh(inst.part)` / `mesh(inst.texPart)` find them. */
49
+ flatten(doc: Doc, opts?: FlattenOptions): Instance[];
42
50
  }
@@ -1,3 +1,5 @@
1
+ import { type Doc } from '../spec.js';
2
+ import { type FlattenOptions, type PartInstance } from '../model.js';
1
3
  export declare const TOP_STUD_RE: RegExp;
2
4
  export declare const UNDER_STUD_RE: RegExp;
3
5
  export declare const norm: (ref: any) => any;
@@ -17,6 +19,11 @@ export declare class Library {
17
19
  text: Map<string, string>;
18
20
  stats: Map<string, any>;
19
21
  constructor(root: string);
22
+ /** Document files registered by `flatten` (MPD files, synthetic parts): name -> key `mpd/<name>`. Searched first (MPD spec:
23
+ * a document's files win over library files). First registration wins; a different text for the same name goes to `conflicts`. */
24
+ virtual: Map<string, string>;
25
+ conflicts: Set<string>;
26
+ addVirtual(name: string, text: string): string;
20
27
  resolve(ref: any): string | null;
21
28
  read(key: string): string;
22
29
  isPart(key: any): any;
@@ -27,7 +34,27 @@ export declare function splitModel(text: any, fallbackName?: string): {
27
34
  main: string | null;
28
35
  data: Map<string, string>;
29
36
  };
30
- export declare function flatten(model: any, lib: any, onInstance: any): {
37
+ /** Parse the files of a `splitModel` result into a document (same parser as the browser path). */
38
+ export declare function modelDoc(model: {
39
+ files: Map<string, string>;
40
+ main: string | null;
41
+ }): Doc;
42
+ export type ResolvedInstance = {
43
+ part: string;
44
+ ref: string;
45
+ color: string;
46
+ m: number[];
47
+ texPart?: string;
48
+ texRef?: string;
49
+ inst: PartInstance;
50
+ };
51
+ /**
52
+ * Flatten a `splitModel` result to library-resolved instances (`part` = library key, `ref` = name as referenced). Same walk as the
53
+ * browser `flatten` (shared code): MPD-embedded parts and synthetic parts (a model file's own geometry; with `textures`, parts wrapped
54
+ * by a model-level !TEXMAP) are registered on `lib` as virtual files (`mpd/<name>`) so the Compiler compiles them; every file of the
55
+ * document is registered, so MPD files also win over library files referenced from parts (MPD spec).
56
+ */
57
+ export declare function flatten(model: any, lib: Library, onInstance: (i: ResolvedInstance) => void, opts?: FlattenOptions): {
31
58
  missing: Map<any, any>;
32
59
  };
33
60
  export declare function collectDeps(lib: any, refs: any): Map<any, any>;
package/dist/render.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as THREE from 'three';
2
- /** `cond`: draw type-5 conditional lines when the pack carries them (pack v2, built with `cond: true`); 'idx' edges only. Default true.
2
+ /** `cond`: draw type-5 conditional lines when the pack carries them (pack v2, built with `cond: true`); with 'idx' or legacy `true` edges. Default true.
3
3
  * `condStuds`: also draw them on stud batches (stud silhouettes). Default true. */
4
4
  export type LdrParams = {
5
5
  pack: string;
package/dist/scene.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as THREE from "three";
2
2
  import { type Pack } from "./render.js";
3
3
  import type { PartInstance, LDrawColor } from "./model.js";
4
+ import type { StepView } from "./spec.js";
4
5
  export type ColorSource = Map<number, string | LDrawColor> | Record<number | string, string | LDrawColor> | ((code: number) => THREE.ColorRepresentation | undefined);
5
6
  export interface LdrObjectOptions {
6
7
  /** Draw LDraw type-2 edge lines (indexed, sharing each part's instance matrices). Default true. */
@@ -54,6 +55,9 @@ export interface LdrObject {
54
55
  stats: LdrStats;
55
56
  /** Number of build steps (nested submodel steps are sequenced). */
56
57
  stepCount: number;
58
+ /** Per build step, the view rotation set by MLCad `0 ROTSTEP` (null = default view). Get the 3x3 (LDraw space, row-major) with
59
+ * `stepViewMatrix(view, yourDefaultView)`; in three.js terms rotate the model by it inside the object's -Y-up flip. */
60
+ stepViews: (StepView | null)[];
57
61
  /** Show instances up to and including `step` (null = all). Rebuilds batches; culling is recomputed for the partial model. */
58
62
  setStep(step: number | null): void;
59
63
  setEdges(on: boolean): void;
package/dist/spec.d.ts CHANGED
@@ -57,17 +57,23 @@ export type Cmd = ({
57
57
  } & TexAnno) | ({
58
58
  t: 2 | 5;
59
59
  color: number;
60
- p: number[];
60
+ p: number[]; /** source line, only with `parse(..., { geometryText: true })` (then `p` is empty) */
61
+ src?: string;
61
62
  } & TexAnno) | ({
62
63
  t: 3 | 4;
63
64
  color: number;
64
65
  p: number[];
66
+ src?: string;
65
67
  } & TexAnno);
66
68
  export interface LFile {
67
69
  name: string;
68
70
  cmds: Cmd[];
69
71
  movedTo?: string; /** Every TEXMAP START/NEXT in this file, in order. */
70
72
  texmaps?: TexMap[];
73
+ /** File type from the header (`0 !LDRAW_ORG <type>`, or legacy `0 LDRAW_ORG` / `0 Unofficial <type>` / `0 Official LCAD <type>`), lowercased
74
+ * with any `unofficial_` prefix removed, e.g. "part", "subpart", "primitive", "shortcut", "model". Only header lines before the file's
75
+ * first type 1-5 line count, and only known types are recorded. */
76
+ org?: string;
71
77
  }
72
78
  /** An MPD `0 !DATA` block (base64-decoded), e.g. an embedded PNG texture. */
73
79
  export interface DataBlock {
@@ -85,7 +91,12 @@ export declare const norm: (s: string) => string;
85
91
  export declare function parseColor(tok: string): number;
86
92
  /** Parse the tail of `0 !TEXMAP START|NEXT <method> <params> <png> [GLOSSMAP <png>]`. Returns null when malformed. */
87
93
  export declare function parseTexMap(tail: string, next?: boolean): TexMap | null;
88
- export declare function parse(src: string, name?: string): Doc;
94
+ /** `geometryText`: keep type 2-5 lines as their source text (`src`) with empty `p` instead of parsing their numbers (for tools that
95
+ * only walk references and pass geometry through as text, e.g. the node pack builder; ~3x faster on geometry-heavy files). */
96
+ export interface ParseOptions {
97
+ geometryText?: boolean;
98
+ }
99
+ export declare function parse(src: string, name?: string, popts?: ParseOptions): Doc;
89
100
  /** Find an embedded texture in a parsed MPD: tries `name`, then without / with a `textures/` prefix (case-insensitive). */
90
101
  export declare function resolveTexture(doc: Doc, name: string): DataBlock | null;
91
102
  /**
@@ -132,6 +143,7 @@ export interface PartMesh {
132
143
  texture?: Int32Array;
133
144
  textures?: string[];
134
145
  }
146
+ /** A placed part from `Library.flatten` (same fields as `PartInstance`, plus library resolution). */
135
147
  export interface Instance {
136
148
  part: string;
137
149
  color: number;
@@ -142,8 +154,77 @@ export interface Instance {
142
154
  path: string;
143
155
  missing: boolean;
144
156
  aliasOf?: string;
157
+ embedded?: boolean;
158
+ source?: (() => string) & {
159
+ file?: LFile;
160
+ step?: number;
161
+ };
162
+ texPart?: string;
163
+ texSource?: string;
164
+ view?: StepView;
145
165
  }
146
166
  /** Smooth normals that respect LDraw semantics: faces sharing an edge are smoothed together unless the
147
167
  * edge is a type-2 line (hard). Conditional (type-5) lines force smooth; unmarked edges smooth only below creaseDeg.
148
168
  * `cond` may be stride-6 (legacy endpoints only) or stride-12 (endpoints + 2 control points). */
149
169
  export declare function smoothNormals(pos: number[], edges: number[], cond: number[], creaseDeg?: number): Float32Array;
170
+ export type FileKind = "part" | "model";
171
+ /**
172
+ * Is a file of an MPD document a part (render its geometry as one unit) or a model (walk its type-1 lines into instances)?
173
+ * The MPD spec (ldraw.org article 47) makes no distinction, so this follows the file headers, then the content:
174
+ * 1. A header type decides (`0 !LDRAW_ORG <type>`, legacy `0 LDRAW_ORG`, `0 Unofficial <type>`, `0 Official LCAD <type>`;
175
+ * an `Unofficial_` prefix is ignored): Part, Subpart, Primitive, 8_Primitive, 48_Primitive, Shortcut, Helper -> part;
176
+ * Model, Submodel, Configuration -> model.
177
+ * 2. Without a header, the main (first) file is a model, and any other file is a part iff it has its own geometry
178
+ * (a type 2-5 line, or a `0 !:` TEXMAP geometry line) or references a subpart / low-res primitive (`s/`, `8/`, `48/`).
179
+ * Everything else (only type-1 references) is a model, as before.
180
+ * Files not in the document are library files (parts). A model's own type 2-5 lines are rendered too, as synthetic
181
+ * parts (see `looseSource`).
182
+ */
183
+ export declare function fileKind(doc: Doc, name: string): FileKind;
184
+ /** Local steps (as counted by `flatten`) in which a file has its own type 2-5 lines or `0 !:` geometry, ascending. */
185
+ export declare function looseSteps(f: LFile): number[];
186
+ /** LDraw colour token for a colour code: decimal, or `0x2RRGGBB` for direct colours. */
187
+ export declare const colorToken: (c: number) => string;
188
+ /** Name of the synthetic part holding a model file's own geometry in local step `step`: `<file>#<step>`. */
189
+ export declare const loosePartName: (file: string, step: number) => string;
190
+ /**
191
+ * The synthetic part for a model file's own type 2-5 lines in local step `step`, as a file: every `0 BFC` line of the file
192
+ * (winding / certification state, in order), the step's `!TEXMAP` and `0 !:` lines, and the step's type 2-5 lines. Type-1 lines
193
+ * are dropped (they are instances), together with a `!TEXMAP NEXT` that applied to one. Commands are shared with `f`.
194
+ */
195
+ export declare function looseFile(f: LFile, step: number): LFile;
196
+ /** LDraw source text of `looseFile(f, step)`. */
197
+ export declare function looseSource(f: LFile, step: number): string;
198
+ /** FNV-1a 32-bit, hex. */
199
+ export declare function hash32(s: string): string;
200
+ /** Inverse of an affine LDraw matrix, or null when singular. */
201
+ export declare function invert(m: M): M | null;
202
+ /**
203
+ * Synthetic part for a part reference wrapped by a model-level `!TEXMAP` (the map is defined in the model, not in the part file).
204
+ * `map` must already be in the part's own coordinates. The wrapper is a certified file that applies the map to the part, so
205
+ * TEXMAP-aware compilers texture it exactly as if the START were written in the part, and plain compilers produce the part itself.
206
+ * Name: `<part>#tex-<hash of the source>` (identical maps on the same part share one synthetic part).
207
+ */
208
+ export declare function texWrapper(part: string, map: TexMap): {
209
+ name: string;
210
+ source: string;
211
+ };
212
+ /** View rotation for a build step from MLCad `0 ROTSTEP` (MLCad LDraw command extensions 2.0, §6.1).
213
+ * `abs` false: view = <default view matrix> x `m`; true: view = `m`. `m` is 3x3 row-major. `rotstep` is the command that set it. */
214
+ export interface StepView {
215
+ abs: boolean;
216
+ m: number[];
217
+ rotstep: RotStep;
218
+ }
219
+ /** ROTSTEP rotation matrix for angles in degrees, MLCad 2.0 §6.1.5 (= Rx * Ry * Rz), 3x3 row-major [a b c d e f g h i]. */
220
+ export declare function rotStepMatrix(x: number, y: number, z: number): number[];
221
+ /** The view matrix for a step (3x3 row-major): REL/ADD-from-default = `defaultView` x m, ABS = m, no ROTSTEP / END = `defaultView`. */
222
+ export declare function stepViewMatrix(view: StepView | null | undefined, defaultView?: number[]): number[];
223
+ /**
224
+ * View per local step of a file (index = step number as counted by `flatten`; null = default view). MLCad semantics: STEP and
225
+ * ROTSTEP both end a step; a ROTSTEP sets the view of the step it ends (MLCad 2.0 §6.1: "drawing stops after executing the
226
+ * command"; put a plain STEP right before it to show the rotation on its own) and of every later step until the next ROTSTEP.
227
+ * REL: default x R; ADD: current x R (from the default view if none); ABS: R; END: back to the default view. Each file starts
228
+ * at the default view (submodel steps are viewed on their own, as LPub3D does).
229
+ */
230
+ export declare function fileStepViews(f: LFile): (StepView | null)[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldr",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "High-performance LEGO (LDraw) rendering for three.js: instanced parts packs, hidden stud/tube culling, low-detail studs, indexed edges, build steps.",
5
5
  "license": "MIT",
6
6
  "author": "Evan Bacon",