Problem with initializing and positioning cutting panels

Hi, I have a problem with the cutting panels in my React component. I really want to implement this example with cutting panels:
https://techsoft3d.github.io/Engine-Scan/
First I can’t figure out how to make a function that clears all panels.
Second, somehow the positions of the panels themselves when initials are selected are a bit confusing for the user. For example, when I select the X axis panel, it stands in front of the object and hides it. In the demo, when you choose X axis to cut, the panel is behind the object and it is much clearer where it can be cut.

When I choose Z axis in the demo, the plane stands above the object and the cutting direction is clear. Whereas with me it stands in front of the object and in a vertical position and it is very confusing. In general, if you could provide me with functions for the correct position and behavior of these panels, I would be very grateful. I put all the code on my React component so that important context is not lost, but the functions responsible for the cutting panels are addCuttingPlane and handleCuttingPlaneChange. Thanks in 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: 'FaceNormal', label: 'Face Normal' }
];

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: "/data/microengine.scs",
    });

    try {
      hwvInstance.start();
      setHwv(hwvInstance);
      initAxisTriad(hwvInstance);
      initNavCube(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 || cuttingPlanes.length >= 6) return;

    try {
      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.min;
          break;
        case 'Y':
          axis = Communicator.Axis.Y;
          normal = new Communicator.Point3(0, 1, 0);
          position = bounding.min;
          break;
        case 'Z':
          axis = Communicator.Axis.Z;
          normal = new Communicator.Point3(0, 0, 1);
          position = bounding.min;
          break;
        case 'FaceNormal':
          normal = new Communicator.Point3(0, 0, 1);
          position = bounding.center();
          break;
        default:
          console.error("Invalid plane type selected");
          return;
      }

      const referenceGeometry = hwv.cuttingManager.createReferenceGeometryFromAxis(Communicator.Axis.X, bounding);
      const plane = Communicator.Plane.createFromPointAndNormal(position, normal);
      const cuttingSection = hwv.cuttingManager.getCuttingSection(0);
      await cuttingSection.addPlane(plane, referenceGeometry);
      await cuttingSection.activate();
      setCuttingPlanes([...cuttingPlanes, { type, plane }]);
    } catch (error) {
      console.error("Error adding cutting plane:", error);
    }
  };

  const handleCuttingPlaneChange = (selectedOption) => {
    addCuttingPlane(selectedOption.value);
  };

  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.Orbit;
        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-10 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;

Initial position of Z axis on my end and in the demo


Initial position of Y axis on my end and in the demo


Initial postion of X axis on my end and in demo (all the examples are cutting correct, but initial position is very confusing)


Hello @dayana,

In the code you provided, you are hard-coding the axis (Communicator.Axis.X) of the reference geometry representing the cutting plane location:
const referenceGeometry = hwv.cuttingManager.createReferenceGeometryFromAxis(Communicator.Axis.X, bounding);

I think it should be:
const referenceGeometry = hwv.cuttingManager.createReferenceGeometryFromAxis(axis, bounding);

You can either deactivate or clear all the cutting sections using the functions deactivateAllCuttingSections or clearAllCuttingSections.

Thanks,
Tino

Thanks, @tino, this was very helpfull. Also I changed this configuration, and now the plates are positioned correctly.

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