Applying Materials to Specific Objects in Speckle Viewer with Custom UserStrings - Need Guidance

Hi @alex ,thank you for the reference! I’m currently trying to apply materials to specific objects in speckle. The workflow is:

  1. Upload objects from rhino. These objects have custom userString obj_id attached to them. The objects are structured inside a layer named ‘regions’.
  2. I have constructed a dictionary of colors for each obj_id, this is a color representation of a value (E.g. footprint, temperature etc)

It seems that the speckle API 2.0 has changed quite a bit from the example in this thread. Could you please provide some guidance on how I can achieve this?

This is how my current function looks like:

  function applyMaterial() {
    if (!speckleViewer) return;
    // Walking the tree and finding 'regions' objects
    speckleViewer.getWorldTree().walk((node: TreeNode) => {

      if (node.parent?.model?.raw?.name == 'regions') {
        const renderTree = speckleViewer.getWorldTree().getRenderTree();
        const rvs = renderTree.getRenderViewsForNode(node);
        if (node.model.raw.userStrings) {
          const material = new THREE.MeshStandardMaterial({
            color: new THREE.Color(colors[node.model.raw.userStrings.ObjectId]), // Hexadecimal color
            opacity: 1,
            transparent: true, // Set to true if opacity < 1
            roughness: 1,
            metalness: 0,
          });
          speckleViewer.getRenderer().setMaterial(rvs, material);
        }
      }

      return true;
    });

The issue is, it seems that the renderView returned is the same for all the regions node. Meaning that it just changed the color of the whole objects inside the regions layer. I’m not sure how to fix this.
I also need the functionality to resetMaterial to what it was previously.

Thanks in advance for any help you could provide!

Hi @NaoB

I think it would be easier to approach the node search differently. Instead of walking through the whole tree, you could get the layer node like so

const layerNode = speckleViewer.getWorldTree().findAll((node: TreeNode) => {
  return node.model.raw.name === 'regions'
})[0]

Then get all of it’s render view children nodes

/** We take the render views node, not the render views themselves*/
const rvNodes: TreeNode[] = speckleViewer.getWorldTree().getRenderTree().getRenderViewNodesForNode(layerNode )

Then loop through them and apply materials. Note that I used SpeckleStandardMaterial which is an extension of MeshStandardMaterial with extra speckle dust.

rvNodes.forEach((node: TreeNode) => {
  if (node.model.raw.userStrings) {
    const material = new SpeckleStandardMaterial({
        color: new THREE.Color(colors[node.model.raw.userStrings.ObjectId]),
        opacity: 1,
        transparent: true, // Set to true if opacity < 1
        roughness: 1,
        metalness: 0,
      })
    speckleViewer.getRenderer().setMaterial([node.model.renderView], material);
  }
})

You might want to use another overload of setMaterial which takes the material in the form of material data like

const customMaterialData = {
    id: 'test',
    color: 0x880044,
    emissive: 0x0,
    opacity: 0.5,
    roughness: 1,
    metalness: 0,
    vertexColors: false,
};
speckleViewer.getRenderer().setMaterial(<rv_array>, customMaterialData );

For cases where you do not know the exact primitive type of the object (mesh, line, point) because this way the viewer will automatically build appropriate material types based on the object’s primitive type. Here’s a live example using it

Please note that I did not run this code, I just wrote it so I might have made typos and such. In any case, you can find the updated viewer API reference here

Let us know if you need more help.

Cheers

1 Like

This works great. Thank you! I modified the code a bit. For some reason the userStrings is stored in the parent node of the rvNode. Though the model itself is in the rvNode.

The colors are applied correctly now :slight_smile:

3 Likes