SD Viewer: Retrieving three.js-representations

Hello everyone,

I have a few questions as I start to explore the ShapeDiver Viewer API and attempt some three.js applications therewith. Let me know if I should break them up or otherwise reformat/repost.

  1. What is the difference between adding three.js objects to the viewport via viewport.threeJsCoreObjects.scene.add(three.jsObject);
    and to the scene tree via
    const threejsnode = new SDV.TreeNode();
    threejsnode.data.push(new SDV.ThreejsData(sceneElements));
    sceneElements.add(threejsObject)
    ...
    SDV.sceneTree.root.addChild(threejsnode);
    SDV.sceneTree.root.updateVersion();
    viewport.update();

assuming threejsObject is some instantiated THREE.Object3D instance, and viewport comes from a SDV.createViewport call?

  1. How does one retrieve the threejs representations of the grasshopper-derived objects from the viewer? (i.e. if I know the name attribute, or could sort by scene tree data id, how to interact with the threejs representation directly)

In accessing three.js data all example callbacks go through all elements of the scene tree, and between all the shared controllers for what I think are the same objects, I’m not sure what to compare to pick out an element.

  1. These are both motivated by a desire to raycast using viewport.threeJsCoreObjects to click to create & manipulate threejs geometry using an intersection with grasshopper-derived scene geometry. Is it possible to somehow pass a reference to the viewport, then have self-contained objects which interact therewith? (e.g. a dot which can be dragged around atop .gh geometry)

Thanks!
Max

Hello Max @OTB,

Thank you for your questions! I also adjusted the documentation regarding this issue with the information below.

I’ll start with some background around how our scene tree works and how three.js objects are created internally. Our scene tree is built out of nodes. Each node can have 0-n child nodes which therefore create the tree structure of the scene tree. Each node can have 0-n data items which represent data that can be either visualized or be used in other ways. In these data items you find the geometry, materials, animations and much more. Whenever a node of the scene tree is created or updated (updateVersion function), the node and it’s descendants are processed and three.js Object3D are created per node and per data item. For the data items, the data of that item is processed and is loaded as the corresponding three.js type (for geometry, a Mesh is created for example). For both, nodes and data items, the created three.js objects can be found in the threeJsObject property with the key in that dictionary being the id of the viewport where you want to access the object from. Please keep in mind, that the scene that is exposed in viewport.threeJsCoreObjects.scene is the same scene that is used when the scene tree structure is converted into three.js objects. This also means that three.js objects further down the scene tree structure are added/removed whenever there are customization calls.

Although this might already clear up a few of your questions, let me now go into detail for them:

  1. While we expose the three.js scene with viewport.threeJsCoreObjects.scene, we recommend to use the approach with the ThreeJsData to add three.js objects. With this approach you can ensure that all objects (your own three.js objects, and the ones we created internally) are added at the same time (for example, after a customization call). Additionally, transformations that are applied in the hierarchy can then be respected when you add your objects further down the scene tree.
  2. If you already know the name of the nodes that are applied in grasshopper, you can search for the nodes in the scene tree via the helper functions getNodesByName or getNodesByNameWithRegex. Once you retrieved the node, you can find the corresponding three.js objects in the threeJsObject property.
  3. I’m not exactly sure what you mean by “pass a reference to the viewport” so I’ll keep this answer a bit general (maybe this was already answered in the beginning of the answer). In our viewer you can use updateCallbacks, either on the session or output level, that can be used to add/manipulate/remove geometry whenever an updated happened. This can then also be used to add custom three.js objects for interactions.

Let me know if you have further questions!
Cheers, Michael

1 Like

Thanks Micheal!

That clarifies some questions on what is updated when, and how those updates are or aren’t preserved across client/server interactions.

I am still struggling a bit to work with actual node geometry data however.
As I understand it, I first retrieve the sceneTree node, then the associated threeJsObject specific to the viewport. I set ball as a function in imitation of the property of sceneTree:

const ball = () => SDV.sceneTree.root.getNodesByName('ball')[0].threeJsObject[viewport.id];

I can now manipulate ball() via THREE.Object3D methods and update the viewport to observe the changes.

A few questions arise from the resulting behavior:

  1. How does SDV (from import * as SDV from "@shapediver/viewer";) know of the (session-specific?) sceneTree?
  2. Why does the original .gh geometry remain unchanged? That is, after ball().translateZ(4), the associated threeJsObject moves, but the another sphere remains (with a different texture?).
  3. Is it possible to access the .geometry of ball()? Or other, non-generic Object3D threejs properties (i.e. if it’s a Mesh in Rhino, how do I get the associated THREE.Mesh properties in the viewer?)
  4. Why are node threeJsObject children infinitely recursive? That is, they always seem to have children with identical properties save for their uuid.

For reference, I am using this simple grasshopper script to test and carrying on with the conventions expressed in the documentation sandboxes (i.e. all ts in IIFE, viewport, session declared, SDV imported).
ShapeDiver_ThreeJS_test.gh (8.8 KB)
I’ll be happy to attach my little index.ts file, but I was warned by the forum that’s not allowed?
Here’s a .gif if it helps instead:
threejs_pt_manipulation

I’m sure I’ll ever have linger questions which take a while to specify, but I hope these are clear!
Cheers,
Max

Hello @OTB !

Thank you for your questions!

  1. The scene tree is built in a ways that each session has a node under the root of the scene tree. So all session geometry is in the scene tree hierarchy. You can get the specific node of a session in the SessionApi and the node of a specific output in the OutputApi. All of these nodes are somewhere in the scene tree, this is just a more direct way to access them.
  2. The reason why there is a second sphere is an issue on the Grasshopper side. You have the preview on for a lot of the components in your Grasshopper script, which will result in geometry in the Viewer. You can read more about that here.
  3. Yes, you can access the three.js Mesh. I created an example for you, where I show how to do that.
  4. I was not able to reproduce this issue, can you please provide an example here?

You can also create a CodeSandBox (like what I just linked above) to create a little example, this helps a lot to find issues that might not be that obvious just from text and a gif.

All the best,
Michael

1 Like

Thanks @MajorMeerkatThe3rd,

I corrected the overlapping previews, but now I’m somewhat stumped while making use of the attributes in the ShapeDiver output. I read through the documentation and create tests, but somehow still cannot recreate the behavior in your example.
Would it be possible to share the grasshopper file associated with the example you created, or one similiarly illustrative?

I forked your example to use my .gh file, and don’t really understand what corresponds to the “Door” parameter in the original .gh file. Overall, I’m approaching this from a view towards threejs interactivity; that is, I want to perform a bunch of threejs interactions using the ShapeDiver geometry, then pass some data therefrom either in a subsequent page load, or potentially as a parameter for customization.

This could be complicated by customization during the threejs interaction; perhaps it’s possible to pass the parameters of a threejs interaction into empty attributes so they can be remembered (or just set in browser/Sd session memory…), but for now I’m just looking to seperately interact with the isolated threejs geometry.

This below image, the result of expanding the below log call in the developer tools console is what I refer to as infinitely recursive children (as I could continue to expand the objects and their children)

const ball = () => SDV.sceneTree.root.getNodesByName('ball')[0].threeJsObject[viewport.id];
console.log("ball:", ball())


Associated glTF-output based .gh file: attributes-glTF-output.gh (12.9 KB)

Cheers,
Max