Ejemplo n.º 1
0
 def _renderer_default(self):
     center = tuple(self.ds.domain_center.in_units("code_length").d)
     right = tuple((self.ds.domain_right_edge +
                    (self.ds.domain_right_edge - self.ds.domain_center) *
                    2.0).in_units("code_length").d)
     camera = pythreejs.PerspectiveCamera(
         position=right, fov=20, children=[pythreejs.AmbientLight()])
     scene = pythreejs.Scene(
         children=[camera, pythreejs.AmbientLight(color="#dddddd")] +
         self.grid_views)
     orbit_control = pythreejs.OrbitControls(controlling=camera)
     renderer = pythreejs.Renderer(
         scene=scene,
         camera=camera,
         controls=[orbit_control],
         width=400,
         height=400,
         background="black",
         background_opacity=1,
         antialias=True,
     )
     camera.lookAt(center)
     orbit_control.target = center
     renderer.layout.border = "1px solid darkgrey"
     return renderer
Ejemplo n.º 2
0
def view_3js(sheet, coords=['x', 'y', 'z'], **draw_specs):
    """
    Creates a javascript renderer of the edge lines to be displayed
    in Jupyter Notebooks

    Returns
    -------

    renderer: a :class:`pythreejs.pythreejs.Renderer` instance
    lines: a :class:`pythreejs.pythreejs.Line` object

    Example
    -------
    >>> from IPython import display
    >>> renderer, lines = view_3js(eptm)
    >>> display(renderer)
    """

    lines = edge_lines(sheet, coords, **draw_specs)
    scene = py3js.Scene(children=[
        lines,
        py3js.DirectionalLight(color='#ccaabb', position=[0, 5, 0]),
        py3js.AmbientLight(color='#cccccc')
    ])

    c = py3js.PerspectiveCamera(position=[0, 5, 5])
    renderer = py3js.Renderer(camera=c,
                              scene=scene,
                              controls=[py3js.OrbitControls(controlling=c)])
    return renderer, lines
Ejemplo n.º 3
0
def plot_voxelgrid_with_pythreejs(voxel_centers, voxel_colors, width, height,
                                  **kwargs):
    if pythreejs is None:
        raise ImportError(
            "pythreejs is needed for plotting with pythreejs backend.")
    if display is None:
        raise ImportError(
            "IPython is needed for plotting with pythreejs backend.")

    centroid, camera_position = get_centroid_and_camera_position(voxel_centers)
    camera = pythreejs.PerspectiveCamera(fov=90,
                                         aspect=width / height,
                                         position=camera_position,
                                         up=[0, 0, 1])
    mesh = get_voxelgrid_pythreejs(voxel_centers, voxel_colors)
    scene = pythreejs.Scene(children=[camera, mesh], background=None)
    controls = pythreejs.OrbitControls(controlling=camera,
                                       target=tuple(centroid))
    camera.lookAt(tuple(centroid))
    renderer = pythreejs.Renderer(scene=scene,
                                  camera=camera,
                                  controls=[controls],
                                  width=width,
                                  height=height)
    display(renderer)
Ejemplo n.º 4
0
    def _ipython_display_(self):
        # This needs to actually display, which is not the same as returning a display.
        cam = pythreejs.PerspectiveCamera(
            position=[25, 35, 100],
            fov=20,
            children=[pythreejs.AmbientLight()],
        )
        children = [cam, pythreejs.AmbientLight(color="#dddddd")]
        material = pythreejs.MeshBasicMaterial(color="#ff0000",
                                               vertexColors="VertexColors",
                                               side="DoubleSide")
        for model in self.components:
            mesh = pythreejs.Mesh(geometry=model.geometry,
                                  material=material,
                                  position=[0, 0, 0])
            children.append(mesh)

        scene = pythreejs.Scene(children=children)

        rendererCube = pythreejs.Renderer(
            camera=cam,
            background="white",
            background_opacity=1,
            scene=scene,
            controls=[pythreejs.OrbitControls(controlling=cam)],
            width=800,
            height=800,
        )

        return rendererCube
Ejemplo n.º 5
0
    def __init__(self, obj, width=512, height=512, textureMap=None, scalarField=None, vectorField=None):
        # Note: subclass's constructor should define
        # self.MeshConstructor and self.isLineMesh, which will
        # determine how the geometry is interpreted.
        if (self.isLineMesh is None):
            self.isLineMesh = False
        if (self.MeshConstructor is None):
            self.MeshConstructor = pythreejs.Mesh

        light = pythreejs.PointLight(color='white', position=[0, 0, 5])
        light.intensity = 0.6
        self.cam = pythreejs.PerspectiveCamera(position = [0, 0, 5], up = [0, 1, 0], aspect=width / height,
                                               children=[light])

        self.avoidRedrawFlicker = False

        self.objects      = pythreejs.Group()
        self.meshes       = pythreejs.Group()
        self.ghostMeshes  = pythreejs.Group() # Translucent meshes kept around by preserveExisting

        self.materialLibrary = MaterialLibrary(self.isLineMesh)

        # Sometimes we do not use a particular attribute buffer, e.g. the index buffer when displaying
        # per-face scalar fields. But to avoid reallocating these buffers when
        # switching away from these cases, we need to preserve the buffers
        # that may have previously been allocated. This is done with the bufferAttributeStash.
        # A buffer attribute, if it exists, must always be attached to the
        # current BufferGeometry or in this stash (but not both!).
        self.bufferAttributeStash = {}

        self.currMesh        = None # The main mesh being viewed
        self.wireframeMesh   = None # Wireframe for the main visualization mesh
        self.pointsMesh      = None # Points for the main visualization mesh
        self.vectorFieldMesh = None

        self.cachedWireframeMaterial = None
        self.cachedPointsMaterial    = None

        self.objects.add([self.meshes, self.ghostMeshes])
        self.shouldShowWireframe = False
        self.scalarField = None
        self.vectorField = None

        self.arrowMaterial = None # Will hold this viewer's instance of the special vector field shader
        self._arrowSize    = 60

        # Camera needs to be part of the scene because the scene light is its child
        # (so that it follows the camera).
        self.scene = pythreejs.Scene(children=[self.objects, self.cam, pythreejs.AmbientLight(intensity=0.5)])

        # Sane trackball controls.
        self.controls = pythreejs.TrackballControls(controlling=self.cam)
        self.controls.staticMoving = True
        self.controls.rotateSpeed  = 2.0
        self.controls.zoomSpeed    = 2.0
        self.controls.panSpeed     = 1.0

        self.renderer = pythreejs.Renderer(camera=self.cam, scene=self.scene, controls=[self.controls], width=width, height=height)
        self.update(True, obj, updateModelMatrix=True, textureMap=textureMap, scalarField=scalarField, vectorField=vectorField)
Ejemplo n.º 6
0
 def _default_camera(self):
     # see https://github.com/maartenbreddels/ipyvolume/pull/40 for an explanation
     z = 2 * np.tan(45.0 / 2.0 * np.pi / 180) / np.tan(
         self.camera_fov / 2.0 * np.pi / 180)
     return pythreejs.PerspectiveCamera(fov=self.camera_fov,
                                        position=(0, 0, z),
                                        width=400,
                                        height=500)
Ejemplo n.º 7
0
 def __initialize_camera(self, width, height):
     camera_target = np.mean([drawable.center for drawable in self.drawables], axis=0)
     camera_position = tuple(camera_target + [0, 0, np.mean([drawable.scale for drawable in self.drawables])])
     directional_light = three.DirectionalLight(color = '#ffffff', position = [0, 10, 0], intensity = 1)
     camera = three.PerspectiveCamera(
         position=camera_position, aspect=width/height, lookAt=camera_target, fov=50, near=.1, far=20000,
         children=[directional_light]
     )
     return camera
Ejemplo n.º 8
0
    def __init__(self, width=600, height=400):
        light = p3js.DirectionalLight(color='#ffffff', position=[0, 0, 1], intensity=0.5)
        self.camera = p3js.PerspectiveCamera(position=[2.0, 5.0, 2.0], fov=50, children=[light], aspect=width/float(height))
        self.camera.up = (0.0, 0.0, 1.0)

        self.width = 600
        self.height = 400
        self.geometry = []
        self.draw_axes(size=1)
Ejemplo n.º 9
0
def get_camera_pythreejs(centroid, xyz, width, height):
    camera = pythreejs.PerspectiveCamera(
        fov=90,
        aspect=width / height,
        position=tuple(centroid +
                       [0, abs(xyz.max(0)[1]),
                        abs(xyz.max(0)[2]) * 1.5]),
        up=[0, 0, 1])
    camera.lookAt(tuple(centroid))
    return camera
Ejemplo n.º 10
0
Archivo: scene.py Proyecto: zizai/pydy
    def display_jupyter(self, window_size=(800, 600), axes_arrow_length=None):
        """Returns a PyThreeJS Renderer and AnimationAction for displaying and
        animating the scene inside a Jupyter notebook.

        Parameters
        ==========
        window_size : 2-tuple of integers
            2-tuple containing the width and height of the renderer window in
            pixels.
        axes_arrow_length : float
            If a positive value is supplied a red (x), green (y), and blue (z)
            arrows of the supplied length will be displayed as arrows for the
            global axes.

        Returns
        =======
        vbox : widgets.VBox
            A vertical box containing the action (pythreejs.AnimationAction)
            and renderer (pythreejs.Renderer).

        """
        if p3js is None:
            raise ImportError('pythreejs needs to be installed.')

        self._generate_meshes_tracks()

        view_width = window_size[0]
        view_height = window_size[1]

        camera = p3js.PerspectiveCamera(position=[1, 1, 1],
                                        aspect=view_width / view_height)
        key_light = p3js.DirectionalLight()
        ambient_light = p3js.AmbientLight()

        children = self._meshes + [camera, key_light, ambient_light]

        if axes_arrow_length is not None:
            children += [p3js.AxesHelper(size=abs(axes_arrow_length))]

        scene = p3js.Scene(children=children)

        controller = p3js.OrbitControls(controlling=camera)
        renderer = p3js.Renderer(camera=camera,
                                 scene=scene,
                                 controls=[controller],
                                 width=view_width,
                                 height=view_height)

        clip = p3js.AnimationClip(tracks=self._tracks, duration=self.times[-1])
        action = p3js.AnimationAction(p3js.AnimationMixer(scene), clip, scene)

        return widgets.VBox([action, renderer])
Ejemplo n.º 11
0
 def __init__(self, eye_pos, obj_pos, obj, up=(0, 1, 0)):
     self.camera = three.PerspectiveCamera(
         position=tuple(eye_pos),
         lookAt=tuple(obj_pos),
         fov=30,
     )
     self.camera.up = up
     self.light = three.PointLight(
         color="white",
         position=(-eye_pos[0], eye_pos[1], eye_pos[2]),
     )
     self.obj = obj
     self.scene = three.Scene(children=[self.obj, self.light, self.camera])
Ejemplo n.º 12
0
    def __init__(self, settings):
        self.__update_settings(settings)
        self._light = p3s.DirectionalLight(color='white', position=[0, 0, 1], intensity=0.6)
        self._light2 = p3s.AmbientLight(intensity=0.5)
        self._cam = p3s.PerspectiveCamera(position=[0, 0, 1], lookAt=[0, 0, 0], fov=self.__s["fov"],
                                     aspect=self.__s["width"]/self.__s["height"], children=[self._light])
        self._orbit = p3s.OrbitControls(controlling=self._cam)
        self._scene = p3s.Scene(children=[self._cam, self._light2], background=self.__s["background"])#"#4c4c80"
        self._renderer = p3s.Renderer(camera=self._cam, scene = self._scene, controls=[self._orbit],
                    width=self.__s["width"], height=self.__s["height"], antialias=self.__s["antialias"])

        self.__objects = {}
        self.__cnt = 0
Ejemplo n.º 13
0
def pvcamera_to_threejs_camera(pv_camera, lights, aspect):
    """Return an ipygany camera dict from a ``pyvista.Plotter`` object."""
    # scene will be centered at focal_point, so adjust the position
    position = np.array(pv_camera.position) - np.array(pv_camera.focal_point)
    far = np.linalg.norm(position) * 100000

    return tjs.PerspectiveCamera(
        up=pv_camera.up,
        children=lights,
        position=position.tolist(),
        fov=pv_camera.view_angle,
        aspect=aspect,
        far=far,
        near=0.01,
    )
Ejemplo n.º 14
0
 def __init__(self):
     # TODO: arguments for width/height
     self._width = 600
     self._height = 400
     self._ball = _three.Mesh(
         geometry=_three.SphereGeometry(
             radius=1,
             widthSegments=30,
             heightSegments=20,
         ),
         material=_three.MeshLambertMaterial(color='lightgray'),
     )
     self._axes = _three.AxesHelper(size=1.2)
     self._ambient_light = _three.AmbientLight(
         intensity=0.5,
     )
     self._directional_light1 = _three.DirectionalLight(
         position=[0, 0, 1],
         intensity=0.6,
     )
     self._directional_light2 = _three.DirectionalLight(
         position=[0, 0, -1],
         intensity=0.6,
     )
     self._scene = _three.Scene(
         children=[
             self._ball,
             self._axes,
             self._ambient_light,
             self._directional_light1,
             self._directional_light2,
         ],
     )
     self._camera = _three.PerspectiveCamera(
         position=[0, 0, 2.4],
         up=[0, 0, 1],
         aspect=self._width/self._height,
     )
     self._controls = _three.OrbitControls(controlling=self._camera)
     self._renderer = _three.Renderer(
         camera=self._camera,
         scene=self._scene,
         controls=[self._controls],
         width=self._width,
         height=self._height,
         #alpha=True,
         #clearOpacity=0.5,
     )
Ejemplo n.º 15
0
def viewer_cloth(cloth):
    view_width = 800
    view_height = 600
    camera = THREE.PerspectiveCamera(position=[20, 5, 30],
                                     aspect=view_width / view_height)
    key_light = THREE.DirectionalLight(position=[10, 10, 10])
    ambient_light = THREE.AmbientLight()
    axes_helper = THREE.AxesHelper(0.5)

    scene = THREE.Scene()
    controller = THREE.OrbitControls(controlling=camera)
    renderer = THREE.Renderer(camera=camera,
                              scene=scene,
                              controls=[controller],
                              width=view_width,
                              height=view_height)
    scene.children = [cloth, axes_helper, camera, key_light, ambient_light]
    return renderer
Ejemplo n.º 16
0
    def getView(self, regenerateView=False, viewWidth=600, viewHeight=400):
        if regenerateView or self.view is None:
            center = self.model.get_center()
            camera = three.PerspectiveCamera(
                position=(center + np.array([0, 200, 0])).tolist(),
                aspect=viewWidth / viewHeight)
            key_light = three.DirectionalLight(position=[0, 10, 10])
            ambient_light = three.AmbientLight()

            scene = three.Scene(
                children=[self.selectable, camera, key_light, ambient_light])

            three.Picker(controlling=scene, event='mousemove')
            controller = three.OrbitControls(controlling=camera,
                                             screenSpacePanning=True,
                                             target=center.tolist())
            camera.lookAt(center.tolist())

            self.coord_label = HTML('Select beads by double-clicking')
            selIncrButton = Button(description='Increase selection')
            selIncrButton.on_click(self.increaseSelection)
            selDecrButton = Button(description='Decrease selection')
            selDecrButton.on_click(self.decreaseSelection)

            click_picker = three.Picker(controlling=self.selectable,
                                        event='dblclick')
            click_picker.observe(self.selectBead, names=['point'])

            renderer = three.Renderer(camera=camera,
                                      scene=scene,
                                      controls=[controller, click_picker],
                                      width=viewWidth,
                                      height=viewHeight,
                                      antialias=True)

            self.view = VBox([
                self.coord_label, renderer,
                HBox([selDecrButton, selIncrButton])
            ])
        return self.view
Ejemplo n.º 17
0
    def _setup_scene(self, bounding_box, render_size):
        self.min_width = render_size[0]
        self.min_height = render_size[1]
        fov_angle = 60
        if len(bounding_box[0]) == 2:
            lower = np.array([bounding_box[0][0], bounding_box[0][1], 0])
            upper = np.array([bounding_box[1][0], bounding_box[1][1], 0])
            bounding_box = (lower, upper)
        combined_bounds = np.hstack(bounding_box)

        absx = np.abs(combined_bounds[0] - combined_bounds[3])
        not_mathematical_distance_scaling = 1.2
        self.camera_distance = np.sin(
            (90 - fov_angle / 2) * np.pi / 180) * 0.5 * absx / np.sin(
                fov_angle / 2 * np.pi / 180)
        self.camera_distance *= not_mathematical_distance_scaling
        xhalf = (combined_bounds[0] + combined_bounds[3]) / 2
        yhalf = (combined_bounds[1] + combined_bounds[4]) / 2
        zhalf = (combined_bounds[2] + combined_bounds[5]) / 2
        self.mesh_center = (xhalf, yhalf, zhalf)
        self.cam = p3js.PerspectiveCamera(
            aspect=render_size[0] / render_size[1],
            position=[0, 0, 0 + self.camera_distance])
        self.light = p3js.AmbientLight(color='white', intensity=1.0)
        self.scene = p3js.Scene(children=[self.cam, self.light],
                                background='white')
        self.controller = p3js.OrbitControls(
            controlling=self.cam,
            position=[0, 0, 0 + self.camera_distance],
            target=[0, 0, 0])
        self.freeze_camera(True)
        self.renderer = p3js.Renderer(camera=self.cam,
                                      scene=self.scene,
                                      controls=[self.controller],
                                      webgl_version=1,
                                      width=render_size[0],
                                      height=render_size[1])
Ejemplo n.º 18
0
def display_scene(lm_scene):
    """Display Lightmetrica scene."""

    # Scene
    scene = three.Scene()

    # Camera
    # Get lm camera information
    lm_main_camera = lm_scene.camera()
    lm_camera_params = lm_main_camera.underlying_value()
    camera = three.PerspectiveCamera(fov=lm_camera_params['vfov'],
                                     aspect=lm_camera_params['aspect'],
                                     near=0.1,
                                     far=10000)
    camera.position = lm_camera_params['eye']
    camera.up = lm_camera_params['up']

    scene.add(camera)

    # Mesh
    def add_lm_scene_mesh():
        # Default material
        mat_default = three.MeshBasicMaterial(color='#000000',
                                              wireframe=True,
                                              transparent=True,
                                              opacity=0.2,
                                              depthTest=False)

        # Convert lm mesh
        def traverse_func(node, trans):
            # Underlying mesh
            mesh = node.primitive.mesh
            if mesh is None:
                return

            # Iterate through all triangles
            vs = []

            def process_triangle(face_index, tri):
                vs.append(list(tri.p1.p))
                vs.append(list(tri.p2.p))
                vs.append(list(tri.p3.p))

            mesh.foreach_triangle(process_triangle)

            # Create geometry
            ps_attr = three.BufferAttribute(array=vs, normalized=False)
            geom = three.BufferGeometry(attributes={'position': ps_attr})

            # Create mesh
            mesh = three.Mesh(geometry=geom, material=mat_default)
            mesh.matrixAutoUpdate = False
            mesh.matrix = trans.T.flatten().tolist()
            scene.add(mesh)

        lm_scene.traverse_primitive_nodes(traverse_func)

    add_lm_scene_mesh()

    # View frustum
    def add_view_frustum():
        position = np.array(lm_camera_params['eye'])
        center = np.array(lm_camera_params['center'])
        up = np.array(lm_camera_params['up'])
        aspect = lm_camera_params['aspect']
        fov = math.radians(lm_camera_params['vfov'])

        M = lookat_matrix(position, center, up)
        z = 5
        half_fov = fov * .5
        y = math.tan(half_fov) * z
        x = aspect * y

        p = list(position)
        p1 = list(position + np.dot(M, [-x, -y, -z]))
        p2 = list(position + np.dot(M, [x, -y, -z]))
        p3 = list(position + np.dot(M, [x, y, -z]))
        p4 = list(position + np.dot(M, [-x, y, -z]))

        # Add mesh
        geom = three.Geometry(
            vertices=[p, p1, p2, p, p2, p3, p, p3, p4, p, p4, p1])
        mat = three.MeshBasicMaterial(color='#00ff00',
                                      wireframe=True,
                                      side='DoubleSide')
        mesh = three.Line(geometry=geom, material=mat)
        scene.add(mesh)

    add_view_frustum()

    # Axis
    axes = three.AxesHelper(size=1)
    scene.add(axes)

    # Renderer
    controls = three.OrbitControls(controlling=camera)

    # Rendered image size
    w = 1000
    h = w / lm_camera_params['aspect']

    # We need to set both target and lookAt in this order.
    # Otherwise the initial target position becomes wrong.
    # cf. https://github.com/jupyter-widgets/pythreejs/issues/200
    controls.target = lm_camera_params['center']
    camera.lookAt(lm_camera_params['center'])
    renderer = three.Renderer(camera=camera,
                              scene=scene,
                              width=w,
                              height=h,
                              controls=[controls])

    # Button to reset camera configuration
    # Note that we need to press the button twice to reset the control
    # to the correct target possibly due to the bug of pythreejs.
    reset_camera_button = widgets.Button(description="Reset Camera")

    @reset_camera_button.on_click
    def reset_camera_button_on_click(b):
        controls.reset()
        controls.target = lm_camera_params['center']
        camera.lookAt(lm_camera_params['center'])

    # Display all
    display(reset_camera_button)
    display(renderer)

    return scene, camera, renderer
Ejemplo n.º 19
0
def create_jsrenderer(scene,
                      height=400,
                      width=400,
                      background='gray',
                      orthographic=False,
                      camera_position=(0, 0, -10),
                      view=(10, -10, -10, 10),
                      fov=50):
    """
    
    Properties
    ----------
    orthographic : bool
        use orthographic camera (True) or perspective (False) 
    camera_position : tuple
        position of camera in scene
    view : tuple
        view extents: (top, bottom, left, right) (orthographic only)
    fov : float
        camera field of view (perspective only)
    
    Returns
    -------
    camera : pythreejs.Camera
    renderer : pythreejs.Renderer
                      
    Examples
    --------
    
    >>> import pythreejs as js
    >>> scene = js.Scene(children=[js.AmbientLight(color='#777777')])
    >>> camera, renderer = create_jsrenderer(scene,200,200, 'gray', (1,-1,-1,1))
    >>> type(renderer)
    <class 'pythreejs.pythreejs.Renderer'>
    >>> type(camera)
    <class 'pythreejs.pythreejs.OrthographicCamera'>
    
    """

    if orthographic:
        top, bottom, left, right = view
        camera = js.OrthographicCamera(position=camera_position,
                                       up=[0, 0, 1],
                                       top=top,
                                       bottom=bottom,
                                       left=left,
                                       right=right,
                                       near=.1,
                                       far=2000)
    else:
        camera = js.PerspectiveCamera(position=camera_position,
                                      up=[0, 0, 1],
                                      far=2000,
                                      near=.1,
                                      fov=fov,
                                      aspect=width / float(height))
    camera.children = [
        js.DirectionalLight(color='white', position=[3, 5, 1], intensity=0.5)
    ]
    control = js.OrbitControls(controlling=camera)
    renderer = js.Renderer(camera=camera,
                           background=background,
                           background_opacity=.1,
                           height=str(height),
                           width=str(width),
                           scene=scene,
                           controls=[control])
    return camera, renderer
Ejemplo n.º 20
0
def cqdisplay(result, color='#708090', scale=1.0):
    'display CQ object in a ThreeJS Webgl context'
    # Open stream
    output = StringIO.StringIO()

    # cadquery will stream a ThreeJS JSON (using old v3 schema, which is deprecated)
    exporters.exportShape(result.shape.findSolid().scale(scale), 'TJS', output)

    # store stream to a variable
    contents = output.getvalue()

    # Close stream
    output.close()

    # Overwrite the JSON color portion with user defined color. Disallows NAMED colors
    col = list(matplotlib.colors.hex2color(color))
    old_col_str = '"colorDiffuse" : [0.6400000190734865, 0.10179081114814892, 0.126246120426746]'
    new_col_str = '"colorDiffuse" : ' + str(col)
    new_contents = contents.replace(old_col_str, new_col_str)

    # Take the string and create a proper json object
    contents = json.loads(contents)

    # Vertices and Faces are both flat lists, but the pythreejs module requires list of lists
    old_v = contents['vertices']
    old_f = contents['faces']

    # Splits the list up in 3s, to produce a list of lists representing the vertices
    vertices = [old_v[i:i+3] for i in range(0, len(old_v), 3)]

    # JSON Schema has first position in the face's list reserved to indicate type.
    # Cadquery returns Triangle mesh, so we know that we must split list into lists of length 4
    # 1st entry to indicate triangle, next 3 to specify vertices
    three_faces = [old_f[i:i+4] for i in range(0, len(old_f), 4)]
    faces = []

    # Drop the first entry in the face list
    for entry in three_faces:
        entry.pop(0)
        faces.append(entry)

    # Cadquery does not supply face normals in the JSON,
    # and we cannot use THREE.JS built in 'computefaceNormals'
    # (at least, not easily)
    # Instead, we just calculate the face normals ourselves.
    # It is just the cross product of 2 vectors in the triangle.
    # TODO: see if there is a better way to achieve this result
    face_normals = []

    for entry in faces:
        v_a = np.asarray(vertices[entry[0]])
        v_b = np.asarray(vertices[entry[1]])
        v_c = np.asarray(vertices[entry[2]])

        vec_a = v_b - v_a
        vec_b = v_c - v_a

        cross = np.cross(vec_a, vec_b)

        face_normals.append([cross[0], cross[1], cross[2]])

    # set up geometry
    geom = pythreejs.PlainGeometry(vertices=vertices, faces=faces, faceNormals=face_normals)
    mtl = pythreejs.LambertMaterial(color=color, shading='FlatShading')
    obj = pythreejs.Mesh(geometry=geom, material=mtl)

    # set up scene and camera
    cam_dist = 50
    fov = 35
    cam = pythreejs.PerspectiveCamera(
        position=[cam_dist, cam_dist, cam_dist], fov=fov,
        children=[pythreejs.DirectionalLight(color='#ffffff', position=[-3, 5, 1], intensity=0.45)])
    scn_chld = [
        obj,
        pythreejs.AmbientLight(color='#dddddd')
    ]
    scn = pythreejs.Scene(children=scn_chld)

    render = pythreejs.Renderer(
        width='830'.decode('utf-8'),
        height='553'.decode('utf-8'),
        camera=cam,
        scene=scn,
        controls=[pythreejs.OrbitControls(controlling=cam)]
        )

    return render
Ejemplo n.º 21
0
    def __init__(self,
                 scipp_obj_dict=None,
                 positions=None,
                 axes=None,
                 masks=None,
                 cmap=None,
                 log=None,
                 vmin=None,
                 vmax=None,
                 color=None,
                 aspect=None,
                 background=None,
                 nan_color=None,
                 pixel_size=None,
                 tick_size=None,
                 show_outline=True):

        super().__init__(scipp_obj_dict=scipp_obj_dict,
                         positions=positions,
                         axes=axes,
                         masks=masks,
                         cmap=cmap,
                         log=log,
                         vmin=vmin,
                         vmax=vmax,
                         color=color,
                         aspect=aspect,
                         button_options=['X', 'Y', 'Z'])

        self.vslice = None
        self.current_cut_surface_value = None
        self.cut_slider_steps = 10.
        self.cbar_image = widgets.Image()
        self.cut_options = {
            "Xplane": 0,
            "Yplane": 1,
            "Zplane": 2,
            "Xcylinder": 3,
            "Ycylinder": 4,
            "Zcylinder": 5,
            "Sphere": 6,
            "Value": 7
        }

        # Prepare colormaps
        self.cmap = copy(cm.get_cmap(self.params["values"][self.name]["cmap"]))
        self.cmap.set_bad(color=nan_color)
        self.scalar_map = cm.ScalarMappable(
            norm=self.params["values"][self.name]["norm"], cmap=self.cmap)
        self.masks_scalar_map = None
        if self.params["masks"][self.name]["show"]:
            self.masks_cmap = copy(
                cm.get_cmap(self.params["masks"][self.name]["cmap"]))
            self.masks_cmap.set_bad(color=nan_color)
            self.masks_scalar_map = cm.ScalarMappable(
                norm=self.params["values"][self.name]["norm"],
                cmap=self.masks_cmap)

        # Generate the colorbar image
        self.create_colorbar()

        # Useful variables
        self.permutations = {"x": ["y", "z"], "y": ["x", "z"], "z": ["x", "y"]}
        self.remaining_inds = [0, 1]

        # Search the coordinates to see if one contains vectors. If so, it will
        # be used as position vectors.
        self.axlabels = {"x": "", "y": "", "z": ""}
        self.positions = None
        self.pixel_size = pixel_size
        self.tick_size = tick_size
        if positions is not None:
            coord = self.data_array.coords[positions]
            self.positions = np.array(coord.values, dtype=np.float32)
            self.axlabels.update({
                "x": name_with_unit(coord, name="X"),
                "y": name_with_unit(coord, name="Y"),
                "z": name_with_unit(coord, name="Z")
            })
        else:
            # If no positions are supplied, create a meshgrid from coordinate
            # axes.
            coords = []
            labels = []
            for dim, val in self.slider.items():
                if val.disabled:
                    arr = self.slider_coord[self.name][dim]
                    if self.histograms[self.name][dim][dim]:
                        arr = to_bin_centers(arr, dim)
                    coords.append(arr.values)
                    labels.append(
                        name_with_unit(self.slider_coord[self.name][dim]))
            z, y, x = np.meshgrid(*coords, indexing='ij')
            self.positions = np.array(
                [x.ravel(), y.ravel(), z.ravel()], dtype=np.float32).T
            if self.pixel_size is None:
                self.pixel_size = coords[0][1] - coords[0][0]
            self.axlabels.update({
                "z": labels[0],
                "y": labels[1],
                "x": labels[2]
            })

        # Find spatial and value limits
        self.xminmax, self.center_of_mass = self.get_spatial_extents()
        self.vminmax = [
            sc.min(self.data_array.data).value,
            sc.max(self.data_array.data).value
        ]

        # Create the point cloud with pythreejs
        self.points_geometry, self.points_material, self.points = \
            self.create_points_geometry()

        # Create outline around point positions
        self.outline, self.axticks = self.create_outline()

        # Save the size of the outline box for later
        self.box_size = np.diff(list(self.xminmax.values()), axis=1).ravel()

        # Define camera: look at the centre of mass of the points
        camera_lookat = self.center_of_mass
        camera_pos = np.array(self.center_of_mass) + 1.2 * self.box_size
        self.camera = p3.PerspectiveCamera(position=list(camera_pos),
                                           aspect=config.plot.width /
                                           config.plot.height)

        # Add red/green/blue axes helper
        self.axes_3d = p3.AxesHelper(10.0 * np.linalg.norm(camera_pos))

        # Create the pythreejs scene
        self.scene = p3.Scene(children=[
            self.camera, self.axes_3d, self.points, self.outline, self.axticks
        ],
                              background=background)

        # Add camera controller
        self.controller = p3.OrbitControls(controlling=self.camera,
                                           target=camera_lookat)
        self.camera.lookAt(camera_lookat)

        # Render the scene into a widget
        self.renderer = p3.Renderer(camera=self.camera,
                                    scene=self.scene,
                                    controls=[self.controller],
                                    width=config.plot.width,
                                    height=config.plot.height)

        # Update visibility of outline according to keyword arg
        self.outline.visible = show_outline
        self.axticks.visible = show_outline

        # Opacity slider: top value controls opacity if no cut surface is
        # active. If a cut curface is present, the upper slider is the opacity
        # of the slice, while the lower slider value is the opacity of the
        # data not in the cut surface.
        self.opacity_slider = widgets.FloatRangeSlider(
            min=0.0,
            max=1.0,
            value=[0.1, 1],
            step=0.01,
            description="Opacity slider: When no cut surface is active, the "
            "max value of the range slider controls the overall opacity, "
            "and the lower value has no effect. When a cut surface is "
            "present, the max value is the opacity of the slice, while the "
            "min value is the opacity of the background.",
            continuous_update=True,
            style={'description_width': '60px'})
        self.opacity_slider.observe(self.update_opacity, names="value")
        self.opacity_checkbox = widgets.Checkbox(
            value=self.opacity_slider.continuous_update,
            description="Continuous update",
            indent=False,
            layout={"width": "20px"})
        self.opacity_checkbox_link = widgets.jslink(
            (self.opacity_checkbox, 'value'),
            (self.opacity_slider, 'continuous_update'))

        self.toggle_outline_button = widgets.ToggleButton(value=show_outline,
                                                          description='',
                                                          button_style='')
        self.toggle_outline_button.observe(self.toggle_outline, names="value")
        # Run a trigger to update button text
        self.toggle_outline({"new": show_outline})

        # Add buttons to provide a choice of different cut surfaces:
        # - Cartesian X, Y, Z
        # - Cylindrical X, Y, Z (cylinder major axis)
        # - Sperical R
        # - Value-based iso-surface
        # Note additional spaces required in cylindrical names because
        # options must be unique.
        self.cut_surface_buttons = widgets.ToggleButtons(
            options=[('X ', self.cut_options["Xplane"]),
                     ('Y ', self.cut_options["Yplane"]),
                     ('Z ', self.cut_options["Zplane"]),
                     ('R ', self.cut_options["Sphere"]),
                     (' X ', self.cut_options["Xcylinder"]),
                     (' Y ', self.cut_options["Ycylinder"]),
                     (' Z ', self.cut_options["Zcylinder"]),
                     ('', self.cut_options["Value"])],
            value=None,
            description='Cut surface:',
            button_style='',
            tooltips=[
                'X-plane', 'Y-plane', 'Z-plane', 'Sphere', 'Cylinder-X',
                'Cylinder-Y', 'Cylinder-Z', 'Value'
            ],
            icons=(['cube'] * 3) + ['circle-o'] + (['toggle-on'] * 3) +
            ['magic'],
            style={"button_width": "55px"},
            layout={'width': '350px'})
        self.cut_surface_buttons.observe(self.update_cut_surface_buttons,
                                         names="value")
        # Add a capture for a click event: if the active button is clicked,
        # this resets the togglebuttons value to None and deletes the cut
        # surface.
        self.cut_surface_buttons.on_msg(self.check_if_reset_needed)

        # Add slider to control position of cut surface
        self.cut_slider = widgets.FloatSlider(min=0,
                                              max=1,
                                              description="Position:",
                                              disabled=True,
                                              value=0.5,
                                              layout={"width": "350px"})
        self.cut_checkbox = widgets.Checkbox(value=True,
                                             description="Continuous update",
                                             indent=False,
                                             layout={"width": "20px"},
                                             disabled=True)
        self.cut_checkbox_link = widgets.jslink(
            (self.cut_checkbox, 'value'),
            (self.cut_slider, 'continuous_update'))
        self.cut_slider.observe(self.update_cut_surface, names="value")

        # Allow to change the thickness of the cut surface
        self.cut_surface_thickness = widgets.BoundedFloatText(
            value=0.05 * self.box_size.max(),
            min=0,
            layout={"width": "150px"},
            disabled=True,
            description="Thickness:",
            style={'description_width': 'initial'})
        self.cut_surface_thickness.observe(self.update_cut_surface,
                                           names="value")
        self.cut_thickness_link = widgets.jslink(
            (self.cut_slider, 'step'), (self.cut_surface_thickness, 'value'))
        self.cut_slider.observe(self.update_cut_surface, names="value")

        # Put widgets into boxes
        self.cut_surface_controls = widgets.HBox([
            self.cut_surface_buttons,
            widgets.VBox([
                widgets.HBox([self.cut_slider, self.cut_checkbox]),
                self.cut_surface_thickness
            ])
        ])

        self.box = widgets.VBox([
            widgets.HBox([self.renderer, self.cbar_image]),
            widgets.VBox(self.vbox),
            widgets.HBox([
                self.opacity_slider, self.opacity_checkbox,
                self.toggle_outline_button
            ]), self.cut_surface_controls
        ])

        # Update list of members to be returned in the SciPlot object
        self.members.update({
            "camera": self.camera,
            "scene": self.scene,
            "renderer": self.renderer
        })

        return
Ejemplo n.º 22
0
 def _default_camera(self):
     # return pythreejs.CombinedCamera(fov=46, position=(0, 0, 2), width=400, height=500)
     return pythreejs.PerspectiveCamera(fov=46, position=(0, 0, 2), width=400, height=500)
Ejemplo n.º 23
0
def visualise(mesh,
              geometric_field,
              number_of_dimensions,
              xi_interpolation,
              dependent_field=None,
              variable=None,
              mechanics_animation=False,
              colour_map_dependent_component_number=None,
              cmap='gist_rainbow',
              resolution=1,
              node_labels=False):

    if number_of_dimensions != 3:
        print(
            'Warning: Only visualisation of 3D meshes is currently supported.')
        return

    if xi_interpolation != [1, 1, 1]:
        print(
            'Warning: Only visualisation of 3D elements with linear Lagrange \
            interpolation along all coordinate directions is currently \
            supported.')
        return

    view_width = 600
    view_height = 600

    debug = False
    if debug:
        vertices = [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0],
                    [1, 0, 1], [1, 1, 0], [1, 1, 1]]

        faces = [[0, 1, 3], [0, 3, 2], [0, 2, 4], [2, 6, 4], [0, 4, 1],
                 [1, 4, 5], [2, 3, 6], [3, 7, 6], [1, 5, 3], [3, 5, 7],
                 [4, 6, 5], [5, 6, 7]]

        vertexcolors = [
            '#000000', '#0000ff', '#00ff00', '#ff0000', '#00ffff', '#ff00ff',
            '#ffff00', '#ffffff'
        ]
    else:
        # Get mesh topology information.
        num_nodes = mesh_tools.num_nodes_get(mesh, mesh_component=1)
        node_nums = list(range(1, num_nodes + 1))
        num_elements, element_nums = mesh_tools.num_element_get(
            mesh, mesh_component=1)

        # Convert geometric field to a morphic mesh and export to json
        mesh = mesh_tools.OpenCMISS_to_morphic(mesh,
                                               geometric_field,
                                               element_nums,
                                               node_nums,
                                               dimension=3,
                                               interpolation='linear')
        vertices, faces, _, xi_element_nums, xis = get_faces(
            mesh, res=resolution, exterior_only=True, include_xi=True)

        vertices = vertices.tolist()
        faces = faces.tolist()

    centroid = np.mean(vertices, axis=0)
    max_positions = np.max(vertices, axis=0)
    min_positions = np.min(vertices, axis=0)
    range_positions = max_positions - min_positions

    if (dependent_field is not None) and (colour_map_dependent_component_number
                                          is not None):

        solution = np.zeros(xis.shape[0])
        for idx, (xi, xi_element_num) in enumerate(zip(xis, xi_element_nums)):
            solution[idx] = mesh_tools.interpolate_opencmiss_field_xi(
                dependent_field,
                xi,
                element_ids=[xi_element_num],
                dimension=3,
                deriv=1)[colour_map_dependent_component_number - 1]

        minima = min(solution)
        maxima = max(solution)

        import matplotlib
        norm = matplotlib.colors.Normalize(vmin=minima, vmax=maxima, clip=True)
        mapper = cm.ScalarMappable(norm=norm, cmap=cm.get_cmap(name=cmap))

        vertex_colors = np.zeros((len(vertices), 3), dtype='float32')
        for idx, v in enumerate(solution):
            vertex_colors[idx, :] = mapper.to_rgba(v, alpha=None)[:3]
        # else:
        #     raise ValueError('Visualisation not supported.')
    else:
        vertex_colors = np.tile(np.array([0.5, 0.5, 0.5], dtype='float32'),
                                (len(vertices), 1))

    geometry = pjs.BufferGeometry(attributes=dict(
        position=pjs.BufferAttribute(vertices, normalized=False),
        index=pjs.BufferAttribute(
            np.array(faces).astype(dtype='uint16').ravel(), normalized=False),
        color=pjs.BufferAttribute(vertex_colors),
    ))

    if mechanics_animation:
        deformed_vertices = np.zeros((xis.shape[0], 3), dtype='float32')
        for idx, (xi, xi_element_num) in enumerate(zip(xis, xi_element_nums)):
            deformed_vertices[idx, :] = \
            mesh_tools.interpolate_opencmiss_field_xi(
                dependent_field, xi, element_ids=[xi_element_num],
                dimension=3,
                deriv=1)[0][:3]
        geometry.morphAttributes = {
            'position': [
                pjs.BufferAttribute(deformed_vertices),
            ]
        }

        geometry.exec_three_obj_method('computeFaceNormals')
        geometry.exec_three_obj_method('computeVertexNormals')

        surf1 = pjs.Mesh(geometry,
                         pjs.MeshPhongMaterial(color='#ff3333',
                                               shininess=150,
                                               morphTargets=True,
                                               side='FrontSide'),
                         name='A')
        surf2 = pjs.Mesh(geometry,
                         pjs.MeshPhongMaterial(color='#ff3333',
                                               shininess=150,
                                               morphTargets=True,
                                               side='BackSide'),
                         name='B')
        surf = pjs.Group(children=[surf1, surf2])

        # camera = pjs.PerspectiveCamera(
        #     fov=20, position=[range_positions[0] * 10,
        #                       range_positions[1] * 10,
        #                       range_positions[2] * 10],
        #     width=view_width,
        #     height=view_height, near=1,
        #     far=max(range_positions) * 10)

        camera = pjs.PerspectiveCamera(position=[
            range_positions[0] * 3, range_positions[1] * 3,
            range_positions[2] * 3
        ],
                                       aspect=view_width / view_height)
        camera.up = [0, 0, 1]
        camera.lookAt(centroid.tolist())

        scene3 = pjs.Scene(children=[
            surf1, surf2, camera,
            pjs.DirectionalLight(position=[3, 5, 1], intensity=0.6),
            pjs.AmbientLight(intensity=0.5)
        ])
        axes = pjs.AxesHelper(size=range_positions[0] * 2)
        scene3.add(axes)

        A_track = pjs.NumberKeyframeTrack(
            name='scene/A.morphTargetInfluences[0]',
            times=[0, 3],
            values=[0, 1])
        B_track = pjs.NumberKeyframeTrack(
            name='scene/B.morphTargetInfluences[0]',
            times=[0, 3],
            values=[0, 1])
        pill_clip = pjs.AnimationClip(tracks=[A_track, B_track])
        pill_action = pjs.AnimationAction(pjs.AnimationMixer(scene3),
                                          pill_clip, scene3)

        renderer3 = pjs.Renderer(
            camera=camera,
            scene=scene3,
            controls=[pjs.OrbitControls(controlling=camera)],
            width=view_width,
            height=view_height)

        display(renderer3, pill_action)

    else:
        geometry.exec_three_obj_method('computeFaceNormals')
        geometry.exec_three_obj_method('computeVertexNormals')

        surf1 = pjs.Mesh(geometry=geometry,
                         material=pjs.MeshLambertMaterial(
                             vertexColors='VertexColors',
                             side='FrontSide'))  # Center the cube.
        surf2 = pjs.Mesh(geometry=geometry,
                         material=pjs.MeshLambertMaterial(
                             vertexColors='VertexColors',
                             side='BackSide'))  # Center the cube.
        surf = pjs.Group(children=[surf1, surf2])

        camera = pjs.PerspectiveCamera(position=[
            range_positions[0] * 3, range_positions[1] * 3,
            range_positions[2] * 3
        ],
                                       aspect=view_width / view_height)

        camera.up = [0, 0, 1]
        camera.lookAt(centroid.tolist())

        # if perspective:
        #     camera.mode = 'perspective'
        # else:
        #     camera.mode = 'orthographic'

        lights = [
            pjs.DirectionalLight(position=[
                range_positions[0] * 16, range_positions[1] * 12,
                range_positions[2] * 17
            ],
                                 intensity=0.5),
            pjs.AmbientLight(intensity=0.8),
        ]
        orbit = pjs.OrbitControls(controlling=camera,
                                  screenSpacePanning=True,
                                  target=centroid.tolist())

        scene = pjs.Scene()
        axes = pjs.AxesHelper(size=max(range_positions) * 2)
        scene.add(axes)
        scene.add(surf1)
        scene.add(surf2)
        scene.add(lights)

        if node_labels:
            # Add text labels for each mesh node.
            v, ids = mesh.get_node_ids(group='_default')
            for idx, v in enumerate(v):
                text = make_text(str(ids[idx]), position=(v[0], v[1], v[2]))
                scene.add(text)

        # Add text for axes labels.
        x_axis_label = make_text('x',
                                 position=(max(range_positions) * 2, 0, 0))
        y_axis_label = make_text('y',
                                 position=(0, max(range_positions) * 2, 0))
        z_axis_label = make_text('z',
                                 position=(0, 0, max(range_positions) * 2))
        scene.add(x_axis_label)
        scene.add(y_axis_label)
        scene.add(z_axis_label)

        renderer = pjs.Renderer(scene=scene,
                                camera=camera,
                                controls=[orbit],
                                width=view_width,
                                height=view_height)
        camera.zoom = 1
        display(renderer)

    return vertices, faces
Ejemplo n.º 24
0
    def _render_obj(self, rendered_obj, **kw):
        obj_geometry = pjs.BufferGeometry(attributes=dict(
            position=pjs.BufferAttribute(rendered_obj.plot_verts),
            color=pjs.BufferAttribute(rendered_obj.base_cols),
            normal=pjs.BufferAttribute(
                rendered_obj.face_normals.astype('float32'))))
        vertices = rendered_obj.vertices

        # Create a mesh. Note that the material need to be told to use the vertex colors.
        my_object_mesh = pjs.Mesh(
            geometry=obj_geometry,
            material=pjs.MeshLambertMaterial(vertexColors='VertexColors'),
            position=[0, 0, 0],
        )

        line_material = pjs.LineBasicMaterial(color='#ffffff',
                                              transparent=True,
                                              opacity=0.3,
                                              linewidth=1.0)
        my_object_wireframe_mesh = pjs.LineSegments(
            geometry=obj_geometry,
            material=line_material,
            position=[0, 0, 0],
        )

        n_vert = vertices.shape[0]
        center = vertices.mean(axis=0)

        extents = self._get_extents(vertices)
        max_delta = np.max(extents[:, 1] - extents[:, 0])
        camPos = [center[i] + 4 * max_delta for i in range(3)]
        light_pos = [center[i] + (i + 3) * max_delta for i in range(3)]

        # Set up a scene and render it:
        camera = pjs.PerspectiveCamera(position=camPos,
                                       fov=20,
                                       children=[
                                           pjs.DirectionalLight(
                                               color='#ffffff',
                                               position=light_pos,
                                               intensity=0.5)
                                       ])
        camera.up = (0, 0, 1)

        v = [0.0, 0.0, 0.0]
        if n_vert > 0: v = vertices[0].tolist()
        select_point_geom = pjs.SphereGeometry(radius=1.0)
        select_point_mesh = pjs.Mesh(
            select_point_geom,
            material=pjs.MeshBasicMaterial(color=SELECTED_VERTEX_COLOR),
            position=v,
            scale=(0.0, 0.0, 0.0))

        #select_edge_mesh = pjs.ArrowHelper(dir=pjs.Vector3(1.0, 0.0, 0.0), origin=pjs.Vector3(0.0, 0.0, 0.0), length=1.0,
        #                                  hex=SELECTED_EDGE_COLOR_INT, headLength=0.1, headWidth=0.05)

        arrow_cyl_mesh = pjs.Mesh(geometry=pjs.SphereGeometry(radius=0.01),
                                  material=pjs.MeshLambertMaterial())
        arrow_head_mesh = pjs.Mesh(geometry=pjs.SphereGeometry(radius=0.001),
                                   material=pjs.MeshLambertMaterial())

        scene_things = [
            my_object_mesh, my_object_wireframe_mesh, select_point_mesh,
            arrow_cyl_mesh, arrow_head_mesh, camera,
            pjs.AmbientLight(color='#888888')
        ]

        if self.draw_grids:
            grids, space = self._get_grids(vertices)
            scene_things.append(grids)

        scene = pjs.Scene(children=scene_things, background=BACKGROUND_COLOR)

        click_picker = pjs.Picker(controlling=my_object_mesh, event='dblclick')
        out = Output()
        top_msg = HTML()

        def on_dblclick(change):
            if change['name'] == 'point':
                try:
                    point = np.array(change['new'])
                    face = click_picker.faceIndex
                    face_points = rendered_obj.face_verts[face]
                    face_vecs = face_points - np.roll(face_points, 1, axis=0)
                    edge_lens = np.sqrt((face_vecs**2).sum(axis=1))
                    point_vecs = face_points - point[np.newaxis, :]
                    point_dists = (point_vecs**2).sum(axis=1)
                    min_point = np.argmin(point_dists)
                    v1s = point_vecs.copy()
                    v2s = np.roll(v1s, -1, axis=0)
                    edge_mids = 0.5 * (v2s + v1s)
                    edge_mid_dists = (edge_mids**2).sum(axis=1)
                    min_edge_point = np.argmin(edge_mid_dists)
                    edge_start = min_edge_point
                    edge = face * 3 + edge_start
                    close_vert = rendered_obj.face_verts[face, min_point]
                    edge_start_vert = rendered_obj.face_verts[face, edge_start]
                    edge_end_vert = rendered_obj.face_verts[face,
                                                            (edge_start + 1) %
                                                            3]

                    vertex = face * 3 + min_point
                    radius = min(
                        [edge_lens.max() * 0.02, 0.1 * edge_lens.min()])

                    edge_head_length = radius * 4
                    edge_head_width = radius * 2
                    select_point_mesh.scale = (radius, radius, radius)
                    top_msg.value = '<font color="{}">selected face: {}</font>, <font color="{}">edge: {}</font>, <font color="{}"> vertex: {}</font>'.format(
                        SELECTED_FACE_COLOR, face, SELECTED_EDGE_COLOR, edge,
                        SELECTED_VERTEX_COLOR, vertex)
                    newcols = rendered_obj.base_cols.copy()
                    newcols[face * 3:(face + 1) * 3] = np.array(
                        SELECTED_FACE_RGB, dtype='float32')
                    select_point_mesh.position = close_vert.tolist()
                    obj_geometry.attributes['color'].array = newcols

                    with out:
                        make_arrow(arrow_cyl_mesh, arrow_head_mesh,
                                   edge_start_vert, edge_end_vert, radius / 2,
                                   radius, radius * 3, SELECTED_EDGE_COLOR)

                except:
                    with out:
                        print(traceback.format_exc())

        click_picker.observe(on_dblclick, names=['point'])

        renderer_obj = pjs.Renderer(
            camera=camera,
            background='#cccc88',
            background_opacity=0,
            scene=scene,
            controls=[pjs.OrbitControls(controlling=camera), click_picker],
            width=self.width,
            height=self.height)

        display_things = [top_msg, renderer_obj, out]
        if self.draw_grids:
            s = """
<svg width="{}" height="30">
<rect width="20" height="20" x="{}" y="0" style="fill:none;stroke-width:1;stroke:rgb(0,255,0)" />
    <text x="{}" y="15">={:.1f}</text>
  Sorry, your browser does not support inline SVG.
</svg>""".format(self.width, self.width // 2, self.width // 2 + 25, space)
            display_things.append(HTML(s))

        display(VBox(display_things))
Ejemplo n.º 25
0
    def _render_stl(self, stl_file):
        vertices, faces = self._conv_stl(stl_file)

        # Map the vertex colors into the 'color' slot of the faces
        faces = [f + [None, [OBJ_COLOR for i in f], None] for f in faces]

        # Create the geometry:
        obj_geometry = pjs.Geometry(vertices=vertices,
                                    faces=faces,
                                    colors=[OBJ_COLOR] * len(vertices))
        # Calculate normals per face, for nice crisp edges:
        obj_geometry.exec_three_obj_method('computeFaceNormals')

        # Create a mesh. Note that the material need to be told to use the vertex colors.
        my_object_mesh = pjs.Mesh(
            geometry=obj_geometry,
            material=pjs.MeshLambertMaterial(vertexColors='VertexColors'),
            position=[0, 0, 0],  # Center the cube
        )

        n_vert = len(vertices)
        center = [
            sum([vertex[i] for vertex in vertices]) / float(n_vert)
            for i in range(3)
        ]
        extents = self._get_extents(vertices)
        max_delta = max([extent[1] - extent[0] for extent in extents])
        camPos = [center[i] + 4 * max_delta for i in range(3)]
        light_pos = [center[i] + (i + 3) * max_delta for i in range(3)]

        # Set up a scene and render it:
        camera = pjs.PerspectiveCamera(position=camPos,
                                       fov=20,
                                       children=[
                                           pjs.DirectionalLight(
                                               color='#ffffff',
                                               position=light_pos,
                                               intensity=0.5)
                                       ])
        camera.up = (0, 0, 1)

        scene_things = [
            my_object_mesh, camera,
            pjs.AmbientLight(color='#888888')
        ]
        if self.draw_grids:
            grids, space = self._get_grids(vertices)
            scene_things.append(grids)

        scene = pjs.Scene(children=scene_things, background=BACKGROUND_COLOR)

        renderer_obj = pjs.Renderer(
            camera=camera,
            background='#cccc88',
            background_opacity=0,
            scene=scene,
            controls=[pjs.OrbitControls(controlling=camera)],
            width=self.width,
            height=self.height)

        display_things = [renderer_obj]
        if self.draw_grids:
            s = """
<svg width="{}" height="30">
<rect width="20" height="20" x="{}" y="0" style="fill:none;stroke-width:1;stroke:rgb(0,255,0)" />
    <text x="{}" y="15">={:.1f}</text>
  Sorry, your browser does not support inline SVG.
</svg>""".format(self.width, self.width // 2, self.width // 2 + 25, space)
            display_things.append(HTML(s))

        display(*display_things)
Ejemplo n.º 26
0
    def plot(self,
             backend="pythreejs",
             width=800,
             height=500,
             background="black",
             mesh=False,
             use_as_color=["red", "green", "blue"],
             cmap="hsv",
             return_scene=False,
             output_name="pyntcloud_plot",
             polylines={}):
        """Visualize a PyntCloud  using different backends.

        Parameters
        ----------
        backend: {"pythreejs", "threejs"}, optional
            Default: "pythreejs"
            Used to select one of the available libraries for plotting.

        width: int, optional
            Default: 800

        height: int, optional
            Default: 500

        background: str, optional
            Default: "black"
            Used to select the default color of the background.
            In some backends, i.e "pythreejs" the background can be dynamically changed.

        use_as_color: str or ["red", "green", "blue"], optional
            Default: ["red", "green", "blue"]
            Indicates which scalar fields will be used to colorize the rendered
            point cloud.

        cmap: str, optional
            Default: "hsv"
            Color map that will be used to convert a single scalar field into rgb.
            Check matplotlib cmaps.

        return_scene: bool, optional
            Default: False.
            Used with "pythreejs" backend in order to return the pythreejs.Scene object

        polylines: dict, optional
            Default {}.
            Mapping hexadecimal colors to a list of list(len(3)) representing the points of the polyline.
            Example:
            polylines={
                "0xFFFFFF": [[0, 0, 0], [0, 0, 1]],
                "0xFF00FF": [[1, 0, 0], [1, 0, 1], [1, 1, 1]]
            }

        Returns
        -------
        pythreejs.Scene if return_scene else None
        """
        try:
            colors = self.points[use_as_color].values
        except KeyError:
            colors = None

        if use_as_color != ["red", "green", "blue"] and colors is not None:
            import matplotlib.pyplot as plt
            s_m = plt.cm.ScalarMappable(cmap=cmap)
            colors = s_m.to_rgba(colors)[:, :-1] * 255
        elif colors is None:
            # default color orange
            colors = np.repeat([[255, 125, 0]], self.xyz.shape[0], axis=0)

        colors = colors.astype(np.uint8)

        ptp = self.xyz.ptp()

        if backend == "pythreejs":
            import ipywidgets
            import pythreejs
            from IPython.display import display

            if mesh:
                raise NotImplementedError(
                    "Plotting mesh geometry with pythreejs backend is not supported yet."
                )

            if polylines:
                raise NotImplementedError(
                    "Plotting polylines with pythreejs backend is not supported yet."
                )

            points_geometry = pythreejs.BufferGeometry(attributes=dict(
                position=pythreejs.BufferAttribute(self.xyz, normalized=False),
                color=pythreejs.BufferAttribute(list(map(tuple, colors)))))

            points_material = pythreejs.PointsMaterial(
                vertexColors='VertexColors')

            points = pythreejs.Points(geometry=points_geometry,
                                      material=points_material,
                                      position=tuple(self.centroid))

            camera = pythreejs.PerspectiveCamera(
                fov=90,
                aspect=width / height,
                position=tuple(
                    self.centroid +
                    [0,
                     abs(self.xyz.max(0)[1]),
                     abs(self.xyz.max(0)[2]) * 2]),
                up=[0, 0, 1])

            orbit_control = pythreejs.OrbitControls(controlling=camera)
            orbit_control.target = tuple(self.centroid)

            camera.lookAt(tuple(self.centroid))

            scene = pythreejs.Scene(children=[points, camera],
                                    background=background)

            renderer = pythreejs.Renderer(scene=scene,
                                          camera=camera,
                                          controls=[orbit_control],
                                          width=width,
                                          height=height)

            display(renderer)

            size = ipywidgets.FloatSlider(min=0.,
                                          max=(ptp / 100),
                                          step=(ptp / 1000))
            ipywidgets.jslink((size, 'value'), (points_material, 'size'))

            color = ipywidgets.ColorPicker()
            ipywidgets.jslink((color, 'value'), (scene, 'background'))

            display(
                ipywidgets.HBox(children=[
                    ipywidgets.Label('Background color:'), color,
                    ipywidgets.Label('Point size:'), size
                ]))
            return scene if return_scene else None

        elif backend == "threejs":
            points = pd.DataFrame(self.xyz, columns=["x", "y", "z"])

            for n, i in enumerate(["red", "green", "blue"]):
                points[i] = colors[:, n]

            new_PyntCloud = PyntCloud(points)

            if mesh and self.mesh is not None:
                new_PyntCloud.mesh = self.mesh[["v1", "v2", "v3"]]

            return plot_PyntCloud(new_PyntCloud,
                                  IFrame_shape=(width, height),
                                  point_size=ptp / 100,
                                  point_opacity=0.9,
                                  output_name=output_name,
                                  polylines=polylines)
        else:
            raise NotImplementedError(
                "{} backend is not supported".format(backend))
Ejemplo n.º 27
0
    def __init__(self,
                 cmap=None,
                 norm=None,
                 figsize=None,
                 unit=None,
                 log=None,
                 nan_color=None,
                 masks=None,
                 pixel_size=None,
                 tick_size=None,
                 background=None,
                 show_outline=True,
                 extend=None,
                 xlabel=None,
                 ylabel=None,
                 zlabel=None):

        if figsize is None:
            figsize = (config.plot.width, config.plot.height)

        # Figure toolbar
        self.toolbar = PlotToolbar(ndim=3)

        # Prepare colormaps
        self.cmap = cmap
        self.cmap.set_bad(color=nan_color)
        self.scalar_map = cm.ScalarMappable(norm=norm, cmap=self.cmap)
        self.masks_scalar_map = None
        if len(masks) > 0:
            self.masks_cmap = masks["cmap"]
            self.masks_cmap.set_bad(color=nan_color)
            self.masks_scalar_map = cm.ScalarMappable(norm=norm,
                                                      cmap=self.masks_cmap)

        self.axlabels = {"x": xlabel, "y": ylabel, "z": zlabel}
        self.positions = None
        self.pixel_size = pixel_size
        self.tick_size = tick_size
        self.show_outline = show_outline
        self.unit = unit

        # Create the colorbar image
        self.cbar_image = ipw.Image()
        self.cbar_fig, self.cbar = self._create_colorbar(figsize, extend)

        # Create the point cloud material with pythreejs
        self.points_material = self._create_points_material()
        self.points_geometry = None
        self.point_cloud = None
        self.outline = None
        self.axticks = None
        self.camera_reset = {}

        # Define camera
        self.camera = p3.PerspectiveCamera(position=[0, 0, 0],
                                           aspect=config.plot.width /
                                           config.plot.height)

        # Add red/green/blue axes helper
        self.axes_3d = p3.AxesHelper()

        # Create the pythreejs scene
        self.scene = p3.Scene(children=[self.camera, self.axes_3d],
                              background=background)

        # Add camera controller
        self.controls = p3.OrbitControls(controlling=self.camera)

        # Render the scene into a widget
        self.renderer = p3.Renderer(camera=self.camera,
                                    scene=self.scene,
                                    controls=[self.controls],
                                    width=figsize[0],
                                    height=figsize[1])
Ejemplo n.º 28
0
def generate_3js_render(
    element_groups,
    canvas_size,
    zoom,
    camera_fov=30,
    background_color="white",
    background_opacity=1.0,
    reuse_objects=False,
    use_atom_arrays=False,
    use_label_arrays=False,
):
    """Create a pythreejs scene of the elements.

    Regarding initialisation performance, see: https://github.com/jupyter-widgets/pythreejs/issues/154
    """
    import pythreejs as pjs

    key_elements = {}
    group_elements = pjs.Group()
    key_elements["group_elements"] = group_elements

    unique_atom_sets = {}
    for el in element_groups["atoms"]:
        element_hash = (
            ("radius", el.sradius),
            ("color", el.color),
            ("fill_opacity", el.fill_opacity),
            ("stroke_color", el.get("stroke_color", "black")),
            ("ghost", el.ghost),
        )
        unique_atom_sets.setdefault(element_hash, []).append(el)

    group_atoms = pjs.Group()
    group_ghosts = pjs.Group()

    atom_geometries = {}
    atom_materials = {}
    outline_materials = {}

    for el_hash, els in unique_atom_sets.items():
        el = els[0]
        data = dict(el_hash)

        if reuse_objects:
            atom_geometry = atom_geometries.setdefault(
                el.sradius,
                pjs.SphereBufferGeometry(radius=el.sradius,
                                         widthSegments=30,
                                         heightSegments=30),
            )
        else:
            atom_geometry = pjs.SphereBufferGeometry(radius=el.sradius,
                                                     widthSegments=30,
                                                     heightSegments=30)

        if reuse_objects:
            atom_material = atom_materials.setdefault(
                (el.color, el.fill_opacity),
                pjs.MeshLambertMaterial(color=el.color,
                                        transparent=True,
                                        opacity=el.fill_opacity),
            )
        else:
            atom_material = pjs.MeshLambertMaterial(color=el.color,
                                                    transparent=True,
                                                    opacity=el.fill_opacity)

        if use_atom_arrays:
            atom_mesh = pjs.Mesh(geometry=atom_geometry,
                                 material=atom_material)
            atom_array = pjs.CloneArray(
                original=atom_mesh,
                positions=[e.position.tolist() for e in els],
                merge=False,
            )
        else:
            atom_array = [
                pjs.Mesh(
                    geometry=atom_geometry,
                    material=atom_material,
                    position=e.position.tolist(),
                    name=e.info_string,
                ) for e in els
            ]

        data["geometry"] = atom_geometry
        data["material_body"] = atom_material

        if el.ghost:
            key_elements["group_ghosts"] = group_ghosts
            group_ghosts.add(atom_array)
        else:
            key_elements["group_atoms"] = group_atoms
            group_atoms.add(atom_array)

        if el.get("stroke_width", 1) > 0:
            if reuse_objects:
                outline_material = outline_materials.setdefault(
                    el.get("stroke_color", "black"),
                    pjs.MeshBasicMaterial(
                        color=el.get("stroke_color", "black"),
                        side="BackSide",
                        transparent=True,
                        opacity=el.get("stroke_opacity", 1.0),
                    ),
                )
            else:
                outline_material = pjs.MeshBasicMaterial(
                    color=el.get("stroke_color", "black"),
                    side="BackSide",
                    transparent=True,
                    opacity=el.get("stroke_opacity", 1.0),
                )
            # TODO use stroke width to dictate scale
            if use_atom_arrays:
                outline_mesh = pjs.Mesh(
                    geometry=atom_geometry,
                    material=outline_material,
                    scale=(1.05, 1.05, 1.05),
                )
                outline_array = pjs.CloneArray(
                    original=outline_mesh,
                    positions=[e.position.tolist() for e in els],
                    merge=False,
                )
            else:
                outline_array = [
                    pjs.Mesh(
                        geometry=atom_geometry,
                        material=outline_material,
                        position=e.position.tolist(),
                        scale=(1.05, 1.05, 1.05),
                    ) for e in els
                ]

            data["material_outline"] = outline_material

            if el.ghost:
                group_ghosts.add(outline_array)
            else:
                group_atoms.add(outline_array)

        key_elements.setdefault("atom_arrays", []).append(data)

    group_elements.add(group_atoms)
    group_elements.add(group_ghosts)

    group_labels = add_labels(element_groups, key_elements, use_label_arrays)
    group_elements.add(group_labels)

    if len(element_groups["cell_lines"]) > 0:
        cell_line_mat = pjs.LineMaterial(
            linewidth=1,
            color=element_groups["cell_lines"].group_properties["color"])
        cell_line_geo = pjs.LineSegmentsGeometry(positions=[
            el.position.tolist() for el in element_groups["cell_lines"]
        ])
        cell_lines = pjs.LineSegments2(geometry=cell_line_geo,
                                       material=cell_line_mat)
        key_elements["cell_lines"] = cell_lines
        group_elements.add(cell_lines)

    if len(element_groups["bond_lines"]) > 0:
        bond_line_mat = pjs.LineMaterial(
            linewidth=element_groups["bond_lines"].
            group_properties["stroke_width"],
            vertexColors="VertexColors",
        )
        bond_line_geo = pjs.LineSegmentsGeometry(
            positions=[
                el.position.tolist() for el in element_groups["bond_lines"]
            ],
            colors=[[Color(c).rgb for c in el.color]
                    for el in element_groups["bond_lines"]],
        )
        bond_lines = pjs.LineSegments2(geometry=bond_line_geo,
                                       material=bond_line_mat)
        key_elements["bond_lines"] = bond_lines
        group_elements.add(bond_lines)

    group_millers = pjs.Group()
    if len(element_groups["miller_lines"]) or len(
            element_groups["miller_planes"]):
        key_elements["group_millers"] = group_millers

    if len(element_groups["miller_lines"]) > 0:
        miller_line_mat = pjs.LineMaterial(
            linewidth=3,
            vertexColors="VertexColors"  # TODO use stroke_width
        )
        miller_line_geo = pjs.LineSegmentsGeometry(
            positions=[
                el.position.tolist() for el in element_groups["miller_lines"]
            ],
            colors=[[Color(el.stroke_color).rgb] * 2
                    for el in element_groups["miller_lines"]],
        )
        miller_lines = pjs.LineSegments2(geometry=miller_line_geo,
                                         material=miller_line_mat)
        group_millers.add(miller_lines)

    for el in element_groups["miller_planes"]:
        vertices = el.position.tolist()
        faces = [(
            0,
            1,
            2,
            triangle_normal(vertices[0], vertices[1], vertices[2]),
            "black",
            0,
        )]
        if len(vertices) == 4:
            faces.append((
                2,
                3,
                0,
                triangle_normal(vertices[2], vertices[3], vertices[0]),
                "black",
                0,
            ))
        elif len(vertices) != 3:
            raise NotImplementedError("polygons with more than 4 points")
        plane_geom = pjs.Geometry(vertices=vertices, faces=faces)
        plane_mat = pjs.MeshBasicMaterial(
            color=el.fill_color,
            transparent=True,
            opacity=el.fill_opacity,
            side="DoubleSide",
        )
        plane_mesh = pjs.Mesh(geometry=plane_geom, material=plane_mat)
        group_millers.add(plane_mesh)

    group_elements.add(group_millers)

    scene = pjs.Scene(background=None)
    scene.add([group_elements])

    view_width, view_height = canvas_size

    minp, maxp = element_groups.get_position_range()
    # compute a minimum camera distance, that is guaranteed to encapsulate all elements
    camera_dist = maxp[2] + sqrt(maxp[0]**2 + maxp[1]**2) / tan(
        radians(camera_fov / 2))

    camera = pjs.PerspectiveCamera(
        fov=camera_fov,
        position=[0, 0, camera_dist],
        aspect=view_width / view_height,
        zoom=zoom,
    )
    scene.add([camera])
    ambient_light = pjs.AmbientLight(color="lightgray")
    key_elements["ambient_light"] = ambient_light
    direct_light = pjs.DirectionalLight(position=(maxp * 2).tolist())
    key_elements["direct_light"] = direct_light
    scene.add([camera, ambient_light, direct_light])

    camera_control = pjs.OrbitControls(controlling=camera,
                                       screenSpacePanning=True)

    atom_picker = pjs.Picker(controlling=group_atoms, event="dblclick")
    key_elements["atom_picker"] = atom_picker
    material = pjs.SpriteMaterial(
        map=create_arrow_texture(right=False),
        transparent=True,
        depthWrite=False,
        depthTest=False,
    )
    atom_pointer = pjs.Sprite(material=material,
                              scale=(4, 3, 1),
                              visible=False)
    scene.add(atom_pointer)
    key_elements["atom_pointer"] = atom_pointer

    renderer = pjs.Renderer(
        camera=camera,
        scene=scene,
        controls=[camera_control, atom_picker],
        width=view_width,
        height=view_height,
        alpha=True,
        clearOpacity=background_opacity,
        clearColor=background_color,
    )
    return renderer, key_elements
Ejemplo n.º 29
0
def mesh_animation(times, xt, faces):
    """ Animate a mesh from a sequence of mesh vertex positions

        Args:
        times   - a list of time values t_i at which the configuration x is specified
        xt      -   i.e., x(t). A list of arrays representing mesh vertex positions at times t_i.
                    Dimensions of each array should be the same as that of mesh.geometry.array
        TODO nt - n(t) vertex normals
        faces    - array of faces, with vertex loop for each face

        Side effects:
            displays rendering of mesh, with animation action

        Returns: None
        TODO renderer - THREE.Render to show the default scene
        TODO position_action - THREE.AnimationAction IPython widget
    """

    position_morph_attrs = []
    for pos in xt[
            1:]:  # xt[0] uses as the Mesh's default/initial vertex position
        position_morph_attrs.append(
            THREE.BufferAttribute(pos, normalized=False))

    # Testing mesh.geometry.morphAttributes = {'position': position_morph_attrs}
    geom = THREE.BufferGeometry(
        attributes={
            'position': THREE.BufferAttribute(xt[0], normalized=False),
            'index': THREE.BufferAttribute(faces.ravel())
        },
        morphAttributes={'position': position_morph_attrs})
    matl = THREE.MeshStandardMaterial(side='DoubleSide',
                                      color='red',
                                      wireframe=True,
                                      morphTargets=True)

    mesh = THREE.Mesh(geom, matl)

    # create key frames
    position_track = THREE.NumberKeyframeTrack(
        name='.morphTargetInfluences[0]',
        times=times,
        values=list(range(0, len(times))))
    # create animation clip from the morph targets
    position_clip = THREE.AnimationClip(tracks=[position_track])
    # create animation action
    position_action = THREE.AnimationAction(THREE.AnimationMixer(mesh),
                                            position_clip, mesh)

    # TESTING
    camera = THREE.PerspectiveCamera(position=[2, 1, 2], aspect=600 / 400)
    scene = THREE.Scene(children=[
        mesh, camera,
        THREE.AxesHelper(0.2),
        THREE.DirectionalLight(position=[3, 5, 1], intensity=0.6),
        THREE.AmbientLight(intensity=0.5)
    ])
    renderer = THREE.Renderer(
        camera=camera,
        scene=scene,
        controls=[THREE.OrbitControls(controlling=camera)],
        width=600,
        height=400)

    display(renderer, position_action)
Ejemplo n.º 30
0
def create_world_axes(camera,
                      controls,
                      initial_rotation=np.eye(3),
                      length=30,
                      width=3,
                      camera_fov=10):
    """Create a renderer, containing an axes and camera that is synced to another camera.

    adapted from http://jsfiddle.net/aqnL1mx9/

    Parameters
    ----------
    camera : pythreejs.PerspectiveCamera
    controls : pythreejs.OrbitControls
    initial_rotation : list or numpy.array
        initial rotation of the axes
    length : int
        length of axes lines
    width : int
        line width of axes

    Returns
    -------
    pythreejs.Renderer

    """
    import pythreejs as pjs

    canvas_width = length * 2
    canvas_height = length * 2

    ax_scene = pjs.Scene()

    group_ax = pjs.Group()
    # NOTE: could use AxesHelper, but this does not allow for linewidth seletion
    # TODO: add arrow heads (ArrowHelper doesn't seem to work)
    ax_line_mat = pjs.LineMaterial(linewidth=width,
                                   vertexColors="VertexColors")
    ax_line_geo = pjs.LineSegmentsGeometry(
        positions=[[[0, 0, 0], length * r / np.linalg.norm(r)]
                   for r in initial_rotation],
        colors=[[Color(c).rgb] * 2 for c in ("red", "green", "blue")],
    )
    ax_lines = pjs.LineSegments2(geometry=ax_line_geo, material=ax_line_mat)
    group_ax.add(ax_lines)

    ax_scene.add([group_ax])

    camera_dist = length / tan(radians(camera_fov / 2))

    ax_camera = pjs.PerspectiveCamera(fov=camera_fov,
                                      aspect=canvas_width / canvas_height,
                                      near=1,
                                      far=1000)
    ax_camera.up = camera.up
    ax_renderer = pjs.Renderer(
        scene=ax_scene,
        camera=ax_camera,
        width=canvas_width,
        height=canvas_height,
        alpha=True,
        clearOpacity=0.0,
        clearColor="white",
    )

    def align_axes(change=None):
        """Align axes to world."""
        # TODO: this is not working correctly for TrackballControls, when rotated upside-down
        # (OrbitControls enforces the camera up direction,
        # so does not allow the camera to rotate upside-down).
        # TODO how could this be implemented on the client (js) side?
        new_position = np.array(camera.position) - np.array(controls.target)
        new_position = camera_dist * new_position / np.linalg.norm(
            new_position)
        ax_camera.position = new_position.tolist()
        ax_camera.lookAt(ax_scene.position)

    align_axes()

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

    return ax_renderer