Not able to render a layer tree component in React.js

I was trying to create a layer tree component, using resources from this page:

[Layers and filters β€” HOOPS Communicator 2024.2.0 Documentation]

I have created this component in React.js:

import React, { useState, useEffect } from 'react';

const LayerTree = ({ hwv }) => {
  const [layers, setLayers] = useState([]);

  useEffect(() => {
    const fetchLayers = () => {
      if (hwv && hwv.model) {
        const layersMap = hwv.model.getLayers();
        if (layersMap.size > 0) {
          const layersArray = Array.from(layersMap, ([id, name]) => ({ id, name }));
          setLayers(layersArray);
          console.log("Layers fetched:", layersArray);
        } else {
          console.log("No layers found or layers are not yet loaded.");
        }
      }
    };

    // Listen for a specific event that tells you the model is fully loaded; for now, we use a timeout for demonstration
    setTimeout(fetchLayers, 1000);

    return () => {
      // Cleanup logic if necessary
    };
  }, [hwv]);

  const toggleLayerVisibility = (layerId) => {
    const nodeIds = hwv.model.getNodesFromLayer(layerId);
    nodeIds.forEach(nodeId => {
      const isVisible = hwv.model.getNodeVisibility(nodeId);
      hwv.model.setNodeVisibility(nodeId, !isVisible);
    });
    hwv.view.update();
  };

  return (
    <div className="layer-tree">
      <ul>
        {layers.map(layer => (
          <li key={layer.id} onClick={() => toggleLayerVisibility(layer.id)}>
            {layer.name}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default LayerTree;

I am using LayerTree component in HoopsViewer component:

import React, { useState, useEffect } from "react";
import ModelTree from "./ModelTree";
import LayerTree from "./LayerTree";
import HomeIcon from "~/icons/HomeIcon";
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'
};

const HoopsViewer = () => {
  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([]);

  useEffect(() => {
    const script = document.createElement("script");
    script.src = "/js/hoops/hoops_web_viewer.js";
    script.onload = () => initViewer();
    document.body.appendChild(script);
    return () => {
      document.body.removeChild(script);
      hwv?.shutdown();
    };
  }, []);


  const initViewer = () => {
    const hwvInstance = new window.Communicator.WebViewer({
      containerId: "viewer",
      endpointUri: "/data/microengine.scs",
    });

    try {
      hwvInstance.start();
      setHwv(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.TopRight);
    navCube.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();
    newCamera.setPosition(new Communicator.Point3(config.position.x, config.position.y, config.position.z));
    newCamera.setTarget(new Communicator.Point3(config.target.x, config.target.y, config.target.z));
    newCamera.setUp(new Communicator.Point3(config.up.x, config.up.y, config.up.z));

    const aspectRatio = hwv.view.getCanvasSize().x / hwv.view.getCanvasSize().y;
    const baseSize = 200;
    newCamera.setWidth(baseSize * aspectRatio);
    newCamera.setHeight(baseSize);

    hwv.view.setCamera(newCamera, 750);
  };

  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;
    operatorManager.pop();

    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.push(operatorId);
    } 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="left">
          <div style={{ ...buttonStyle, marginRight: '10px' }} onClick={resetCamera}>
            <HomeIcon className="w-4 h-4 text-black" />
          </div>
        </Tooltip>

        <Tooltip text="Take Snapshot" position="left">
          <div style={{ ...buttonStyle }} onClick={takeSnapshot}>
            <span>πŸ“Έ</span>
          </div>
        </Tooltip>

        <Tooltip text="Camera Operation" position="left">
          <Select
            placeholder="Ca.."
            onChange={handleOperationChange}
            options={operatorOptions}
            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',
                '&:hover': {
                  borderColor: 'rgba(156, 163, 175, 0.5)'
                }
              })
            }}
          />
        </Tooltip>

        <Tooltip text="Camera menu" position="left">
          <Select
            value={cameraOptions.find(option => option.value === cameraView)}
            onChange={handleChangeCameraView}
            options={cameraOptions}
            menuPlacement="top"
            styles={{
              control: (base) => ({
                ...base,
                marginRight: "10px",
                cursor: "pointer",
                marginLeft: "10px",
                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)'
                }
              })
            }}
          />
        </Tooltip>

        <Tooltip text="Draw mode" position="left">
          <Select
            value={drawMode}
            onChange={handleChangeDrawMode}
            options={drawModeOptions}
            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)'
                }
              })
            }}
          />
        </Tooltip>

        <Tooltip text="Explode Menu" position="left">
          <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="left">
          <Select
            placeholder="Cu.."
            onChange={handleCuttingPlaneChange}
            options={cuttingPlaneOptions}
            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',
                '&:hover': {
                  borderColor: 'rgba(156, 163, 175, 0.5)'
                }
              })
            }}
          />
        </Tooltip>
      </div>
      {hwv && <ModelTree hwv={hwv} />}
      {hwv && <LayerTree hwv={hwv} />}
    </div>
  );
};

export default HoopsViewer;

My problem here is that I can’t visualize a layer tree. My layers come out as an empty array. Does having layers depend on the file we provide? If yes is there a file in hoops web viewer installer that I can use as a mock to test the layer tree ? Thanks in advance !

Hello @dayana,

Not all CAD files will have layers. In the link you referenced in your post, the source CAD file used for that part of the docs can be found in this directory:
HOOPS_Communicator_2024.X.0\authoring\converter\example_data\HotelFloorplan.dwg

Or a quicker alternative is to load the SCS file found in:
HOOPS_Communicator_2024.X.0\quick_start\converted_models\standard\scs_models\HotelFloorplan.scs

You could try these files for mock testing your build.

Thanks,
Tino

1 Like