We recently released ESM for Communicator for an improved development experience.
Here is a guide for setting up Communicator ESM with TypeScript in Next.js, a full-stack React framework.
Note: This guide was created for HOOPS Communicator 2024.4.0 and instructions may vary in future releases.
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. More info on configuration related to Next.js can be found in the Next.js documentation.
cd
to the new<Next_App>
directory created (test_app_ts
in this case) and runnpm run dev
to start the development server to view the app and verify successful installation. -
Create a directory under
<Next_App>/app
calledhoops
. -
Copy
<Communicator_Install_Directory>/web_viewer/types
to this directory. -
Copy
<Communicator_Install_Directory>/web_viewer/hoops-web-viewer-monolith.mjs
to<Next_App>/app/hoops/types/web-viewer
.
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.
- Create a new file called
package.json
in<Next_App>/app/hoops/types/web-viewer
and paste the following:
{
"name": "@hoops/web-viewer",
"version": "0.0.1",
"main": "./hoops-web-viewer-monolith.mjs",
"typings": "./index.d.ts"
}
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:
'use client'
import * as Communicator from '../hoops/types/web-viewer';
import { useState, useEffect } from 'react';
- 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) {
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:
import Viewer from './components/Viewer';
export default function Home() {
return (
<Viewer />
)
}
- Check your app by running
npm run dev
:
Note: You may see error messages in the terminal. This is a known issue and a resolution is being worked on.
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)=>{
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/types/web-viewer';
import { useState, useEffect } from 'react';
export default function Viewer() {
const [nodeId, setNodeId] = useState<number | null>(null);
useEffect(()=>{
let hwv = startViewer("viewer", "./models/microengine.scs");
// Cleanup
return () => {
hwv.shutdown();
}
}, []);
let hwv : Communicator.WebViewer;
function startViewer(container: string, model: string) {
hwv = new Communicator.WebViewer({
containerId: container,
endpointUri: model
});
window.onresize = () => {
hwv.resizeCanvas();
}
hwv.setCallbacks({
selectionArray: (selEvents)=>{
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>
</>
)
}
Please let me know if you have questions or run into any issues with setting up HOOPS Communicator.
Feel free to share any of your own tips as well!