Inability of the web viewer to draw the whole model after we change the position of the model

Hi, thank you for your support last week. I notice something odd about the models when I change the camera position. As if the whole model is not drawn. This is the default position of the model.

This is how it looks for example when I select “Right View”.

Maybe my camera settings are wrong. (Using NavCube, everything works perfectly.)
The function responsible for changing the camera options is called handleChangeCameraView. Thanks is advance!

import React, { useState, useEffect } from "react";
import { fetchAuthSession } from "aws-amplify/auth";
import ModelTree from "./ModelTree";
// import LayerTree from "./LayerTree";
import HomeIcon from "~/icons/HomeIcon";
import CameraIcon from "~/icons/CameraIcon";
import ExplodeCubeIcon from "~/icons/ExplodeCubeIcon";
import Select from 'react-select';
import Tooltip from "../basics/Tooltip";

const operatorOptions = [
  { value: 'Orbit', label: 'Orbit' },
  { value: 'Turntable', label: 'Turntable' }
];

const cameraPositions = {
  Front: { position: { x: 10, y: 0, z: 0 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: 0, z: 1 } },
  Back: { position: { x: -10, y: 0, z: 0 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: 0, z: 1 } },
  Right: { position: { x: 0, y: 10, z: 0 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: 0, z: 1 } },
  Left: { position: { x: 0, y: -10, z: 0 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: 0, z: 1 } },
  Top: { position: { x: 0, y: 0, z: 10 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: 1, z: 1 } },
  Bottom: { position: { x: 0, y: 0, z: -10 }, target: { x: 0, y: 0, z: 0 }, up: { x: 0, y: -1, z: 1 } }
};

const cameraOptions = [
  { value: 'Front', label: 'Front View' },
  { value: 'Back', label: 'Back View' },
  { value: 'Right', label: 'Right View' },
  { value: 'Left', label: 'Left View' },
  { value: 'Top', label: 'Top View' },
  { value: 'Bottom', label: 'Bottom View' }
];

const cuttingPlaneOptions = [
  { value: 'X', label: 'X-axis' },
  { value: 'Y', label: 'Y-axis' },
  { value: 'Z', label: 'Z-axis' },
  { value: 'clearAll', label: 'Clear all' },
];

const drawModeOptions = [
  {
    value: 'Shaded', label: "Shaded"
  },
  {
    value: 'HiddenLine', label: "Hidden Line"
  },
  {
    value: 'Wireframe', label: "Wireframe"
  },
  {
    value: 'XRay', label: "X Ray"
  }
];

const buttonStyle = {
  backgroundColor: 'rgba(255, 255, 255, 0.50)',
  border: '1px solid rgba(156, 163, 175, 0.5)',
  borderRadius: '0.375rem',
  cursor: 'pointer',
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
  width: '40px',
  height: '40px'
};

type Props = {
  cadData: string;
}

const HoopsViewer = ({ cadData }: Props) => {
  const [hwv, setHwv] = useState(null);
  const [explodeMagnitude, setExplodeMagnitude] = useState(0);
  const [showExplode, setShowExplode] = useState(false);
  const [drawMode, setDrawMode] = useState({ value: 'Shaded', label: "Shaded" });
  const [cameraView, setCameraView] = useState('Back');
  const [cuttingPlanes, setCuttingPlanes] = useState([]);

  const [selectsOpen, setSelectsOpen] = useState({
    operation: false,
    camera: false,
    drawMode: false,
    cuttingPlane: false
  });

  const handleMenuOpen = (selectName) => {
    setSelectsOpen({ ...selectsOpen, [selectName]: true });
  };

  const handleMenuClose = (selectName) => {
    setSelectsOpen({ ...selectsOpen, [selectName]: false });
  };

  useEffect(() => {
    if (!cadData) {
      return;
    }

    const initViewer = async () => {
      const session = await fetchAuthSession();
      const accessToken = session.tokens?.accessToken?.toString();
      if (!accessToken) {
        console.error("Error in HoopsViewer");
        return;
      }

      if (hwv) {
        hwv.shutdown();
      }

      initViewerWithCADData(accessToken, cadData);
    };

    const script = document.createElement("script");
    script.src = "/js/hoops/hoops_web_viewer.js";
    document.body.appendChild(script);

    script.onload = () => {
      initViewer();
    };

    return () => {
      document.body.removeChild(script);
      if (hwv) {
        hwv.shutdown();
      }
    };
  }, [cadData]);

  const initViewerWithCADData = (authToken, cadData) => {
    const customFetch = (url, options = {}) => {
      if (url.includes(cadData)) {
        if (!(options.headers instanceof Headers)) {
          options.headers = new Headers(options.headers);
        }
        options.headers.set('Authorization', `Bearer ${authToken}`);
      }
      return originalFetch(url, options);
    };

    if (!window.originalFetch) {
      window.originalFetch = window.fetch;
    }

    window.fetch = customFetch;

    const hwvInstance = new window.Communicator.WebViewer({
      containerId: "viewer",
      endpointUri: cadData,
    });

    hwvInstance.setCallbacks({
      sceneReady: function () {
        initAxisTriad(hwvInstance);
        initNavCube(hwvInstance);
      }
    });

    try {
      hwvInstance.start();
      setHwv(hwvInstance);
    } catch (error) {
      console.error("Failed to start HWV", error);
    }
  };

  const initNavCube = (hwvInstance) => {
    const navCube = hwvInstance.view.getNavCube();
    navCube.setAnchor(window.Communicator.OverlayAnchor.UpperRightCorner);
    navCube.enable();
  };

  const initAxisTriad = (hwvInstance) => {
    const axisTriad = hwvInstance.view.getAxisTriad();
    axisTriad.setAnchor(window.Communicator.OverlayAnchor.LowerLeftCorner);
    axisTriad.setAxisColor(Communicator.Axis.Y, Communicator.Color.blue());
    axisTriad.setAxisColor(Communicator.Axis.X, Communicator.Color.green());
    axisTriad.setAxisColor(Communicator.Axis.Z, Communicator.Color.red());
    axisTriad.enable();
  };

  const addCuttingPlane = async (type) => {
    if (!hwv) return;

    const bounding = await hwv.model.getModelBounding(true, true);
    let axis, normal, position;

    switch (type) {
      case 'X':
        axis = Communicator.Axis.X;
        normal = new Communicator.Point3(1, 0, 0);
        position = bounding.max;
        break;
      case 'Y':
        axis = Communicator.Axis.Y;
        normal = new Communicator.Point3(0, 1, 0);
        position = bounding.max;
        break;
      case 'Z':
        axis = Communicator.Axis.Z;
        normal = new Communicator.Point3(0, 0, 1);
        position = bounding.max;
        break;
      default:
        console.error("Invalid plane type selected");
        return;
    }

    const sectionIndex = cuttingPlanes.length;
    const cuttingSection = hwv.cuttingManager.getCuttingSection(sectionIndex);

    const referenceGeometry = hwv.cuttingManager.createReferenceGeometryFromAxis(axis, bounding);
    const plane = Communicator.Plane.createFromPointAndNormal(position, normal);

    await cuttingSection.addPlane(plane, referenceGeometry);
    await cuttingSection.activate();

    setCuttingPlanes([...cuttingPlanes, { type, plane, sectionIndex }]);
  };

  const clearAllCuttingPlanes = async () => {
    if (!hwv) {
      console.error("HWV instance not initialized");
      return;
    }

    try {
      await hwv.cuttingManager.clearAllCuttingSections();
      console.log("All cutting sections cleared and deactivated.");
      setCuttingPlanes([]);
    } catch (error) {
      console.error("Error clearing cutting sections:", error);
    }
  };

  const handleCuttingPlaneChange = (selectedOption) => {
    switch (selectedOption.value) {
      case 'clearAll':
        clearAllCuttingPlanes();
        break;
      default:
        addCuttingPlane(selectedOption.value);
        break;
    }
  };

  const resetCamera = () => {
    hwv.view.resetCamera(750);
  };

  const handleChangeDrawMode = (selectedOption) => {
    setDrawMode(selectedOption);
    hwv.view.setDrawMode(Communicator.DrawMode[selectedOption.value]);
  };

  const handleChangeCameraView = (selectedOption) => {
    setCameraView(selectedOption.value);
    const config = cameraPositions[selectedOption.value];
    const newCamera = new Communicator.Camera();

    hwv.model.getModelBounding(true, true).then((bounding) => {
      const center = new Communicator.Point3(
        (bounding.min.x + bounding.max.x) / 2,
        (bounding.min.y + bounding.max.y) / 2,
        (bounding.min.z + bounding.max.z) / 2
      );

      newCamera.setPosition(new Communicator.Point3(
        config.position.x + center.x - config.target.x,
        config.position.y + center.y - config.target.y,
        config.position.z + center.z - config.target.z
      ));
      newCamera.setTarget(center);
      newCamera.setUp(new Communicator.Point3(config.up.x, config.up.y, config.up.z));

      const size = Math.sqrt(
        Math.pow(bounding.max.x - bounding.min.x, 2) +
        Math.pow(bounding.max.y - bounding.min.y, 2) +
        Math.pow(bounding.max.z - bounding.min.z, 2)
      );
      const aspectRatio = hwv.view.getCanvasSize().x / hwv.view.getCanvasSize().y;
      const zoomFactor = 0.75;
      const fieldOfView = zoomFactor * size;

      newCamera.setWidth(fieldOfView * aspectRatio);
      newCamera.setHeight(fieldOfView);

      hwv.view.setCamera(newCamera, 750);
    }).catch((error) => {
      console.error("Error setting camera view:", error);
    });
  };

  const handleChangeExplodeMagnitude = (event) => {
    const newMagnitude = Number(event.target.value);
    setExplodeMagnitude(newMagnitude);
    if (hwv) {
      hwv.getExplodeManager().setMagnitude(newMagnitude / 100);
    }
  };

  const createSnapshotConfig = () => {
    const config = new Communicator.SnapshotConfig();
    config.width = 1920;
    config.height = 1080;
    config.layers = Communicator.SnapshotLayer.All;

    return config;
  };

  const takeSnapshot = () => {
    if (!hwv) {
      console.error("HWV instance not initialized");
      return;
    }

    const snapshotConfig = createSnapshotConfig();

    hwv.takeSnapshot(snapshotConfig).then((result) => {
      if (result instanceof Blob) {
        const url = URL.createObjectURL(result);
        const link = document.createElement('a');
        link.href = url;
        link.download = "snapshot.png";
        link.click();
        URL.revokeObjectURL(url);
      } else if (result instanceof HTMLImageElement) {
        const src = result.src;
        const link = document.createElement('a');
        link.href = src;
        link.download = "snapshot.png";
        link.click();
      } else {
        console.error("Snapshot did not return a valid result:", result);
      }
    }).catch((error) => {
      console.error("Error taking snapshot:", error);
    });
  };

  const handleOperationChange = (selectedOption) => {
    if (!hwv) {
      console.error("HWV instance not initialized");
      return;
    }

    const operatorManager = hwv.operatorManager;
    let operatorId;
    switch (selectedOption.value) {
      case 'Orbit':
        operatorId = Communicator.OperatorId.Navigate;
        break;
      case 'Turntable':
        operatorId = Communicator.OperatorId.Turntable;
        break;
      default:
        console.error("Unsupported operation:", selectedOption.value);
        return;
    }

    if (operatorId !== undefined) {
      operatorManager.set(operatorId, 0);
    } else {
      console.error("Invalid operator ID for", selectedOption.value);
    }
  };

  return (
    <div className="flex h-full w-full relative h-screen">
      <div id="viewer" className="absolute top-0 left-0 w-full h-full" />
      <div className="absolute bottom-12 left-1/2 transform -translate-x-1/2 flex items-center">
        <Tooltip text={"Reset camera"} position="top">
          <div style={{ ...buttonStyle, marginRight: '10px' }} onClick={resetCamera}>
            <HomeIcon className="w-4 h-4 text-black" />
          </div>
        </Tooltip>

        <Tooltip text="Take Snapshot" position="top">
          <div style={{ ...buttonStyle }} onClick={takeSnapshot}>
            <CameraIcon className="w-4 h-4" />
          </div>
        </Tooltip>

        <Tooltip text="Camera Operation" position="top" isVisible={!selectsOpen?.operation}>
          <Select
            placeholder="Ca.."
            onChange={handleOperationChange}
            options={operatorOptions}
            onMenuOpen={() => handleMenuOpen('operation')}
            onMenuClose={() => handleMenuClose('operation')}
            menuPlacement="top"
            styles={{
              control: (base) => ({
                ...base,
                minWidth: 80,
                marginLeft: "10px",
                cursor: "pointer",
                marginRight: "10px",
                backgroundColor: 'rgba(255, 255, 255, 0.50)',
                borderColor: 'rgba(156, 163, 175, 0.5)',
                boxShadow: 'none',
                zIndex: 1000,
                '&:hover': {
                  borderColor: 'rgba(156, 163, 175, 0.5)'
                }
              })
            }}
          />
        </Tooltip>

        <Tooltip text="Camera menu" position="top" isVisible={!selectsOpen.camera}>
          <Select
            value={cameraOptions.find(option => option.value === cameraView)}
            onChange={handleChangeCameraView}
            options={cameraOptions}
            onMenuOpen={() => handleMenuOpen('camera')}
            onMenuClose={() => handleMenuClose('camera')}
            menuPlacement="top"
            styles={{
              control: (base) => ({
                ...base,
                marginRight: "10px",
                cursor: "pointer",
                minWidth: 80,
                backgroundColor: 'rgba(255, 255, 255, 0.50)',
                borderColor: 'rgba(156, 163, 175, 0.5)',
                boxShadow: 'none',
                '&:hover': {
                  borderColor: 'rgba(156, 163, 175, 0.5)'
                },
                zIndex: 1000
              })
            }}
          />
        </Tooltip>

        <Tooltip text="Draw mode" position="top" isVisible={!selectsOpen.drawMode}>
          <Select
            value={drawMode}
            onChange={handleChangeDrawMode}
            options={drawModeOptions}
            onMenuOpen={() => handleMenuOpen('drawMode')}
            onMenuClose={() => handleMenuClose('drawMode')}
            menuPlacement="top"
            styles={{
              control: (base) => ({
                ...base,
                minWidth: 80,
                cursor: "pointer",
                marginRight: "10px",
                backgroundColor: 'rgba(255, 255, 255, 0.50)',
                borderColor: 'rgba(156, 163, 175, 0.5)',
                boxShadow: 'none',
                '&:hover': {
                  borderColor: 'rgba(156, 163, 175, 0.5)'
                },
                zIndex: 1000
              })
            }}
          />
        </Tooltip>

        <Tooltip text="Explode Menu" position="top" isVisible={!showExplode}>
          <div style={{ ...buttonStyle, position: 'relative', marginRight: "10px" }} onClick={() => setShowExplode(!showExplode)}>
            <ExplodeCubeIcon className="w-4 h-4" />
            {showExplode && (
              <input
                type="range"
                min="0"
                max="100"
                value={explodeMagnitude}
                onChange={handleChangeExplodeMagnitude}
                className="slider-vertical"
              />
            )}
          </div>
        </Tooltip>

        <Tooltip text="Cutting Plane Menu" position="top" isVisible={!selectsOpen?.cuttingPlane}>
          <Select
            placeholder="Cu.."
            onChange={handleCuttingPlaneChange}
            options={cuttingPlaneOptions}
            onMenuOpen={() => handleMenuOpen('cuttingPlane')}
            onMenuClose={() => handleMenuClose('cuttingPlane')}
            menuPlacement="top"
            styles={{
              control: (base) => ({
                ...base,
                minWidth: 80,
                cursor: "pointer",
                backgroundColor: 'rgba(255, 255, 255, 0.50)',
                borderColor: 'rgba(156, 163, 175, 0.5)',
                boxShadow: 'none',
                zIndex: 100,
                '&:hover': {
                  borderColor: 'rgba(156, 163, 175, 0.5)'
                }
              })
            }}
          />
        </Tooltip>

      </div>
      {hwv && <ModelTree hwv={hwv} />}
      {/* {hwv && <LayerTree hwv={hwv} />} */}
    </div>
  );
};

export default HoopsViewer;

Hello @dayana,

In your implementation, it looks like the camera is clipping the model in certain sections. This can happen when manually setting the camera attributes and can also occur depending on the model being loaded. One way to solve this is to use the ViewOrientation enum which has pre-defined orientations for the view right out-of-the-box.

If you still want to manually calculate the camera object, we recommend that the distance from the camera position to the camera target is 2.5 times the field width. In a different toolkit, we provide more details on this.

Also available in the forum is this post which provides a short code snippet. Granted, the code is for Visualize but the logic is straigthforward.

Thanks,
Tino

Thank you, now I am using the ViewOrientation. This is the code sample that now works for me perfect:

  const handleChangeCameraView = (selectedOption) => {
    setCameraView(selectedOption.value);
    if (!hwv) {
      console.error("HWV instance not initialized");
      return;
    }
    const orientation = window.Communicator.ViewOrientation[selectedOption.value];
    hwv.view.setViewOrientation(orientation, 750);
  };

Best regards,
Dayana

1 Like