jupyter-widgets / pythreejs

A Jupyter - Three.js bridge
https://pythreejs.readthedocs.io
Other
951 stars 188 forks source link

Exporting scene to three.js JSON #157

Closed mkhorton closed 2 years ago

mkhorton commented 6 years ago

I'm interested in a Pythonic way of generating three.js scenes into three.js's native JSON format for later rendering on the web, outside of a notebook environment.

What work would be required to add this sort of functionality to pythreejs?

mkhorton commented 6 years ago

Commenting here in case anyone else finds this thread and is looking for something similar:

https://github.com/jzitelli/three.py exists and seems to work, though it hasn't been updated in a while -- not sure if this is the older three.js JSON format, looks similar to the current format though.

vidartf commented 6 years ago

If we assume that it is executed in a notebook during scene creation, we can simply use three's own toJSON() function, and save the output. There are two possible ways to do this:

  1. Save it on the client's computer by triggering a download of a JSON file in the browser.
  2. Save it on the server by having the kernel write the JSON to a file.

To achieve 1., we would need to add a button or message that triggers the toJSON() function on the relevant three object (the scene in this case). See e.g. how the Three editor does it.

For 2., this is currently possible, just not very elegant:

import json

def export_scene(scene, filename):
    old_ret_val = scene._on_ret_val
    def on_data(method, data):
        if method != 'toJSON':
            return
        scene._on_ret_val = old_ret_val
        json.dump(data, filename)
    scene._on_ret_val = on_data
    scene.exec_three_obj_method('toJSON')

The logic behind 2. could be cleaned up if there was an signal/slot event logic to the execute method (i.e. no overwriting/monkey-patching). That would be a pure python thing, so anyone proficient enough in Python could contribute it.

vidartf commented 6 years ago

If we assume that it is executed in a notebook during scene creation.

If this assumption doesn't hold, we could try to have a pure Python implementation based on the widgets / autogen code, and current serializers. I'm not entirely sure how much work this would be, either to develop or to maintain.

mkhorton commented 6 years ago

Hi @vidartf , thanks for the reply ! I didn't realize the toJSON() function was available, so that's good to know.

Ideally, it'd be executed outside the notebook -- I'd like to generate of the order of 100k scene JSONs so a pure Python approach would be great, though I imagine I can write a script to do this via a notebook. I'll try the method 2 you suggested to start with and see how that works.

vidartf commented 6 years ago

If you want something pure Python, you could try something like this (might not be a perfect fit, but it would be interesting to see where it differs, and by how much):

import json
from ipywidgets.embed import dependency_state

def export_scene(scene, filename):
    state = dependency_state(scene)
    json.dump(state, filename)
mkhorton commented 6 years ago

Hmm, this is interesting. It definitely seems like the output from the dependency_state could be converted into the JSON format.

As an example,

ball = Mesh(geometry=SphereGeometry(radius=1), 
            material=LambertMaterial(color='red'),
            position=[2, 1, 0])

c = PerspectiveCamera(position=[0, 5, 5], up=[0, 1, 0],
                      children=[DirectionalLight(color='white', position=[3, 5, 1], intensity=0.5)])

scene = Scene(children=[ball, c, AmbientLight(color='#777777')])

gives:

{'10b0a2c543954ec99628b3ce0bcd84f7': {'model_module': 'jupyter-threejs',
                                      'model_module_version': '0.3.0-alpha.0',
                                      'model_name': 'MeshModel',
                                      'state': {'children': [],
                                                'geometry': 'IPY_MODEL_c7d34028e78a4d2dacbfad3eb7ebb752',
                                                'material': 'IPY_MODEL_4654ccdfb8bb49049746c729235fcb94',
                                                'position': [2.0, 1.0, 0.0],
                                                'quaternion': [],
                                                'scale': [1.0, 1.0, 1.0],
                                                'up': [0.0, 1.0, 0.0]}},
 '402cf0b394cb45389981f72c82e05343': {'model_module': 'jupyter-threejs',
                                      'model_module_version': '0.3.0-alpha.0',
                                      'model_name': 'PerspectiveCameraModel',
                                      'state': {'children': ['IPY_MODEL_9cd3e202ba01477cb51db5d14e66d005'],
                                                'position': [0.0, 5.0, 5.0],
                                                'quaternion': [],
                                                'scale': [1.0, 1.0, 1.0],
                                                'up': [0.0, 1.0, 0.0]}},
 '4654ccdfb8bb49049746c729235fcb94': {'model_module': 'jupyter-threejs',
                                      'model_module_version': '0.3.0-alpha.0',
                                      'model_name': 'LambertMaterialModel',
                                      'state': {'color': 'red',
                                                'envMap': None,
                                                'lightMap': None,
                                                'map': None,
                                                'specularMap': None}},
 '5503490ffef3458eb874d6bfc8e21dac': {'model_module': 'jupyter-threejs',
                                      'model_module_version': '0.3.0-alpha.0',
                                      'model_name': 'AmbientLightModel',
                                      'state': {'children': [],
                                                'color': '#777777',
                                                'position': [0.0, 0.0, 0.0],
                                                'quaternion': [],
                                                'scale': [1.0, 1.0, 1.0],
                                                'up': [0.0, 1.0, 0.0]}},
 '57f1f9c6670a4831b12111312d690fea': {'model_module': 'jupyter-threejs',
                                      'model_module_version': '0.3.0-alpha.0',
                                      'model_name': 'SceneModel',
                                      'state': {'children': ['IPY_MODEL_10b0a2c543954ec99628b3ce0bcd84f7',
                                                             'IPY_MODEL_402cf0b394cb45389981f72c82e05343',
                                                             'IPY_MODEL_5503490ffef3458eb874d6bfc8e21dac'],
                                                'position': [0.0, 0.0, 0.0],
                                                'quaternion': [],
                                                'scale': [1.0, 1.0, 1.0],
                                                'up': [0.0, 1.0, 0.0]}},
 '9cd3e202ba01477cb51db5d14e66d005': {'model_module': 'jupyter-threejs',
                                      'model_module_version': '0.3.0-alpha.0',
                                      'model_name': 'DirectionalLightModel',
                                      'state': {'children': [],
                                                'intensity': 0.5,
                                                'position': [3.0, 5.0, 1.0],
                                                'quaternion': [],
                                                'scale': [1.0, 1.0, 1.0],
                                                'up': [0.0, 1.0, 0.0]}},
 'c7d34028e78a4d2dacbfad3eb7ebb752': {'model_module': 'jupyter-threejs',
                                      'model_module_version': '0.3.0-alpha.0',
                                      'model_name': 'SphereGeometryModel',
                                      'state': {}}}

Loosely, it looks like:

mkhorton commented 6 years ago

So maybe something along the lines of ...

scene_json = []
for k, v in dependency_state(scene).items():
     state = v['state']
     if 'children' in state:
         state['children'] = [child.replace('IPY_MODEL_', '') for child in state['children']]
     scene_json.append({
         'uuid': k,
         'type': v['model_name'],
         'data': state
     })

I don't think this would work as-is, but it may be something along these lines.

vidartf commented 2 years ago

Closing as stale.