@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.
- package/README.md +480 -0
- package/dist/GCodeSVGRenderer-DaLc9_L-.cjs +1 -0
- package/dist/GCodeSVGRenderer-DnFLLJmH.js +1487 -0
- package/dist/gviewer.cjs +1 -0
- package/dist/gviewer.js +654 -0
- package/dist/react.cjs +1 -0
- package/dist/react.js +141 -0
- package/dist/types/geometry.d.ts +92 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/parser.d.ts +6 -0
- package/dist/types/react/GCodeSVGVisualizer.d.ts +20 -0
- package/dist/types/react/GCodeVisualizer.d.ts +10 -0
- package/dist/types/react/index.d.ts +4 -0
- package/dist/types/types.d.ts +71 -0
- package/dist/types/viewer/GCodeViewer.d.ts +89 -0
- package/dist/types/viewer/ViewCube.d.ts +16 -0
- package/dist/types/viewer/bbox/boundingBox.d.ts +4 -0
- package/dist/types/viewer/bit/bit.d.ts +13 -0
- package/dist/types/viewer/bit/drill-stl-data.d.ts +1 -0
- package/dist/types/viewer/camera/camera.d.ts +21 -0
- package/dist/types/viewer/grid/grid.d.ts +21 -0
- package/dist/types/viewer/index.d.ts +7 -0
- package/dist/types/viewer/render/textSprite.d.ts +12 -0
- package/dist/types/viewer/simulation/heightmap.d.ts +36 -0
- package/dist/types/viewer/simulation/materialSlab.d.ts +12 -0
- package/dist/types/viewer/svg/GCodeSVGRenderer.d.ts +53 -0
- package/dist/types/viewer/svg/types.d.ts +10 -0
- package/dist/types/viewer/themes.d.ts +3 -0
- package/dist/types/viewer/toolpath/streams.d.ts +51 -0
- package/dist/types/viewer/types.d.ts +171 -0
- package/dist/types/virtualizer.d.ts +26 -0
- package/dist/viewcube.css +54 -0
- package/dist/viewer.cjs +1 -0
- package/dist/viewer.js +109 -0
- 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
|