import React, { useState, useEffect, Suspense } from 'react';
import axios from 'axios';
import { Modal, Button, Alert, Spinner } from 'react-bootstrap';
import { Canvas, useThree } from '@react-three/fiber';
import { OrbitControls, Line } from '@react-three/drei';
import * as THREE from 'three';
const DxfContent = ({ data }) => {
const { scene } = useThree();
// Calculate the bounding box of all polylines
const boundingBox = new THREE.Box3();
data.polylines.forEach(polyline => {
polyline.forEach(point => {
boundingBox.expandByPoint(new THREE.Vector3(point[0], point[1], point[2]));
});
});
// Calculate the offset required to move the bottom of the object to z=0
const zMin = boundingBox.min.z;
const translationOffset = new THREE.Vector3(0, 0, -zMin);
// Calculate the new center for the camera target after translation
const originalCenter = boundingBox.getCenter(new THREE.Vector3());
const adjustedCenter = originalCenter.clone().add(translationOffset);
// Determine appropriate size for lighting and grid
const size = boundingBox.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
// Add GridHelper from THREE directly, positioned at the new center
React.useEffect(() => {
const gridHelper = new THREE.GridHelper(maxDim * 2, 20, 0x000000, 0xcccccc);
// The grid itself stays at y=0, but we can align its x/z center with the object
gridHelper.position.set(adjustedCenter.x, 0, adjustedCenter.z);
scene.add(gridHelper);
return () => {
scene.remove(gridHelper);
gridHelper.dispose();
};
}, [maxDim, adjustedCenter, scene]);
return (
<>
<ambientLight intensity={1} />
<pointLight position={[adjustedCenter.x + maxDim, adjustedCenter.y + maxDim, adjustedCenter.z + maxDim]} intensity={0.5} />
<pointLight position={[adjustedCenter.x - maxDim, adjustedCenter.y - maxDim, adjustedCenter.z - maxDim]} intensity={0.5} />
{data.polylines.map((polyline, i) => {
// Apply the translation to each point and explicitly close the loop
const adjustedPoints = polyline.map(p => new THREE.Vector3(p[0], p[1], p[2]).add(translationOffset));
const closedPolyline = [...adjustedPoints, adjustedPoints[0]];
return (
<Line
key={i}
points={closedPolyline}
color="black"
lineWidth={4}
/>
);
})}
<OrbitControls
target={adjustedCenter} // Focus camera on the new, adjusted center
enableDamping
dampingFactor={0.1}
rotateSpeed={0.5}
/>
</>
);
};
const DxfViewer = ({ show, onHide, viewUrl, API_URL }) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
// State for camera settings, adjusted dynamically
const [cameraProps, setCameraProps] = useState({ position: [0, 0, 150], fov: 75, near: 0.1, far: 1000 });
useEffect(() => {
if (show && viewUrl) {
const fetchData = async () => {
setLoading(true);
setError(null);
setData(null);
try {
const response = await axios.get(`${API_URL}${viewUrl}`);
setData(response.data);
// Calculate dynamic camera properties once data is loaded
if (response.data && response.data.polylines.length > 0) {
const boundingBox = new THREE.Box3();
response.data.polylines.forEach(polyline => {
polyline.forEach(point => {
boundingBox.expandByPoint(new THREE.Vector3(point[0], point[1], point[2]));
});
});
const center = boundingBox.getCenter(new THREE.Vector3());
const size = boundingBox.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
const dynamicCameraDistance = maxDim * 1.5; // Used here
setCameraProps(prev => ({
...prev,
position: [center.x, center.y, center.z + dynamicCameraDistance],
near: Math.max(0.1, dynamicCameraDistance / 1000), // Adjust near plane dynamically
far: dynamicCameraDistance * 2 // Adjust far plane dynamically
}));
}
} catch (err) {
setError(err.response?.data?.detail || 'Failed to load DXF data.');
} finally {
setLoading(false);
}
};
fetchData();
}
}, [show, viewUrl, API_URL]);
return (
<Modal show={show} onHide={onHide} size="xl" centered>
<Modal.Header closeButton>
<Modal.Title>DXF Viewer</Modal.Title>
</Modal.Header>
<Modal.Body style={{ height: '70vh', backgroundColor: 'white' }}> {/* Changed to white */}
{loading && <div className="text-center"><Spinner animation="border" variant="dark" /> <span className="text-dark ms-2">Loading geometry...</span></div>} {/* Spinner color changed */}
{error && <Alert variant="danger">{error}</Alert>}
{data && (
<Suspense fallback={<div className="text-center text-dark">Preparing renderer...</div>}> {/* Text color changed */}
<Canvas camera={cameraProps} antialias={true}> {/* Added antialias */}
<color attach="background" args={['white']} /> {/* Explicitly set canvas background */}
<DxfContent data={data} />
</Canvas>
</Suspense>
)}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={onHide}>
Close
</Button>
</Modal.Footer>
</Modal>
);
};
export default DxfViewer;