Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 55 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
],
"main": "src/index.tsx",
"dependencies": {
"gcode-preview": "2.18",
"gcode-preview": "3.0.0-alpha.6",
"react": "18.0.0",
"react-dom": "18.0.0",
"react-scripts": "5.0.1",
Expand All @@ -18,7 +18,7 @@
"devDependencies": {
"@types/react": "17.0.20",
"@types/react-dom": "^18.0.6",
"@types/three": "^0.144.0",
"@types/three": "^0.169.0",
"@typescript-eslint/eslint-plugin": "^5.39.0",
"@typescript-eslint/parser": "^5.39.0",
"eslint-config-prettier": "^8.5.0",
Expand Down
89 changes: 34 additions & 55 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,71 +4,50 @@ import './styles.css';

type GCodePreviewHandle = React.ElementRef<typeof GCodePreview>;

const chunkSize = 250;
// gcode-preview 3.x reads the stream itself and renders progressively while it
// parses, so no manual chunking is needed. The stream has to be decoded to text
// first: the parser splits chunks on '\n' and would silently render nothing if
// it were handed raw bytes.
const loadGcode = async (
target: React.RefObject<GCodePreviewHandle>,
url: string,
signal: AbortSignal
) => {
const response = await fetch(url, { signal });
if (!response.ok || !response.body) {
throw new Error(
`status code: ${response.status}, status text: ${response.statusText}`
);
}
// the load may have been superseded while the file was in flight
if (signal.aborted) return;
await target.current?.load(
response.body.pipeThrough(new TextDecoderStream())
);
};

function App(): JSX.Element {
const gcodePreviewRef1 = useRef<GCodePreviewHandle | null>(null);
const gcodePreviewRef2 = useRef<GCodePreviewHandle | null>(null);
// const [layersLoaded, setLayerLoaded] = useState<number>();

const fetchGcode = async (url: string) => {
const response = await fetch(url);
if (response.status >= 200 && response.status <= 299) {
const file = await response.text();
return file.split('\n');
} else {
const errorMessage = `status code: ${response.status}, status text: ${response.statusText}`;
throw new Error(errorMessage);
}
};

const loadPreviewChunked = (
target: GCodePreviewHandle,
lines: string[],
delay: number
) => {
let c = 0;

// replace substr with substring because it is not part of the main ECMAScript specification
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr
const id =
'__animationTimer__' + Math.random().toString(36).substring(2, 10);
const loadProgressive = () => {
const start = c * chunkSize;
const end = (c + 1) * chunkSize;
const chunk = lines.slice(start, end);

target.processGCode(chunk);
// setLayerLoaded(target.getLayerCount());
c++;
if (c * chunkSize < lines.length) {
window[id] = setTimeout(loadProgressive, delay);
}
};

// cancel loading process if one is still in progress
// mostly when hot reloading
window.clearTimeout(window[id]);
loadProgressive();
};

useEffect(() => {
async function init() {
const lines1 = await fetchGcode('/benchy.gcode');
loadPreviewChunked(
gcodePreviewRef1.current as GCodePreviewHandle,
lines1,
50
);
const controller = new AbortController();

const lines2 = await fetchGcode('/duplo_tracks.gcode');
loadPreviewChunked(
gcodePreviewRef2.current as GCodePreviewHandle,
lines2,
50
async function init() {
await loadGcode(gcodePreviewRef1, '/benchy.gcode', controller.signal);
await loadGcode(
gcodePreviewRef2,
'/duplo_tracks.gcode',
controller.signal
);
}
init();

init().catch(err => {
if ((err as Error).name !== 'AbortError') console.error(err);
});

return () => controller.abort();
}, []);

return (
Expand Down
68 changes: 29 additions & 39 deletions src/components/GCodePreview.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import * as GCodePreview from 'gcode-preview';
import {
forwardRef,
Ref,
useEffect,
useImperativeHandle,
useRef,
useState
} from 'react';
import * as THREE from 'three';
import { GCodePreview } from 'gcode-preview';
import { forwardRef, Ref, useEffect, useImperativeHandle, useRef } from 'react';

interface GCodePreviewProps {
topLayerColor?: string;
Expand All @@ -19,55 +11,53 @@ interface GCodePreviewProps {

interface GCodePreviewHandle {
getLayerCount: () => number;
processGCode: (gcode: string | string[]) => void;
load: (gcode: string | string[] | ReadableStream) => Promise<void>;
}

function GCodePreviewUI(
props: GCodePreviewProps,
ref: Ref<GCodePreviewHandle>
): JSX.Element {
const {
topLayerColor = '',
lastSegmentColor = '',
startLayer,
endLayer,
lineWidth
} = props;
const { topLayerColor, lastSegmentColor, startLayer, endLayer, lineWidth } =
props;
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [preview, setPreview] = useState<GCodePreview.WebGLPreview>();

const resizePreview = () => {
preview?.resize();
};
const previewRef = useRef<GCodePreview | null>(null);

useImperativeHandle(ref, () => ({
getLayerCount() {
return preview?.layers.length as number;
return previewRef.current?.countLayers as number;
},
processGCode(gcode) {
preview?.processGCode(gcode);
async load(gcode) {
const preview = previewRef.current;
if (!preview) return;
// state persists between loads, so start from a clean job.
// clear() also cancels a stream that is still being read.
preview.clear();
await preview.processGCodeStream(gcode);
}
}));

useEffect(() => {
setPreview(
GCodePreview.init({
canvas: canvasRef.current as HTMLCanvasElement,
startLayer,
endLayer,
lineWidth,
topLayerColor: new THREE.Color(topLayerColor).getHex(),
lastSegmentColor: new THREE.Color(lastSegmentColor).getHex(),
buildVolume: { x: 250, y: 220, z: 150 },
initialCameraPosition: [0, 400, 450],
allowDragNDrop: false
})
);
const preview = new GCodePreview({
canvas: canvasRef.current as HTMLCanvasElement,
startLayer,
endLayer,
lineWidth,
topLayerColor,
lastSegmentColor,
buildVolume: { x: 250, y: 220, z: 150, smallGrid: false },
initialCameraPosition: [0, 400, 450],
droppable: false
});
previewRef.current = preview;

const resizePreview = () => preview.sceneManager.resize();
window.addEventListener('resize', resizePreview);

return () => {
window.removeEventListener('resize', resizePreview);
previewRef.current = null;
preview.dispose();
};
}, []);

Expand Down
8 changes: 0 additions & 8 deletions src/types/index.d.ts

This file was deleted.

2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"typeRoots": ["./node_modules/@types", "./src/types"]
"typeRoots": ["./node_modules/@types"]
}
}
Loading