Convert Rhino Mesh to Three.js BufferGeometry

I am attempting to convert a rhino mesh in Grasshopper python to a BufferGeometry that will be read in Three.js/WebGL. Three.js does have converters available to go from .obj to three.js, but for my workflow, I need these files to be outputted from GH as a JSON without saving it as a .obj first.

from System import Guid
import rhinoscriptsyntax as rs
import json

# initiate lists to fill
geometryList = []
childrenList = []
# loop through input meshes
for i, mesh in enumerate(meshes):
    # get UUID
    geometry_guid = str(Guid.NewGuid())
    # JSON BufferGeometry Structure
    geometryDict = {
        "uuid": geometry_guid,
        "type": "BufferGeometry",
        "data": {
            "attributes":{
              "position":{
                "itemSize": 3,
                "type": "Float32Array",
                "array":[],
                "normalized": False
              },
              "normal":{
                "itemSize": 3,
                "type": "Float32Array",
                "array":[],
                "normalized": False
              }
            }
        }
    }
    # children Dict
    # values such as name, visible are input values from GH
    childrenDict = {
        "uuid": str(Guid.NewGuid()),
        "type": "Mesh",
        "name": name,
        "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
        "geometry": geometry_guid,
        "material": material.d['uuid'],
        "visible":visible,
        "castShadow": cast_shadow,
        "receiveShadow": receive_shadow,
        "userData": {}
    }
    # start index
    vertexIndex = 0
    # vertex array creation
    vertexArray = [None] * (len(rs.MeshVertices(mesh)) *3)
    # loop through vertices and append to vertex array
    for vertex in rs.MeshVertices(mesh):

        vertexArray[vertexIndex*3] = vertex[0]
        vertexArray[vertexIndex*3+1] = vertex[1]
        vertexArray[vertexIndex*3+2] = vertex[2]

        vertexIndex+=1
    # add to geometry dictionary
    geometryDict['data']['attributes']['position']['array'] = vertexArray

    # loop through faces
    faceVertices = []
    for face in rs.MeshFaceVertices(mesh):

        faceVertices.append(face[0])
        faceVertices.append(face[1])
        faceVertices.append(face[2])

        if face[2] != face[3]:
            faceVertices.append(face[2])
            faceVertices.append(face[3])
            faceVertices.append(face[0])

    # normal index
    normalIndex = 0
    # normal array creation
    normalArray = [None] * (len(rs.MeshFaceNormals(mesh)) *3)

    for normal in rs.MeshFaceNormals(mesh):

        normalArray[normalIndex*3] = normal[0]
        normalArray[normalIndex*3+1] = normal[1]
        normalArray[normalIndex*3+2] = normal[1]

        normalIndex+=1

    # add normal array to geometry dictionary
    geometryDict['data']['attributes']['normal']['array'] = normalArray

    geometryList.append(geometryDict)
    childrenList.append(childrenDict)

# these meshes are later added to the parent JSON Structure
class GhPythonDictionary(object):
    def __init__(self, pythonDict=None):
        if pythonDict:
            self.d = pythonDict
        else:
            self.d = {
                "material": material.d,
                "geometries": geometryList,
                "children": childrenList
            }
    def ToString(self):
        return 'GhPythonDictionary object'

mesh_object = GhPythonDictionary()

# master dictionary. all scene data goes in here

featureDict = {
    "metadata": {
        "version": 4.5,
        "type": "Object",
        "generator": "Object3D.toJSON",
    },
    "geometries":[],
    "materials":[],
    "object":{
        "uuid": "378FAA8D-0888-4249-8701-92D1C1F37C51",
        "type": "Group",
        "name": file_name,
        "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
        "children": []
    }
}

for i in range(len(mesh_objects)):

    for j in range(len(mesh_objects[i].d['geometries'])):
        featureDict['geometries'].append(mesh_objects[i].d['geometries'][j])

    featureDict['materials'].append(mesh_objects[i].d['material'])

    for j in range(len(mesh_objects[i].d['children'])):
        featureDict['object']['children'].append(mesh_objects[i].d['children'][j])

# file path as a string input from GH
file_path = path + '\\' + file_name + '.json'

# write file to folder path
with open(file_path, 'wb') as outfile:
    json.dump(featureDict, outfile)

A little off topic, but figured worth mentioning.
The latest version of rhino3dm.js supports directly creating threejs BufferGeometry json from Mesh instances.

Here is a basic sample
https://mcneel.github.io/rhino3dm/javascript/samples/viewer/01_basic/index.html

1 Like

Hi,

Thanks for the guidance - in this case would you save out your geometries as an .obj and materials as an .mtl then combine them before render?

are you familiar with the spectacles exporter?

In the sample that I posted, I just saved a 3dm with meshes and then directly read the 3dm file instance in javascript. I then called a function on each mesh to get the BufferGeometry json representation of that mesh.

@Rickson, the Spectacles exporter is now out of date. The objects exported are Geometries which are now deprecated as of Three.js r.99.

@stevebaer - Thank you for the response - in this sample you are taking the geometries contained in the 3dm file and converting them in real time then adding them to the Three.js scene. I have attached a diagram that describes this paradigm that I see when working in an iterative design model. This idea that you are generating 1000s of models in Rhino/Grasshopper and wish to visualize them in the web using three.js.

The conversion from mesh geometries to WebGl readable format has to occur somewhere in the pipeline either when saving out the geometries from Rhino/GH as .obj or 3dm in which you would need to process the files again to convert or finding a way to save them out directly in the three.js format. Three.js seems to be moving toward glTF and moving away from .json. Khronos group released a converter from obj to glTF which still assumes you want to save out the geometries as .obj+.mtl first then convert. What are the benefits of this method as opposed to converting geometries from Grasshopper into a readable three.js format?

1 Like

gltf is better than json for when you need to send data over the network as it is compressed. In the case of rhino3dm, you already have the geometry in the browser process and converting that geometry to json is simply performed in the browser and with no network communication required.

These are all just formats for transferring data. Once the data gets to three.js, it needs to convert that information into arrays of floats and integers for feeding to the GPU.

This is what we have available now.

1 Like