jupyter-widgets / pythreejs

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

Add 'world' axes widget, that syncs with a scene #295

Closed chrisjsewell closed 1 year ago

chrisjsewell commented 4 years ago

Hey guys, thanks for the great package. I've recently utilized it in my ase-notebook package, to create visualisations of atomic configurations.

As you can see in the tutorial here, I've created a simple GUI that contains an axes widget (top right) that syncs with the main scene on the python-side (you can use this Binder link to see it actually working).

I achieved this by adapting the threejs code here (see threejs.py#L406). As mentioned, the linking is currently done on the python-side, but ideally it would be on the javascript-side. Could you give me any pointer on how/if this could be hooked in to the render loop?

vidartf commented 4 years ago

Maybe this would be easier using the AxesHelper object? Either way, I think you might be able to use ipywidgets.jslink to link the two cameras. I think you would need to link the position, quaternion and zoom traits.

chrisjsewell commented 4 years ago

Thanks for the reply.

Maybe this would be easier using the AxesHelper object?

I chose not to use this, so that I could have more control over properties like the line thickness. But, in essence, there shouldn't be any difference.

I think you would need to link the position, quaternion and zoom traits

Ah, I'd tried this before, but forgot to link the position as well. It's an improvement, but two issues:

Here's a toy example:

import numpy as np
import pythreejs as pjs
import ipywidgets

width = 400
height = 400

CANVAS_WIDTH = 100
CANVAS_HEIGHT = 100
CAM_DISTANCE = 150

scene = pjs.Scene()
camera = pjs.PerspectiveCamera(fov=20, aspect=width / height, near=1, far=10000)
camera.position = (camera.position[0], 150, 700)
controls = pjs.OrbitControls(controlling=camera, screenSpacePanning=True)
renderer = pjs.Renderer(
    scene=scene, camera=camera, controls=[controls], width=width, height=height
)

cube = pjs.Mesh(
    pjs.BoxGeometry(200, 200, 200, 1, 1, 1),
    pjs.MeshBasicMaterial(color="red", wireframe=True),
)
scene.add(cube)

axes = pjs.AxesHelper(size=100)
scene.add(axes)

scene2 = pjs.Scene()
camera2 = pjs.PerspectiveCamera(
    fov=20, aspect=CANVAS_WIDTH / CANVAS_HEIGHT, near=1, far=1000
)

renderer2 = pjs.Renderer(
    scene=scene2, camera=camera2, width=CANVAS_WIDTH, height=CANVAS_HEIGHT
)

axes2 = pjs.AxesHelper(size=50)
scene2.add(axes2)

camera2.up = camera.up

# def align_axes(change=None):
#     new_position = np.array(camera.position) - np.array(controls.target)
#     new_position = CAM_DISTANCE * new_position / np.linalg.norm(new_position)
#     camera2.position = new_position.tolist()
#     camera2.lookAt(scene2.position)

# align_axes()

# camera.observe(align_axes, names="position")
# controls.observe(align_axes, names="target")
# scene2.observe(align_axes, names="position")

camera2.quaternion = camera.quaternion
camera2.position = camera.position
camera2.zoom = camera.zoom

ipywidgets.jslink((camera, 'quaternion'), (camera2, 'quaternion'))
ipywidgets.jslink((camera, 'zoom'), (camera2, 'zoom'))
ipywidgets.jslink((camera, 'position'), (camera2, 'position'))

ipywidgets.HBox([renderer, renderer2])
vidartf commented 1 year ago

Closing as stale.