Updated guide for HOOPS Communicator 2025.1.0 and Next.js 15
Initial Setup
-
Download HOOPS Communicator from https://manage.techsoft3d.com/ or our Developer zone if you are an existing partner.
-
Create an empty directory for your project and open Visual Studio Code or your preferred IDE. (The rest of this tutorial will be based on Visual Studio Code.)
-
Open a terminal and run
npx create-next-app@latest
.
Apply your desired configuration as shown above. Note that it is not recommended to use Turbopack with HOOPS Communicator. More info on configuration related to Next.js can be found in the Next.js documentation.
cd
to the new <Next_App>
directory created (hc_2025_next
in this case) and run npm run dev
to start the development server to view the app and verify successful installation.
-
Create a directory under
<Next_App>/app
calledhoops
. -
Copy the contents of
<Communicator_Install_Directory>/web_viewer/@hoops
to this directory.
Note: we are using the monolith version of the web viewer here for simplicity. The non-monolithic version will require additional configuration at this time, which we will not cover in this guide.
Communicator Integration
Create a viewer component
-
Copy microengine.scs from
<Communicator>/quick_start/converted_models/standard/scs
to a new directory called<Next_App>/public/models
. -
Create a directory in
<Next_App>/app
calledcomponents
. -
Create
Viewer.tsx
here and create an empty basic component:
export default function Viewer() {
return (
<>
</>
)
}
- At the top of the file, add the
'use client'
directive to support the web viewer and React functionalities. Also, include the module imports for this basic demo:
import * as Communicator from '../hoops/web-viewer'
import setupMonolith from '../hoops/web-viewer-monolith/setupMonolith';
import { useState, useEffect } from 'react';
- Next, set up the monolith as follows:
const engine = setupMonolith();
Communicator.WebViewer.defaultEngineBinary = engine.binary;
- Within the
Viewer
function, create a variablehwv
for the viewer and a container (div element) for the viewer:
let hwv : Communicator.WebViewer;
return (
<>
<div id="viewer"></div>
</>
)
(If you’d like to confirm your setup, scroll to the bottom for the completed files.)
- Above the
return
in that function, add astartViewer
function:
function startViewer(container: string, model: string) : Communicator.WebViewer {
hwv = new Communicator.WebViewer({
containerId: container,
endpointUri: model
});
window.onresize = () => {
hwv.resizeCanvas();
}
hwv.start();
return hwv;
}
- We’ll want this function executed once the page has rendered, so we can use
useEffect
to call it. Add this before everything else you’ve defined in theViewer
function:
useEffect(() => {
let hwv = startViewer("viewer", "./models/microengine.scs");
return () => {
hwv.shutdown();
}
}, [])
Note the use of the cleanup function (see https://react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) for info).
Render the component
- Delete all contents in
<Next_App>/app/page.js
and replace with the following so just theViewer
component is rendered:
'use client'
import dynamic from "next/dynamic";
const Viewer = dynamic(()=> import('./components/Viewer'), {
ssr: false
});
export default function Home() {
return (
<Viewer />
)
}
Note that we dynamically import the Communicator “Viewer” component and disable server side rendering for it because Communicator depends on the browser API.
- Check your app by running
npm run dev
:
Using State
Next, we will create a simple demo of using State to change what’s on the web page. Specifically, we will display the node ID of the most recently clicked node in the lower right corner of the screen:
- In
Viewer.tsx
in theViewer
function, add the following before theuseEffect
:
const [nodeId, setNodeId] = useState<number | null>(null);
- In the
startViewer
function, add a callback to set thenodeId
state to the most recently clicked node. Add the following immediately beforehwv.start()
:
hwv.setCallbacks({
selectionArray: (selEvents : NodeSelectionEvent[])=>{
if (selEvents.length > 0){
const lastSelectedNode = selEvents[0].getSelection().getNodeId();
setNodeId(lastSelectedNode);
}
else {
setNodeId(null);
}
}
});
- Finally, add the HTML elements to display the
nodeId
by adding the following in thereturn
statement, just after theviewer
div:
<div id="nodeDisplay" style={{position: "absolute", bottom: "10px", right: "10px", fontSize: "18px", padding: "10px"}}>
<p id="nodeDisplayText">{"Node ID: " + nodeId}</p>
</div>
- Run
npm run dev
and notice the “Node ID” display changes based on the last node that’s clicked.
Summary
Directory structure:
Viewer.tsx
'use client'
import * as Communicator from '../hoops/web-viewer'
import setupMonolith from '../hoops/web-viewer-monolith/setupMonolith';
import { useState, useEffect } from 'react';
import { NodeSelectionEvent } from '../hoops/web-viewer/lib/event';
const engine = setupMonolith();
Communicator.WebViewer.defaultEngineBinary = engine.binary;
export default function Viewer() {
const [nodeId, setNodeId] = useState<number | null>(null);
useEffect(() => {
let hwv = startViewer("viewer", "./models/microengine.scs");
return () => {
hwv.shutdown();
}
}, [])
let hwv : Communicator.WebViewer;
function startViewer(container: string, model: string) : Communicator.WebViewer {
hwv = new Communicator.WebViewer({
containerId: container,
endpointUri: model
});
window.onresize = ()=> {
hwv.resizeCanvas();
}
hwv.setCallbacks({
selectionArray: (selEvents : NodeSelectionEvent[])=>{
if (selEvents.length > 0){
const lastSelectedNode = selEvents[0].getSelection().getNodeId();
setNodeId(lastSelectedNode);
}
else {
setNodeId(null);
}
}
});
hwv.start();
return hwv;
}
return (
<>
<div id="viewer"></div>
<div id="nodeDisplay" style={{position: "absolute", bottom: "10px", right: "10px", fontSize: "18px", padding: "10px"}}>
<p id="nodeDisplayText">{"Node ID: " + nodeId}</p>
</div>
</>
)
}