From 8310e7d12b619eb0d90f7f11b74649ed407253a6 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 7 Sep 2025 19:45:09 +0100 Subject: [PATCH] Added file previews --- .../Dashboard/common/GCodePreview.jsx | 68 +++---- .../Dashboard/common/ThreeDPreview.jsx | 168 ++++++++++++++++++ 2 files changed, 203 insertions(+), 33 deletions(-) create mode 100644 src/components/Dashboard/common/ThreeDPreview.jsx diff --git a/src/components/Dashboard/common/GCodePreview.jsx b/src/components/Dashboard/common/GCodePreview.jsx index 0c35cb7..00dfee7 100644 --- a/src/components/Dashboard/common/GCodePreview.jsx +++ b/src/components/Dashboard/common/GCodePreview.jsx @@ -1,36 +1,26 @@ import * as GCodePreview from 'gcode-preview' -import { - forwardRef, - useEffect, - useImperativeHandle, - useRef, - useState -} from 'react' +import PropTypes from 'prop-types' +import { useCallback, useEffect, useRef, useState } from 'react' import * as THREE from 'three' -function GCodePreviewUI(props, ref) { +function GCodePreviewUI(props) { const { + src, topLayerColor = '', lastSegmentColor = '', startLayer, endLayer, - lineWidth + lineWidth, + style = {} } = props const canvasRef = useRef(null) const [preview, setPreview] = useState() - const resizePreview = () => { + const resizePreview = useCallback(() => { preview?.resize() - } + }, [preview]) - useImperativeHandle(ref, () => ({ - getLayerCount() { - return preview?.layers.length - }, - processGCode(gcode) { - preview?.processGCode(gcode) - } - })) + // Ex-ref methods removed; this component is now a regular functional component useEffect(() => { setPreview( @@ -52,21 +42,33 @@ function GCodePreviewUI(props, ref) { return () => { window.removeEventListener('resize', resizePreview) } - }, []) + }, [endLayer, lastSegmentColor, lineWidth, startLayer, topLayerColor]) - return ( -
- + useEffect(() => { + const loadFromSrc = async () => { + if (!src || !preview) return + try { + const response = await fetch(src) + const text = await response.text() + preview.processGCode(text) + } catch (e) { + console.error('Failed to load G-code from src', e) + } + } + loadFromSrc() + }, [src, preview]) -
-
topLayerColor: {topLayerColor}
-
lastSegmentColor: {lastSegmentColor}
-
startLayer: {startLayer}
-
endLayer: {endLayer}
-
lineWidth: {lineWidth}
-
-
- ) + return } -export default forwardRef(GCodePreviewUI) +GCodePreviewUI.propTypes = { + src: PropTypes.string, + topLayerColor: PropTypes.string, + lastSegmentColor: PropTypes.string, + startLayer: PropTypes.number, + endLayer: PropTypes.number, + lineWidth: PropTypes.number, + style: PropTypes.object +} + +export default GCodePreviewUI diff --git a/src/components/Dashboard/common/ThreeDPreview.jsx b/src/components/Dashboard/common/ThreeDPreview.jsx new file mode 100644 index 0000000..ac8a7bc --- /dev/null +++ b/src/components/Dashboard/common/ThreeDPreview.jsx @@ -0,0 +1,168 @@ +import PropTypes from 'prop-types' +import { useCallback, useEffect, useRef, useState } from 'react' +import * as OV from 'online-3d-viewer' +import LoadingPlaceholder from './LoadingPlaceholder' + +function ThreeDPreview(props) { + const { + src, + extension = '.stl', + width = 500, + height = 500, + style = {}, + backgroundColor = '#ffffff', + showGrid = true, + showAxes = true, + enableControls = true + } = props + const containerRef = useRef(null) + const viewer = useRef(null) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const resizeViewer = useCallback(() => { + if (viewer.current && containerRef.current) { + // Resize the viewer container + const container = containerRef.current + if (container.style.width !== width + 'px') { + container.style.width = width + 'px' + } + if (container.style.height !== height + 'px') { + container.style.height = height + 'px' + } + } + }, [viewer, width, height]) + + useEffect(() => { + const initializeViewer = async () => { + if (!containerRef.current) return + + try { + setIsLoading(true) + setError(null) + + // Clear any existing viewer + if (viewer) { + containerRef.current.innerHTML = '' + } + + // Wait for the OV global to be available + if (typeof OV === 'undefined') { + // If OV is not available, try to load it dynamically + console.warn( + 'OV (Online 3D Viewer) is not available. Make sure the library is loaded.' + ) + setError('3D Viewer library not loaded') + setIsLoading(false) + return + } + + // Initialize the online-3d-viewer using OV.EmbeddedViewer + const newViewer = new OV.EmbeddedViewer(containerRef.current, { + camera: new OV.Camera( + new OV.Coord3D(-1.5, 2.0, 3.0), + new OV.Coord3D(0.0, 0.0, 0.0), + new OV.Coord3D(0.0, 1.0, 0.0), + 45.0 + ), + backgroundColor: new OV.RGBAColor(255, 255, 255, 255), + defaultColor: new OV.RGBColor(200, 200, 200), + edgeSettings: new OV.EdgeSettings(false, new OV.RGBColor(0, 0, 0), 1), + environmentSettings: new OV.EnvironmentSettings([], false) + }) + + try { + setIsLoading(true) + setError(null) + + const response = await fetch(src) + const arrayBuffer = await response.arrayBuffer() + + // Create a file-like object from the array buffer + const fileName = `model${extension}` + const file = new File([arrayBuffer], fileName, { + type: 'application/octet-stream' + }) + + // Load model from file using LoadModelFromFileList + await newViewer.LoadModelFromFileList([file]) + console.log(src) + setIsLoading(false) + } catch (err) { + console.error('Failed to load 3D model from src', err) + setError('Failed to load 3D model') + setIsLoading(false) + } + } catch (err) { + console.error('Failed to initialize 3D viewer', err) + setError('Failed to initialize 3D viewer') + setIsLoading(false) + } + } + + initializeViewer() + + window.addEventListener('resize', resizeViewer) + + return () => { + window.removeEventListener('resize', resizeViewer) + if (viewer.current && viewer.current.dispose) { + viewer.current.dispose() + } + } + }, [width, height, backgroundColor, showGrid, showAxes, enableControls, src]) + + const containerStyle = { + width: width + 'px', + height: height + 'px', + backgroundColor, + position: 'relative', + ...style + } + + return ( +
+
+ {isLoading && } + {error && ( +
+ {error} +
+ )} +
+ ) +} + +ThreeDPreview.propTypes = { + src: PropTypes.string, + width: PropTypes.number, + height: PropTypes.number, + style: PropTypes.object, + backgroundColor: PropTypes.string, + showGrid: PropTypes.bool, + showAxes: PropTypes.bool, + enableControls: PropTypes.bool, + extension: PropTypes.string.isRequired +} + +export default ThreeDPreview