Hello, in principle I have the option in my React component to change the orbit camera to orbit and turntable. Initially I use an orbit camera and then I have the options to zoom with the mouse and use pan and walk around the object. However, when I change to turntable and then back to orbit camera this functionality is lost, can you help me. The functions responsible for this are called handleOperationChange. 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: '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 || 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.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 referenceGeometry = hwv.cuttingManager.createReferenceGeometryFromAxis(axis, 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 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.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-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;