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