@sienci/gviewer 0.1.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.
Files changed (35) hide show
  1. package/README.md +480 -0
  2. package/dist/GCodeSVGRenderer-DaLc9_L-.cjs +1 -0
  3. package/dist/GCodeSVGRenderer-DnFLLJmH.js +1487 -0
  4. package/dist/gviewer.cjs +1 -0
  5. package/dist/gviewer.js +654 -0
  6. package/dist/react.cjs +1 -0
  7. package/dist/react.js +141 -0
  8. package/dist/types/geometry.d.ts +92 -0
  9. package/dist/types/index.d.ts +4 -0
  10. package/dist/types/parser.d.ts +6 -0
  11. package/dist/types/react/GCodeSVGVisualizer.d.ts +20 -0
  12. package/dist/types/react/GCodeVisualizer.d.ts +10 -0
  13. package/dist/types/react/index.d.ts +4 -0
  14. package/dist/types/types.d.ts +71 -0
  15. package/dist/types/viewer/GCodeViewer.d.ts +89 -0
  16. package/dist/types/viewer/ViewCube.d.ts +16 -0
  17. package/dist/types/viewer/bbox/boundingBox.d.ts +4 -0
  18. package/dist/types/viewer/bit/bit.d.ts +13 -0
  19. package/dist/types/viewer/bit/drill-stl-data.d.ts +1 -0
  20. package/dist/types/viewer/camera/camera.d.ts +21 -0
  21. package/dist/types/viewer/grid/grid.d.ts +21 -0
  22. package/dist/types/viewer/index.d.ts +7 -0
  23. package/dist/types/viewer/render/textSprite.d.ts +12 -0
  24. package/dist/types/viewer/simulation/heightmap.d.ts +36 -0
  25. package/dist/types/viewer/simulation/materialSlab.d.ts +12 -0
  26. package/dist/types/viewer/svg/GCodeSVGRenderer.d.ts +53 -0
  27. package/dist/types/viewer/svg/types.d.ts +10 -0
  28. package/dist/types/viewer/themes.d.ts +3 -0
  29. package/dist/types/viewer/toolpath/streams.d.ts +51 -0
  30. package/dist/types/viewer/types.d.ts +171 -0
  31. package/dist/types/virtualizer.d.ts +26 -0
  32. package/dist/viewcube.css +54 -0
  33. package/dist/viewer.cjs +1 -0
  34. package/dist/viewer.js +109 -0
  35. package/package.json +81 -0
package/README.md ADDED
@@ -0,0 +1,480 @@
1
+ # gviewer
2
+
3
+ TypeScript library for parsing, virtualizing, and visualizing GCode toolpaths. Built on Three.js with an optional React wrapper.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @sienci/gviewer
9
+ ```
10
+
11
+ Peer dependencies — install whichever you need:
12
+
13
+ ```bash
14
+ npm install three # required for the viewer
15
+ npm install react react-dom # required for the React component
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Import paths
21
+
22
+ | Path | Contents |
23
+ |---|---|
24
+ | `@sienci/gviewer` | Parser, virtualizer, geometry builders, shared types |
25
+ | `@sienci/gviewer/viewer` | `GCodeViewer` class, themes, viewer types |
26
+ | `@sienci/gviewer/react` | `GCodeVisualizer` React component |
27
+ | `@sienci/gviewer/viewer/viewcube.css` | Stylesheet for the ViewCube overlay |
28
+
29
+ ---
30
+
31
+ ## Quick start
32
+
33
+ ### React
34
+
35
+ ```tsx
36
+ import { useRef } from "react";
37
+ import { GCodeVisualizer } from "@sienci/gviewer/react";
38
+ import type { GCodeViewerHandle } from "@sienci/gviewer/viewer";
39
+ import "@sienci/gviewer/viewer/viewcube.css";
40
+
41
+ export function App() {
42
+ const ref = useRef<GCodeViewerHandle>(null);
43
+
44
+ return (
45
+ <>
46
+ <GCodeVisualizer
47
+ id="main"
48
+ ref={ref}
49
+ style={{ width: "100%", height: "600px" }}
50
+ />
51
+ <button onClick={() => ref.current?.loadFromUrl("/part.gcode")}>
52
+ Load
53
+ </button>
54
+ </>
55
+ );
56
+ }
57
+ ```
58
+
59
+ ### Vanilla JS
60
+
61
+ ```ts
62
+ import { GCodeViewer } from "@sienci/gviewer/viewer";
63
+ import "@sienci/gviewer/viewer/viewcube.css";
64
+
65
+ const viewer = new GCodeViewer({
66
+ id: "main",
67
+ container: document.getElementById("viewer")!,
68
+ });
69
+
70
+ await viewer.loadFromUrl("/part.gcode");
71
+ viewer.focusToModel();
72
+ ```
73
+
74
+ ---
75
+
76
+ ## API reference
77
+
78
+ ### `@sienci/gviewer` — core
79
+
80
+ #### `GCodeParser`
81
+
82
+ Stateless line-by-line parser. No state is kept between calls.
83
+
84
+ ```ts
85
+ import { GCodeParser } from "@sienci/gviewer";
86
+
87
+ const parser = new GCodeParser();
88
+ const result = parser.parseLine("G1 X10 Y5 F1200 ; move");
89
+ // result.words — all letter-value pairs
90
+ // result.gcodes — G and M words only
91
+ // result.params — non-G/M words (X, Y, F, …)
92
+ // result.comments — extracted semicolon and paren comments
93
+ ```
94
+
95
+ **`parseLine(line: string): ParsedLine`**
96
+
97
+ Returns a `ParsedLine`:
98
+
99
+ ```ts
100
+ type ParsedLine = {
101
+ raw: string;
102
+ words: GCodeWord[]; // all tokens
103
+ gcodes: GCodeWord[]; // G/M codes
104
+ params: GCodeWord[]; // axis and parameter words
105
+ comments: Comment[]; // extracted comments with positions
106
+ };
107
+
108
+ type GCodeWord = {
109
+ letter: string; // uppercase, e.g. "G", "X"
110
+ value: number;
111
+ raw: string; // original matched text
112
+ start: number; // char offset in stripped line
113
+ end: number;
114
+ };
115
+
116
+ type Comment = {
117
+ type: "paren" | "semicolon";
118
+ text: string;
119
+ start: number; // char offset in original line
120
+ end: number;
121
+ };
122
+ ```
123
+
124
+ ---
125
+
126
+ #### `GCodeVirtualizer`
127
+
128
+ Stateful interpreter. Tracks modal state and machine position across calls to `processLine()`.
129
+
130
+ ```ts
131
+ import { GCodeVirtualizer } from "@sienci/gviewer";
132
+
133
+ const virt = new GCodeVirtualizer({
134
+ onLinearMove({ modals, start, end, transformedStart, transformedEnd }) {
135
+ // called for every G0/G1 segment (subdivided at ≤5° A-axis steps)
136
+ },
137
+ onArcMove({ modals, start, end, center, max, plane, motion, ... }) {
138
+ // called for every G2/G3 move
139
+ },
140
+ });
141
+
142
+ for (const line of gcodeLines) {
143
+ virt.processLine(line);
144
+ }
145
+
146
+ virt.getModals(); // current ModalState
147
+ virt.getPosition(); // current Position { X, Y, Z, A, B, C }
148
+ virt.getUniqueFeedRates(); // number[]
149
+ virt.getUniqueSpindleSpeeds(); // number[]
150
+ virt.getUniqueTools(); // number[]
151
+ virt.reset(); // restore defaults
152
+ ```
153
+
154
+ **Modal state defaults:** `G0`, `G90` (absolute), `G17` (XY plane), `G21` (mm).
155
+
156
+ **Unit conversion:** When `G20` is active, X/Y/Z values are automatically multiplied by 25.4 before being stored and passed to callbacks. A/B/C axes are not scaled.
157
+
158
+ **Supported G-codes:**
159
+
160
+ | Code | Effect |
161
+ |---|---|
162
+ | G0, G1 | Set motion mode (rapid / feed) |
163
+ | G2, G3 | Arc CW / CCW |
164
+ | G17, G18, G19 | Plane selection (XY / ZX / YZ) |
165
+ | G20, G21 | Units (inches / mm) |
166
+ | G90, G91 | Distance mode (absolute / incremental) |
167
+ | G93, G94 | Feed mode |
168
+ | G54–G59 | Coordinate system selection |
169
+ | M3, M4, M5 | Spindle on/off |
170
+ | M7, M8, M9 | Coolant |
171
+ | T | Tool number |
172
+ | F | Feed rate |
173
+ | S | Spindle speed |
174
+
175
+ ---
176
+
177
+ #### Geometry builders
178
+
179
+ Build Three.js-ready `Float32Array` position buffers from arrays of GCode lines. All functions are browser-safe (no Node.js APIs).
180
+
181
+ ##### `buildVerticesFromLines`
182
+
183
+ ```ts
184
+ import { buildVerticesFromLines } from "@sienci/gviewer";
185
+
186
+ const positions: Float32Array = buildVerticesFromLines(lines, {
187
+ arcSegments: 30, // tessellation quality for arcs
188
+ });
189
+ // Flat [x0,y0,z0, x1,y1,z1, ...] line-segment pairs
190
+ ```
191
+
192
+ ##### `buildMovementVerticesFromLines`
193
+
194
+ Separates rapid (G0) and cutting (G1/G2/G3) moves.
195
+
196
+ ```ts
197
+ import { buildMovementVerticesFromLines } from "@sienci/gviewer";
198
+
199
+ const { rapid, cutting } = buildMovementVerticesFromLines(lines);
200
+ ```
201
+
202
+ ##### `buildMovementGeometryFromLinesBatched`
203
+
204
+ Async, progress-reporting version that also tracks per-line vertex ranges.
205
+
206
+ ```ts
207
+ import { buildMovementGeometryFromLinesBatched } from "@sienci/gviewer";
208
+
209
+ const result = await buildMovementGeometryFromLinesBatched(lines, {
210
+ arcSegments: 30,
211
+ batch: {
212
+ onProgress(processed, total) { /* update UI */ },
213
+ yieldEveryLines: 50000, // yield to event loop periodically
214
+ shouldAbort: () => cancelled,
215
+ },
216
+ });
217
+
218
+ // result.positions — Float32Array of all vertices
219
+ // result.prefixEndVertex — Int32Array; result.prefixEndVertex[i] is the
220
+ // cumulative vertex count after line i
221
+ // result.lineStartVertex — Int32Array; first vertex for line i (-1 if none)
222
+ // result.lineEndVertex — Int32Array; last vertex+1 for line i
223
+ // result.lineKind — Uint8Array; 0=none, 1=rapid, 2=cut, 3=mixed
224
+ ```
225
+
226
+ ##### `buildToolpathGeometryFromLinesBatched`
227
+
228
+ Unified builder for both standard and laser modes. Returns separate rapid and per-power-bucket cut streams, each with per-line prefix arrays for progress visualization.
229
+
230
+ ```ts
231
+ import { buildToolpathGeometryFromLinesBatched } from "@sienci/gviewer";
232
+
233
+ const result = await buildToolpathGeometryFromLinesBatched(lines, {
234
+ laserMode: false,
235
+ bucketCount: 16, // power buckets for laser mode
236
+ arcSegments: 30,
237
+ });
238
+
239
+ // result.rapid — { positions, prefixEndVertex }
240
+ // result.cuts — array of { positions, prefixEndVertex }
241
+ // result.cutBucketCount — number of cut streams (1 in non-laser mode)
242
+ // result.maxPower — max spindle speed seen (laser mode)
243
+ ```
244
+
245
+ ##### `buildLaserGeometryFromLinesBatched`
246
+
247
+ Laser-specific variant. Returns rapid positions plus opacity-bucketed cut streams.
248
+
249
+ ```ts
250
+ import { buildLaserGeometryFromLinesBatched } from "@sienci/gviewer";
251
+
252
+ const result = await buildLaserGeometryFromLinesBatched(lines, {
253
+ bucketCount: 16,
254
+ baseOpacity: 0.9,
255
+ });
256
+
257
+ // result.rapidPositions — Float32Array
258
+ // result.rapidPrefixEndVertex — Int32Array
259
+ // result.buckets[i].positions — Float32Array
260
+ // result.buckets[i].opacity — number (0–1, proportional to power)
261
+ // result.buckets[i].prefixEndVertex
262
+ ```
263
+
264
+ ---
265
+
266
+ ### `@sienci/gviewer/viewer` — Three.js viewer
267
+
268
+ #### `GCodeViewer`
269
+
270
+ Full 3D viewer with orbit controls, grid, bounding box, bit marker, and ViewCube.
271
+
272
+ ```ts
273
+ import { GCodeViewer } from "@sienci/gviewer/viewer";
274
+ import "@sienci/gviewer/viewer/viewcube.css";
275
+
276
+ const viewer = new GCodeViewer({
277
+ id: "my-viewer",
278
+ container: document.getElementById("viewer")!,
279
+ options: { /* Partial<GCodeViewerOptions> */ },
280
+ callbacks: {
281
+ onProgress(event) {
282
+ // event.state: "hidden" | "indeterminate" | "determinate"
283
+ },
284
+ onBoundsChanged(event) {
285
+ // event.bounds: { min, max } | null
286
+ },
287
+ },
288
+ });
289
+ ```
290
+
291
+ ##### Loading GCode
292
+
293
+ ```ts
294
+ await viewer.loadFromUrl("/path/to/file.gcode");
295
+ await viewer.loadFromFile(fileInputElement.files[0]);
296
+ await viewer.loadFromText("G21\nG0 X10 Y10\n...");
297
+ await viewer.loadFromLines(["G21", "G0 X10 Y10"]);
298
+ viewer.unload();
299
+ ```
300
+
301
+ ##### Camera
302
+
303
+ ```ts
304
+ viewer.focusToModel(); // animate camera to fit the loaded geometry
305
+ viewer.resetCamera(); // return to initial position
306
+ viewer.snapCameraToView("front", { durationMs: 300 });
307
+ // views: "front" | "back" | "left" | "right" | "top" | "bottom"
308
+ // | "front-top-left" | "front-top-right" | ... (14 presets)
309
+ ```
310
+
311
+ ##### Progress / simulation
312
+
313
+ ```ts
314
+ // Show geometry only from lineIndex onward
315
+ viewer.hideUntilLine(lineIndex, "grey"); // grey out processed lines
316
+ viewer.hideUntilLine(lineIndex, "hide"); // hide processed lines
317
+ viewer.showAll();
318
+ viewer.resetColors();
319
+ ```
320
+
321
+ ##### Bit marker
322
+
323
+ ```ts
324
+ viewer.setBitPosition({ x: 10, y: 5, z: 0 });
325
+ viewer.setBitPosition({ x: 10, y: 5, z: 0 }, { immediate: true });
326
+ viewer.setBitVisible(false);
327
+ ```
328
+
329
+ The bit type is controlled via `options.bit.type`. Four types are available:
330
+
331
+ | Type | Description |
332
+ |---|---|
333
+ | `"drill"` | Real drill-bit mesh (STL) with metallic shading. **Default.** |
334
+ | `"laser"` | Tapered beam with additive purple glow. Set automatically when `mode.laser` is enabled. |
335
+ | `"circle"` | Simple sphere. |
336
+ | `"triangle"` | Cone. |
337
+
338
+ **Laser mode auto-switch:** when `mode.laser` is set to `true`, the bit type automatically switches to `"laser"`. When `mode.laser` is set back to `false`, the bit reverts to whatever type was active before laser mode was enabled.
339
+
340
+ ```ts
341
+ viewer.setOptions({ mode: { laser: true } });
342
+ // bit type is now automatically "laser"
343
+
344
+ viewer.setOptions({ mode: { laser: false } });
345
+ // bit type is restored to its previous value (e.g. "drill")
346
+ ```
347
+
348
+ ##### Options
349
+
350
+ ```ts
351
+ viewer.setOptions({
352
+ units: "mm", // "mm" | "in"
353
+ mode: { laser: false }, // setting true auto-switches bit type to "laser"
354
+ render: {
355
+ theme: gCodeViewerThemePresets["tokyo-night"],
356
+ },
357
+ grid: { size: 1000, axisDepth: 200, labels: true },
358
+ boundingBox: { visible: true, labels: true },
359
+ camera: { fov: 45 },
360
+ });
361
+
362
+ viewer.getOptions(); // returns current options (readonly)
363
+ viewer.getBounds(); // { min, max } | null
364
+ viewer.resize(); // call after container resizes (automatic via ResizeObserver)
365
+ viewer.dispose(); // clean up Three.js resources and DOM elements
366
+ ```
367
+
368
+ ##### Themes
369
+
370
+ ```ts
371
+ import { gCodeViewerThemePresets } from "@sienci/gviewer/viewer";
372
+
373
+ // Available presets:
374
+ // "dark" | "light" | "flexoki-dark" | "tokyo-night"
375
+ // "gruvbox-light" | "ayu-dark" | "ayu-light"
376
+
377
+ viewer.setOptions({ render: { theme: gCodeViewerThemePresets["ayu-dark"] } });
378
+ ```
379
+
380
+ ##### `GCodeViewerOptions` — full reference
381
+
382
+ ```ts
383
+ type GCodeViewerOptions = {
384
+ units: "mm" | "in";
385
+ mode: { laser: boolean };
386
+ bit: {
387
+ enabled: boolean;
388
+ type: "drill" | "laser" | "circle" | "triangle"; // default: "drill"
389
+ size: number; // world units (default: 4.05)
390
+ opacity: number; // 0–1
391
+ tweenMs: number; // animation duration
392
+ colorSource: "cutting" | "rapid" | "custom";
393
+ color: string;
394
+ };
395
+ progress: { mode: "hide" | "grey" };
396
+ grid: { size: number; axisDepth: number; labels: boolean }; // default size: 1000
397
+ boundingBox: { visible: boolean; labels: boolean };
398
+ geometry: {
399
+ arcSegments: number;
400
+ batching: { progressEveryLines: number; yieldEveryLines: number };
401
+ };
402
+ render: { antialias: boolean; theme: GCodeViewerTheme };
403
+ camera: {
404
+ fov: number;
405
+ focusDurationMs: number;
406
+ orbit: { enableDamping: boolean };
407
+ initialPosition: { x: number; y: number; z: number };
408
+ };
409
+ };
410
+ ```
411
+
412
+ ---
413
+
414
+ ### `@sienci/gviewer/react` — React component
415
+
416
+ ```tsx
417
+ import { GCodeVisualizer } from "@sienci/gviewer/react";
418
+ import type { GCodeViewerHandle, GCodeViewerOptions } from "@sienci/gviewer/viewer";
419
+ import "@sienci/gviewer/viewer/viewcube.css";
420
+ ```
421
+
422
+ #### Props
423
+
424
+ ```ts
425
+ type GCodeVisualizerProps = {
426
+ id: string;
427
+ options?: Partial<GCodeViewerOptions>;
428
+ callbacks?: GCodeViewerCallbacks;
429
+ className?: string;
430
+ style?: React.CSSProperties;
431
+ };
432
+ ```
433
+
434
+ The component forwards a `GCodeViewerHandle` ref that exposes the full `GCodeViewer` imperative API:
435
+
436
+ ```tsx
437
+ const ref = useRef<GCodeViewerHandle>(null);
438
+
439
+ <GCodeVisualizer id="viewer" ref={ref} style={{ height: 500 }} />
440
+
441
+ // Later:
442
+ ref.current?.loadFromText(gcode);
443
+ ref.current?.focusToModel();
444
+ ref.current?.hideUntilLine(currentLine, "grey");
445
+ ref.current?.snapCameraToView("top");
446
+ ref.current?.setBitPosition({ x, y, z });
447
+ ```
448
+
449
+ `options` and `callbacks` props are synced to the viewer whenever they change (via `useEffect`).
450
+
451
+ ---
452
+
453
+ ## Development
454
+
455
+ ```bash
456
+ npm install
457
+ npm run build # compile + generate types
458
+ npm test # run all test suites
459
+ npm run test:watch # watch mode
460
+ npm run dev # rebuild on file changes
461
+ npm run demo # start the demo dev server
462
+ npm run build:demo # build the demo for deployment
463
+ ```
464
+
465
+ The demo is automatically built and published to GitHub Pages on every push to `master`.
466
+
467
+ ### Output
468
+
469
+ ```
470
+ dist/
471
+ gviewer.js / gviewer.cjs — core (parser, virtualizer, geometry)
472
+ viewer.js / viewer.cjs — Three.js viewer
473
+ react.js / react.cjs — React component
474
+ viewcube.css — ViewCube styles
475
+ types/ — TypeScript declarations
476
+ ```
477
+
478
+ ## License
479
+
480
+ MIT