Tutorial contents
In this tutorial, we show how to load a ShapeDiver model and read its parameter definitions, in order to automatically generate a HTML user interface and connect it to the model.
We will focus on a barebone, fully functional connection, without doing any styling or custom layout: this part is up to the designers and web developers building applications around ShapeDiver.
Before starting
Make sure you know how to embed a ShapeDiver model and that you are familiar with the ShapeDiver parameter definitions.
Demo model
For this tutorial, we will be working with this example model. It contains most of the parameter types available in ShapeDiver, and one export asset which we will also dynamically add to the user interface. This model can be freely embedded anywhere for testing. Simply use the ticket on the model page.
Simple HTML page
To keep things simple, our HTML page only contains two div elements, one for the viewer, and one that will contain the generated inputs:
<body>
<!-- ShapeDiver Viewer Main Container -->
<div id='sdv-container' style="width:100%;height:300px;">
</div>
<div id="parameters">
</div>
</body>
We'll use the id of the second container (parameters) to find it and add children to it from the script.
Read the model parameters
Before reading the parameter definitions, you need to make sure the scene has finished loading in the viewer. One way to do that is to set an event listener for the VISIBILITY_ON event of the scene interface, which is fired when the loading of the scene is complete:
api.scene.addEventListener(api.scene.EVENTTYPE.VISIBILITY_ON, function() {
// now it's safe to read the model parameters
var parameters = api.parameters.get();
});
This event is fired every time new geometry is loaded in the scene. Since we only want to create the inputs once, we'll add a test for the first time the scene is loaded (viewerInit). After that, we can loop through the parameters and start generating elements for each of them in the document:
var viewerInit = false;
api.scene.addEventListener(api.scene.EVENTTYPE.VISIBILITY_ON, function() {
// now it's safe to read the model parameters
var parameters = api.parameters.get();
if (!viewerInit) {
// we store the location of the div that will contain all the inputs
var globalDiv = document.getElementById("parameters");
for (let i=0; i<parameters.data.length; i++) {
let param = parameters.data[i];
// do something with the parameter definition
}
}
viewerInit = true;
});
Ordering the parameters
ShapeDiver parameters include an order attribute, which describes the order chosen by the designer in the ShapeDiver control widget. This order is in general different from the API response contained in our parameters variable. We can add some logic to the code in order to re-organize the parameter div elements, using the javascript sort() function:
parameters.data.sort(function(a, b) {
return a.order - b.order;
});
This call sorts the array by comparing the value of the order property in its elements. Now we are ready to loop through the individual parameters.
Generating parameter inputs
Now is the time to map the ShapeDiver parameters to user interface elements. We keep it simple in our case, and map all types to standard HTML inputs:
ShapeDiver parameter type | HTML input type |
Float, Int, Even, Odd | range |
String | text |
Bool | checkbox |
Color | color |
Stringlists are mapped to dropdown menus using a HTML select.
For each input, we create a <div> element containing it. The <input> element gets an id corresponding to the ShapeDiver parameter id. The rest of the attributes are specific to each parameter type. We also create a <label> for each input which takes the name of the ShapeDiver parameter.
let param = parameters.data[i];
let paramDiv = document.createElement("div");
let label = document.createElement("label");
label.setAttribute("for", param.id);
label.innerHTML = param.name;
If we want to take into account the hidden attribute of the parameter definition, we can also add the following line in the loop:
if (param.hidden) paramDiv.setAttribute("hidden","");
Number types
It's simpler to handle all number types together, since they are all range inputs. The only difference between them is the step attribute. Its value is 1 for Int, 2 for Even and Odd numbers, and for Float numbers it depends on the precision of the floating number. This precision is given in the parameter type by the attribute decimalplaces: if decimalplaces is 1, the step is 0.1, then 0.01 if decimalplaces is 2, and so on... The quick way to convert one to the other is to call:
step = 1 / Math.pow(10, decimalplaces);
For the rest, there is a one-to-one mapping for min, max and value attributes. We end up with the following piece of code:
if (param.type == "Int" || param.type == "Float" || param.type == "Even" || param.type == "Odd") {
paramInput = document.createElement("input");
paramInput.setAttribute("id", param.id);
paramInput.setAttribute("type", "range");
paramInput.setAttribute("min", param.min);
paramInput.setAttribute("max", param.max);
paramInput.setAttribute("value", param.value);
if (param.type == "Int") paramInput.setAttribute("step", 1);
else if (param.type == "Even" || param.type == "Odd") paramInput.setAttribute("step", 2);
else paramInput.setAttribute("step", 1 / Math.pow(10, param.decimalplaces));
}
Once the numbers are done, the rest of the input types are a piece of cake. Find the details for each of them in the full code at the end of this tutorial. Let's still have a look at the dropdown menus, which work a little differently.
Dropdown menus for Stringlists
For dropdown menus, we use HTML <select> elements, with <option> children for each of the values in the Stringlist. Therefore we have to loop through the choices attribute of the ShapeDiver parameter. Remember that the actual value of Stringlist types is the index in the choices array. We mirror this value in the value attribute of each option, but set the name and content of each option element to the corresponding string in the choices array. Finally, in order to initialize the select element to its default value in the 3D model, we test which option corresponds to the current value of the parameter and add the selected attribute to it.
if (param.type == "StringList") {
paramInput = document.createElement("select");
paramInput.setAttribute("id", param.id);
for (let j = 0; j < param.choices.length; j++) {
let option = document.createElement("option");
option.setAttribute("value", j);
option.setAttribute("name", param.choices[j]);
option.innerHTML = param.choices[j];
if (param.value == j) option.setAttribute("selected", "");
paramInput.appendChild(option);
}
}
Include the inputs in the DOM structure
At the end of each iteration, the element we just generated needs to be added to the parameters <div> of the HTML document.
globalDiv.appendChild(paramDiv);
Connecting the HTML inputs to the 3D model
After generating all the inputs according to the parameter definition, you get a full front-end user interface that accurately represent what can be customized in the model. However, the controls are not yet hooked to any API request for the model to be updated according to their changes.
To do so, let's go back to the part where the inputs are generated. For each of them, we need to add an event listener detecting when their value is changed, so that we can send this value to the 3D model. To this effect, we use the onchange HTML event. In the callback to the event, we send a simple API request to update the value of the corresponding parameter. Each parameter is identified in the call by its id. For most types, it looks like this:
paramInput.onchange = function() {
api.parameters.updateAsync({
id: param.id,
value: this.value
});
};
The only difference concerns boolean parameters. Checkboxes don't have a value attribute, they only have a checked attribute indicating whether the box is selected or not. Therefore, we use this.checked instead of this.value in the callback.
Generating export buttons
In the ShapeDiver control widget, we also include download buttons corresponding to all the export assets that are contained in the model. In the current test model, there is one export available.
In order to create the export buttons, we run a second loop, similar to the first one, through the exports of the model. The exports are returned by the following call:
api.exports.get();
The loop looks very similar to the first one, except we create <input> elements with a type attribute set to "button". As for connecting to the export request, we use here the onclick event of the button and call the requestAsync() function from the API.
let exportAsset = exports.data[i];
let exportDiv = document.createElement("div");
let exportInput = document.createElement("input");
exportInput.setAttribute("id", exportAsset.id);
exportInput.setAttribute("type", "button");
exportInput.setAttribute("name", exportAsset.name);
// for exports, the value attribute is the one defining the displayed text
exportInput.setAttribute("value", exportAsset.name);
exportInput.onclick = function() {
api.exports.requestAsync({
id: this.id
}).then(
function(response) {
let link = response.data.content[0].href;
// the following line triggers the download of the file
window.location = link;
}
);
};
Full code of the tutorial
Find below the full code of the tutorial, which you can open and modify in JSFiddle. Note that you can replace the model ticket with any other publicly available ticket from ShapeDiver, as the script is not model specific.
Comments
0 comments
Article is closed for comments.