Your First Application
This guide walks you through building a functional 3D configurator. While the Main Page provides an overview, here we focus on the implementation logic: connecting standard HTML UI elements to the ShapeDiver API.
1. The HTML Structure
To create a configurator, you need two things: a Viewport container for the 3D scene and a UI Layer for user inputs.
<div id="canvas-container" style="width: 100%; height: 500px; position: relative">
<canvas id="canvas"></canvas>
</div>
<div id="controls" style="padding: 20px; border: 1px solid #eee; margin-top: 10px">
<div style="margin-bottom: 15px">
<label>Model Width: </label>
<input type="range" id="width-slider" />
</div>
<button id="download-btn">Download Object</button>
<div id="output-container" style="margin-top: 20px">
<h3>Output:</h3>
<pre
id="output-content"
style="
height: 200px;
overflow: scroll;
"
></pre>
</div>
</div>
2. The Initialization Script
We initialize the viewer and store the session object. We then pass this session to a dedicated function that handles our UI logic. Further details and options for sessions and viewports can be found on the corresponding linked pages.
To adjust this code for your own model, you first need to retrieve the ticket and adjust the embedding settings. For now you can also just try it out with the provided ticket of the example model.
import { createSession, createViewport, ISessionApi } from "@shapediver/viewer";
const init = async () => {
// create the viewport and connect it to the canvas
const viewport = await createViewport({
canvas: document.getElementById("canvas") as HTMLCanvasElement,
id: "myViewport",
});
// create a session with your ticket and modelViewUrl
// in this case we use one of our example models
const session = await createSession({
ticket: "50eb2a26ddaa432ca18288b8a120ef194fa35bb813e4f43ae89d657991a865f9deaa20a1c840e47cdf6dbc019cd16ae15a9a6b3a7d91722455299d6bd29b1f26b3ff3b7adaac1df3d50f3ba4d010a560180dff8f745c946dadb41167a3431e223d69b32743f167-5b9465f92a0cf9c235b8ea315aab0cd5",
modelViewUrl: "https://sdr7euc1.eu-central-1.shapediver.com",
id: "mySession",
});
// once the session is ready, bind it to our UI elements
bindParameterUI(session);
bindExportUI(session);
};
init();
3. Connecting UI to API
This is the core of your application. We use event listeners to capture user input and translate those actions into API calls.
A. Updating Parameters
When the slider moves, we use update the parameter object and call session.customize to send a customization request.
const bindParameterUI = (session: ISessionApi) => {
const slider = document.getElementById("width-slider") as HTMLInputElement;
// fetch the parameter object by it's name that was given in GrassHopper
const parameterApi = session.getParameterByName("Length")[0];
// adjust the slider according to the parameter definition
slider.min = parameterApi.min + "";
slider.max = parameterApi.max + "";
slider.value = parameterApi.value + "";
slider.step = "1";
slider.addEventListener("change", async () => {
// Update the parameter and customize the session
const newValue = parseInt(slider.value);
parameterApi.value = newValue;
await session.customize();
console.log(`Updated width to: ${newValue}`);
});
};
Note: In this example we show how a specific number parameter is used, here is an advanced example that handles all parameter types.
B. Requesting Exports
Unlike parameters, Exports are triggered by a single action (like a button click) to generate a file on the server.
const bindExportUI = (session: ISessionApi) => {
const downloadBtn = document.getElementById(
"download-btn",
) as HTMLButtonElement;
// Fetch the export object by its name that was given in GrassHopper
const exportApi = session.getExportByName("Obj Export")[0];
downloadBtn.addEventListener("click", async () => {
// 1. Request the computation and file generation
const result = await exportApi.request();
// 2. Handle the result (the download link)
const fileUrl = result.content![0].href;
window.open(fileUrl, "_blank");
});
};
C. Reading Outputs
Outputs can be used to retrieve data directly after a computation. This can be additional information like prices or information about the model. In the
const bindOutputUI = (session: ISessionApi) => {
// Fetch the output parameter by its name that was given in GrassHopper
const outputApi = session.getOutputByName("Image Plane Box")[0];
// define a callback function that will be called whenever the output data is updated
const updateCallback = (newNode?: ITreeNode) => {
const outputApiData = newNode?.data.find(
(d) => d instanceof OutputApiData,
) as OutputApiData;
// get the new content
const content = outputApiData!.api.content![0].data;
// display the content as plain text
const pre = document.getElementById("output-content") as HTMLPreElement;
pre.textContent = JSON.stringify(content, null, 2);
};
// display initial content
updateCallback(outputApi.node);
// set callback for future updates
outputApi.updateCallback = updateCallback;
};
🚀 Live Implementation
The following embed shows this exact code in a live environment. Adjust the slider to see the real-time parameter update, and click the button to see how the export request is processed.
Hint: Clicking Click to Run opens a standalone environment with a live preview and direct links to the CodeSandbox project and GitHub source code.
Next Steps
Now that you've built your first interactive app, dive deeper into the core components:
Read about the structure in the Scene Tree.
Customize the Scene Appearance.
Define ways for users to interact with the model.