New viewer version
This article and the other ones in the Viewer category of this website concern the version 2 of the ShapeDiver viewer API. We have since released a version 3 of the viewer API which is documented in the new help center. All the information below is still relevant if you are using version 2 for legacy purposes. Keep in mind that by the end of 2022, maintenance for the version 2 will stop and no more updates or patches will be rolled out to this version. Consider reading our migration guide to version 3.
Presentation
The goal of this tutorial is to demonstrate the API selection and dragging features and how they can be used to interact with a ShapeDiver model. The final result will allow users to pick points by selecting a location on an object and drag the existing points (represented by spheres) in the 3D model before sending them back to the model which then projects the new point locations back on the object. You can already play with the result by scrolling to the interactive example at the bottom of this page.
Setup of the ShapeDiver model
Download the model
Download the Grasshopper model for this tutorial here. This model is already uploaded to ShapeDiver as a public model that can be seen on the platform. Developers can use this model's ticket to experiment with the API without uploading their own model.
Model description
The model contains an object (a human head) and a text parameter defining the location of points in the scene as a JSON object. The definition then converts this JSON object into actual points in the scene. The points are projected onto the closest point on the object (the head). Small red spheres are also created to indicate the position of the projected points. The spheres will also be used to allow users to drag the points in the online model.
In the model, the projected points can then be used for all kinds of applications (fitting of accessories, measurement of characteristics, etc...). In this example model, we limit the number of input points (three points maximum) and we just create blue lines and a plane between them.
Input points
The Grasshopper model has a single parameter: a JSON object containing the positions of the user-picked points. It is included in the model using a ShapeDiverTextInput component containing the JSON string structured as follows:
{
points: [ // array containing the points
[0,0,0], // points represented by arrays of three numbers
[0,1,0],
[0,0,1]
]
}
By default, the point array is empty (no points picked by the user):
{points:[]}
This input parameter of the model is simply called Points. It can be read and updated using the usual API functions:
// read the current parameter value
let param = api.parameters.get({name: 'Points'}).data[0].value;
// convert the string to a JSON object and extract the point array
let points = JSON.parse(param).points;
// update the value of one of the points
points[0] = [0,0,0];
// request a parameter update with the new values
api.parameters.updateAsync({
name: "Points",
value: JSON.stringify({
'points': points
})
});
Objects in the scene
The scene contains a geometry asset containing the head object, simply called Head. We will need to make the head clickable for adding points.
Small spheres are also created at the location of the projected points. The spheres are used in the model as handles for users to drag the points and change the picked location. There are three distinct geometry assets for the spheres, since we limit the input to three points. They are called Sphere_0, Sphere_1 and Sphere_2, corresponding to the points in the input array (points[0], points[1] and points[2]). We will need to make these spheres draggable for moving points.
The scene contains other objects, but they are not relevant for the API interactions.
Communicating with the model
Using the API, we will need to do the following operations.
Make the head selectable by clicking
We want the head to be selectable by clicking. First, we need to create an interaction group and add it to the scene:
var headGroup = {
id: "head",
selectable: true
};
api.scene.updateInteractionGroups(headGroup);
Then we need to add the head object to this interaction group. We will do so by updating the head asset with the interactionGroup property:
let head = api.scene.get({name: "Head", format: "glb"}, "CommPlugin_1").data[0];
let updateObject = {
id: head.id,
duration: 0,
interactionGroup: headGroup.id
};
api.scene.updatePersistentAsync([updateObject], 'CommPlugin_1');
Adding points by clicking on the head
Finally, we need to set an event listener for clicking events. Whenever the user clicks on the head, we will add a new point to the input parameter and update the model with it. In the callback, the click event contains a property called selectPos with the location on the clicked object.
api.scene.addEventListener(api.scene.EVENTTYPE.SELECT_ON, function(event) {
let pickPoint = event.selectPos;
if (points.length < 3) {
points.push([pickPoint.x, pickPoint.y, pickPoint.z]);
api.parameters.updateAsync({
name: "Points",
value: JSON.stringify({
'points': points
})
});
}
});
By default, objects stay selected until another click happens. In our case, we want the object to be unselected after adding the point, so we do it programmatically:
api.scene.updateSelected(null,api.scene.getSelected());
The above call unselects every object that was previously selected. The first argument is used to programmatically select new objects in the scene, which is why we leave it to null in this case.
Make the spheres draggable
This time, we need to add an interaction group for dragging. We also want the spheres to be hoverable, as a hint to the users. Additionally, when spheres are hovered and dragged, we would like them to be highlighted. We first define the highlighting effect (spheres turn white):
var highlightEffect = {
active: {
name: 'colorHighlight',
options: {
color: [255, 255, 255]
}
}
};
Then we can define the interaction group:
var sphereGroup = {
id: "spheres",
draggable: true,
dragEffect: highlightEffect,
hoverable: true,
hoverEffect: highlightEffect
};
And finally add the spheres to the group:
let assets = api.scene.get(null, "CommPlugin_1");
let updateObjects = [];
for (let assetnum in assets.data) {
let asset = assets.data[assetnum];
let updateObject = {};
if (asset.name.includes("Sphere_")) {
updateObject = {
id: asset.id,
duration: 0,
interactionGroup: sphereGroup.id,
};
}
updateObjects.push(updateObject);
}
api.scene.updatePersistentAsync(updateObjects, 'CommPlugin_1');
Moving points by dragging the spheres
Now that the spheres are in the draggable group, they can already be dragged around freely. However, we still need to listen to the event that tells us when dragging stop (DRAG_END) and send this new position of the sphere to the model with a parameter update.
api.scene.addEventListener(api.scene.EVENTTYPE.DRAG_END, function(event) {
// find which sphere was selected
let sphereID = event.scenePath.split(".")[1]; //gives the ID of the asset
let sphereAsset = api.scene.get({
id: sphereID
}, "CommPlugin_1"); // gives the name of the asset, in our case Sphere_{i}
selectedSphere = sphereAsset.data[0].name.split("_")[1]; // extract the index i
// retrieve the position in the scene when the dragging ended
let newPos = event.dragPosAbs;
// update the point which corresponds to the dragged sphere
points[selectedSphere][0] = newPos.x;
points[selectedSphere][1] = newPos.y;
points[selectedSphere][2] = newPos.z;
// update the input parameter containing the point locatioins
api.parameters.updateAsync({
name: "Points",
value: JSON.stringify({
'points': points
})
});
});
Comments
0 comments
Article is closed for comments.