Newer
Older
CNCTools / ReferenceSurfaceGenerator / frontend / src / DxfViewer.js
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;