Repository: eth-ait/aitviewer (language: python)

Issue:
Can not run multiple viewers sequencially
With the current code, the following crashes on the last line:
 
 ```
    v = Viewer()
    v.run()
    v = Viewer()
    v.run()
```

With the error:

```
Traceback (most recent call last):
  File "analysis/joint_projection_unposed.py", line 164, in <module>
    v.run()
  File "/home/kellerm/Code/aitviewer/aitviewer/viewer.py", line 274, in run
    self._init_scene()
  File "/home/kellerm/Code/aitviewer/aitviewer/viewer.py", line 260, in _init_scene
    self.scene.make_renderable(self.ctx)
  File "/home/kellerm/Code/aitviewer/aitviewer/scene/scene.py", line 149, in make_renderable
    r.make_renderable(self.ctx)
  File "/home/kellerm/Code/aitviewer/aitviewer/scene/node.py", line 359, in _decorator
    func(self, *args, **kwargs)
  File "/home/kellerm/Code/aitviewer/aitviewer/renderables/meshes.py", line 382, in make_renderable
    self.smooth_vao = ctx.vertex_array(self.smooth_prog,
  File "/home/kellerm/Code/skeleton_inference/skel_env/lib/python3.8/site-packages/moderngl/context.py", line 1140, in vertex_array
    return self._vertex_array(*args, **kwargs)
  File "/home/kellerm/Code/skeleton_inference/skel_env/lib/python3.8/site-packages/moderngl/context.py", line 1169, in _vertex_array
    res.mglo, res._glo = self.mglo.vertex_array(program.mglo, content, index_buffer_mglo,
moderngl.error.Error: the program belongs to a different context
Segmentation fault (core dumped)
```

The problem is in `aitviewer/enderables/meshes.py : make_renderable : l.351`, the shader program is not reloaded with the new context. The old shader program instance is used and it has the context of the first viewer, hence the mismatch. 

I see that the shader program is not reloaded everytime l.351 is executed thanks to a cache @functools.lru_cache() in `aitviewer/shaders.py`. I guess this cache should be cleared in between two viewers creation.

This is the hack I have so far that fixes it:

```
def clear_shader_cache():
        # Import and list all the functions that need the cache to be cleared
        from aitviewer.shaders import get_smooth_lit_with_edges_program, get_flat_lit_with_edges_program, get_smooth_lit_texturized_program, get_simple_unlit_program, get_cylinder_program, get_screen_texture_program, get_chessboard_program
        for f in [get_smooth_lit_with_edges_program, get_flat_lit_with_edges_program, get_smooth_lit_texturized_program, get_simple_unlit_program, get_cylinder_program, get_screen_texture_program, get_chessboard_program]:
           # Try clearing the cache
            try:
                f.cache_clear()
                print(f'Clearing cache for {f}')
            except:
                pass
```

```
    #Viewer.py
    def run(self, *args, log=True):
        ...
        self.on_close()
        self.window.destroy()
        self.clear_shader_cache() # I add this line to clear the shader cache 
        ...
        
        
 With that, the following code works:
 
 ```
     for i in range(10):
        v = Viewer()
        v.run()
        # closing manually the window
 ```
        
    But still this tends to crash randomly with a segfault and no error message if I close the window too fast.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index be83979..36f3c08 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@ A set of tools to visualize and interact with sequences of 3D data with cross-pl
 ## Features
 * Easy to use Python interface.
 * Load [SMPL[-H | -X]](https://smpl.is.tue.mpg.de/) / [MANO](https://mano.is.tue.mpg.de/) / [FLAME](https://flame.is.tue.mpg.de/) sequences and display them in an interactive viewer.
+* Support for the [STAR model](https://github.com/ahmedosman/STAR).
 * Manually editable SMPL sequences.
 * Render 3D data on top of images via weak-perspective or OpenCV camera models.
 * Built-in extensible GUI (based on Dear ImGui).
@@ -57,17 +58,16 @@ View an SMPL template
 from aitviewer.renderables.smpl import SMPLSequence
 from aitviewer.viewer import Viewer
 
-smpl_template = SMPLSequence.t_pose()
-
-# Display in viewer.
-v = Viewer()
-v.scene.add(smpl_template)
-v.run()
+if __name__ == '__main__':
+    v = Viewer()
+    v.scene.add(SMPLSequence.t_pose())
+    v.run()
 ```
 
 ## Examples
 
 Check out the [examples](examples/) for a few examples how to use the viewer:
+ * [`animation.py`](examples/animation.py): Example of how 3D primitives can be animated.
 
  * [`camera_path.py`](examples/camera_path.py): Example how to use camera paths.
 
@@ -89,6 +89,8 @@ Check out the [examples](examples/) for a few examples how to use the viewer:
 
  * [`load_VIBE.py`](examples/load_VIBE.py): Loads the result of [VIBE](https://github.com/mkocabas/VIBE) and overlays it on top of the input image.
 
+ * [`missing_frames.py`](examples/missing_frames.py): Example how sequences with intermittent missing frames can be visualized.
+
  * [`quickstart.py`](examples/quickstart.py): The above quickstart example.
 
  * [`render_primitives.py`](examples/render_primitives.py): Renders a bunch of spheres and lines.
@@ -97,6 +99,30 @@ Check out the [examples](examples/) for a few examples how to use the viewer:
 
  * [`vertex_clicking.py`](examples/vertex_clicking.py): An example how to subclass the basic Viewer class for custom interaction.
 
+## Keyboard shortcuts
+
+The viewer supports the following keyboard shortcuts, all of this functionality is also accessible from the menus and windows in the GUI.
+This list can be shown directly in the viewer by clicking on the `Help -> Keyboard shortcuts` menu.
+
+- `SPACE` Start/stop playing animation.
+- `.` Go to next frame.
+- `,` Go to previous frame.
+- `X` Center view on the selected object.
+- `O` Enable/disable orthographic camera.
+- `T` Show the camera target in the scene.
+- `C` Save the camera position and orientation to disk.
+- `L` Load the camera position and orientation from disk.
+- `K` Lock the selection to the currently selected object.
+- `S` Show/hide shadows.
+- `D` Enabled/disable dark mode.
+- `P` Save a screenshot to the the `export/screenshots` directory.
+- `I` Change the viewer mode to `inspect`.
+- `V` Change the viewer mode to `view`.
+- `E` If a mesh is selected, show the edges of the mesh.
+- `F` If a mesh is selected, switch between flat and smooth shading.
+- `Z` Show a debug visualization of the object IDs.
+- `ESC` Exit the viewer.
+
 ## Projects using the AITViewer
 The following projects have used the AITViewer:
 - Dong et al., [Shape-aware Multi-Person Pose Estimation from Multi-view Images](https://ait.ethz.ch/projects/2021/multi-human-pose/), ICCV 2021
diff --git a/aitviewer/aitvconfig.yaml b/aitviewer/aitvconfig.yaml
index 1425cdb..a1873eb 100644
--- a/aitviewer/aitvconfig.yaml
+++ b/aitviewer/aitvconfig.yaml
@@ -8,6 +8,7 @@ datasets:
     smplx:
       "../data/3dpw_smplx_neutral_pkl"
 smplx_models: "../data/smplx_models"
+star_models: "../data/star_models"
 
 # Pytorch configuration.
 device: "cuda:0"
@@ -35,6 +36,6 @@ auto_set_floor: True
 auto_set_camera_target: True
 backface_culling: True
 background_color: [1.0, 1.0, 1.0, 1.0]
-
+window_type: "pyqt5"
 
 
diff --git a/aitviewer/headless.py b/aitviewer/headless.py
index 7fca067..40c419a 100644
--- a/aitviewer/headless.py
+++ b/aitviewer/headless.py
@@ -14,7 +14,10 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <https://www.gnu.org/licenses/>.
 """
+import os
+
 from aitviewer.viewer import Viewer
+from PIL.Image import Image
 
 
 class HeadlessRenderer(Viewer):
@@ -59,3 +62,59 @@ def save_frame(self, file_path, scale_factor: float = None):
         """
         self._init_scene()
         self.export_frame(file_path, scale_factor)
+
+    def save_depth(self, file_path):
+        """
+        Render and save the depth buffer, see 'get_depth()' for more information
+        about the depth format.
+        :param file_path: the path where the image is saved. The file is used by PIL to choose
+        the file format, make sure that you use a format that supports 'F' mode PIL Images (e.g. tiff).
+        """
+        os.makedirs(os.path.dirname(file_path), exist_ok=True)
+        self.get_depth().save(file_path)
+
+    def save_mask(self, file_path):
+        """
+        Render and save a color mask as a 'RGB' PIL image.
+        :param file_path: the path where the image is saved.
+        """
+        os.makedirs(os.path.dirname(file_path), exist_ok=True)
+        self.get_mask().save(file_path)
+
+    def _render_frame(self):
+        self._init_scene()
+
+        # Store run_animation old value and set it to false.
+        run_animations = self.run_animations
+        self.run_animations = False
+
+        # Render frame.
+        self.render(0, 0, export=True)
+
+        # Restore run animation and update last frame rendered time.
+        self.run_animations = run_animations
+
+    def get_frame(self) -> Image:
+        """Render and return a single frame as a 'RGB' PIL image"""
+        self._render_frame()
+        return self.get_current_frame_as_image()
+
+    def get_depth(self) -> Image:
+        """
+        Render and return the depth buffer as a 'F' PIL image.
+        Depth is stored as the z coordinate in eye (view) space.
+        Therefore values in the depth image represent the distance from the pixel to
+        the plane passing through the camera and orthogonal to the view direction.
+        Values are between the near and far plane distances of the camera used for rendering,
+        everything outside this range is clipped by OpenGL.
+        """
+        self._render_frame()
+        return self.get_current_depth_image()
+
+    def get_mask(self) -> Image:
+        """
+        Render and return a color mask as a 'RGB' PIL image. Each object in the mask
+        has a uniform color computed as an hash of the Node uid.
+        """
+        self._render_frame()
+        return self.get_current_mask_image()
diff --git a/aitviewer/models/smpl.py b/aitviewer/models/smpl.py
index 247aaa6..12f3c38 100644
--- a/aitviewer/models/smpl.py
+++ b/aitviewer/models/smpl.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
diff --git a/aitviewer/models/star.py b/aitviewer/models/star.py
new file mode 100644
index 0000000..343f08d
--- /dev/null
+++ b/aitviewer/models/star.py
@@ -0,0 +1,143 @@
+"""
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+"""
+import collections
+import os
+import torch
+import torch.nn.functional as F
+
+try:
+    from star.pytorch.star import STAR
+    from star.config import cfg
+except Exception as e:
+    raise ImportError(f"Cannot import STAR. Please run `pip install git+https://github.com/ahmedosman/STAR.git`\n{e}")
+
+from aitviewer.configuration import CONFIG as C
+from aitviewer.utils.so3 import aa2rot_torch as aa2rot
+from aitviewer.utils.so3 import rot2aa_torch as rot2aa
+
+
+class STARLayer(STAR):
+    """Wraps the publicly available STAR model to match SMPLX model interface"""
+
+    def __init__(self, gender='male', num_betas=300, device=C.device, dtype=C.f_precision):
+        """
+        Initializer.
+        :param gender: Which gender to load.
+        :param num_betas: Number of shape components. Keep in mind STAR and SMPL shape components are not the same
+        (i.e. the first 10 components of STAR cannot simply be assigned from the first 10 components of SMPL)
+        :param device: CPU or GPU.
+        :param dtype: The pytorch floating point data type.
+        """
+
+        # Configure STAR model before initializing
+        cfg.data_type = dtype
+        cfg.path_male_star = os.path.join(C.star_models, "male/model.npz")
+        cfg.path_female_star = os.path.join(C.star_models, "female/model.npz")
+        cfg.path_neutral_star = os.path.join(C.star_models, "neutral/model.npz")
+
+        super(STARLayer, self).__init__(gender=gender, num_betas=num_betas)
+
+        self.device = device
+        self.model_type = 'star'
+        self._parents = None
+        self._children = None
+
+    @property
+    def parents(self):
+        """Return how the joints are connected in the kinematic chain where parents[i, 0] is the parent of
+        joint parents[i, 1]."""
+        if self._parents is None:
+            self._parents = self.kintree_table.transpose(0, 1).cpu().numpy()
+        return self._parents
+
+    @property
+    def joint_children(self):
+        """Return the children of each joint in the kinematic chain."""
+        if self._children is None:
+            self._children = collections.defaultdict(list)
+            for bone in self.parents:
+                if bone[0] != -1:
+                    self._children[bone[0]].append(bone[1])
+        return self._children
+
+    def skeletons(self):
+        """Return how the joints are connected in the kinematic chain where skeleton[0, i] is the parent of
+        joint skeleton[1, i]."""
+        kintree_table = self.kintree_table
+        kintree_table[:, 0] = -1
+        return {'all': kintree_table, 'body': kintree_table[:, :self.n_joints_body + 1]}
+
+    @property
+    def n_joints_body(self):
+        return self.parent.shape[0]
+
+    @property
+    def n_joints_total(self):
+        return self.n_joints_body + 1
+
+    def forward(self, poses_body, betas=None, poses_root=None, trans=None, normalize_root=False):
+        """
+        forwards the model
+        :param poses_body: Pose parameters.
+        :param poses_root: Pose parameters for the root joint.
+        :param beta: Beta parameters.
+        :param trans: Root translation.
+        :param normalize_root: Makes poses relative to the root joint (useful for globally rotated captures).
+        :return: Deformed surface vertices, transformed joints
+        """
+        poses, betas, trans = self.preprocess(poses_body, betas, poses_root, trans, normalize_root)
+
+        v = super().forward(pose=poses, betas=betas, trans=trans)
+        J = v.J_transformed
+        return v, J
+
+    def preprocess(self, poses_body, betas=None, poses_root=None, trans=None, normalize_root=False):
+        batch_size = poses_body.shape[0]
+
+        if poses_root is None:
+            poses_root = torch.zeros([batch_size, 3]).to(dtype=poses_body.dtype, device=self.device)
+        if trans is None:
+            # If we don't supply the root translation explicitly, it falls back to using self.bm.trans
+            # which might not be zero since it is a trainable param that can get updated.
+            trans = torch.zeros([batch_size, 3]).to(dtype=poses_body.dtype, device=self.device)
+
+        if normalize_root:
+            # Make everything relative to the first root orientation.
+            root_ori = aa2rot(poses_root)
+            first_root_ori = torch.inverse(root_ori[0:1])
+            root_ori = torch.matmul(first_root_ori, root_ori)
+            poses_root = rot2aa(root_ori)
+            trans = torch.matmul(first_root_ori.unsqueeze(0), trans.unsqueeze(-1)).squeeze()
+            trans = trans - trans[0:1]
+
+        poses = torch.cat((poses_root, poses_body), dim=1)
+
+        if betas is None:
+            betas = torch.zeros([batch_size, self.num_betas]).to(dtype=poses_body.dtype, device=self.device)
+
+        # Batch shapes if they don't match batch dimension.
+        if betas.shape[0] != batch_size:
+            betas = betas.repeat(batch_size, 1)
+
+        # Lower bound betas
+        if betas.shape[1] < self.num_betas:
+            betas = torch.nn.functional.pad(betas, [0, self.num_betas - betas.shape[1]])
+
+        # Upper bound betas
+        betas = betas[:, :self.num_betas]
+
+        return poses, betas, trans
diff --git a/aitviewer/renderables/arrows.py b/aitviewer/renderables/arrows.py
index 0b92048..050fdbd 100644
--- a/aitviewer/renderables/arrows.py
+++ b/aitviewer/renderables/arrows.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -66,21 +66,33 @@ def __init__(self,
 
         self._add_nodes(self.bases_r, self.arrows_r, show_in_hierarchy=False)
 
+    @property
+    def bounds(self):
+        return self.arrows_r.bounds
+
+    @property
+    def current_bounds(self):
+        return self.arrows_r.current_bounds
+
     @property
     def current_origins(self):
-        return self.origins[self.current_frame_id]
+        idx = self.current_frame_id if self.origins.shape[0] > 1 else 0
+        return self.origins[idx]
 
     @current_origins.setter
     def current_origins(self, origins):
-        self.origins[self.current_frame_id] = origins
+        idx = self.current_frame_id if self.origins.shape[0] > 1 else 0
+        self.origins[idx] = origins
 
     @property
     def current_tips(self):
-        return self.tips[self.current_frame_id]
+        idx = self.current_frame_id if self.tips.shape[0] > 1 else 0
+        return self.tips[idx]
 
     @current_tips.setter
     def current_tips(self, tips):
-        self.tips[self.current_frame_id] = tips
+        idx = self.current_frame_id if self.tips.shape[0] > 1 else 0
+        self.tips[idx] = tips
 
     @property
     def mid_points(self):
diff --git a/aitviewer/renderables/billboard.py b/aitviewer/renderables/billboard.py
index f56ddd9..6619a45 100644
--- a/aitviewer/renderables/billboard.py
+++ b/aitviewer/renderables/billboard.py
@@ -35,14 +35,17 @@ def __init__(self,
                  vertices,
                  texture_paths,
                  img_process_fn=None,
+                 icon="\u0096",
                  **kwargs):
         """ Initializer.
         :param vertices:
             A np array of 4 billboard vertices in world space coordinates of shape (4, 3)
             or an array of shape (N, 4, 3) containing 4 vertices for each frame of the sequence
         :param texture_paths: A list of length N containing paths to the textures as image files.
+        :param img_process_fn: A function with signature f(img, current_frame_id) -> img. This function is called
+            once per image before it is displayed so it can be used to process the image in any way.
         """
-        super(Billboard, self).__init__(n_frames=len(texture_paths), **kwargs)
+        super(Billboard, self).__init__(n_frames=len(texture_paths), icon=icon, **kwargs)
 
         if len(vertices.shape) == 2:
             vertices = vertices[np.newaxis]
@@ -52,7 +55,7 @@ def __init__(self,
         center = np.mean(vertices, axis=(0, 1))
         self.vertices = vertices - center
         self.position = center
-        self.img_process_fn = (lambda img: img) if img_process_fn is None else img_process_fn
+        self.img_process_fn = (lambda img, _: img) if img_process_fn is None else img_process_fn
 
         # Tile the uv buffer to match the size of the vertices buffer,
         # we do this so that we can use the same vertex array for all draws
@@ -141,7 +144,7 @@ def transform(x):
 
         if image_process_fn is None:
             if isinstance(camera, OpenCVCamera) and (camera.dist_coeffs is not None):
-                def undistort(img):
+                def undistort(img, current_frame_id):
                     return cv2.undistort(img, camera.current_K, camera.dist_coeffs)
                 image_process_fn = undistort
 
@@ -172,7 +175,7 @@ def render(self, camera, **kwargs):
                 img = pickle.load(open(path, "rb"))
             else:
                 img = cv2.cvtColor(cv2.flip(cv2.imread(path), 0), cv2.COLOR_BGR2RGB)
-            img = self.img_process_fn(img)
+            img = self.img_process_fn(img, self.current_frame_id)
             self.texture = self.ctx.texture((img.shape[1], img.shape[0]), img.shape[2], img.tobytes())
             self._current_texture_id = self.current_frame_id
 
@@ -180,7 +183,7 @@ def render(self, camera, **kwargs):
         self.prog['texture0'].value = 0
         self.texture.use(0)
 
-        mvp = camera.get_view_projection_matrix() @ self.model_matrix()
+        mvp = camera.get_view_projection_matrix() @ self.model_matrix
         self.prog['mvp'].write(mvp.T.astype("f4").tobytes())
 
         # Compute the index of the first vertex to use if we have a sequence of vertices of length > 1
@@ -204,17 +207,27 @@ def release(self):
             if self.texture:
                 self.texture.release()
 
+    @property
+    def current_vertices(self):
+        return self.vertices[0] if self.vertices.shape[0] <= 1 else self.vertices[self.current_frame_id]
+
+    @property
+    def bounds(self):
+        return self.get_bounds(self.vertices)
+
+    @property
+    def current_bounds(self):
+        return self.get_bounds(self.current_vertices)
+
     def is_transparent(self):
         return self.texture_alpha < 1.0
 
     def closest_vertex_in_triangle(self, tri_id, point):
-        vertices = self.vertices[0] if self.vertices.shape[0] <= 1 else self.vertices[self.current_frame_id]
-        return np.linalg.norm((vertices - point), axis=-1).argmin()
+        return np.linalg.norm((self.current_vertices - point), axis=-1).argmin()
 
     def get_bc_coords_from_points(self, tri_id, points):
-        vertices = self.vertices[0] if self.vertices.shape[0] <= 1 else self.vertices[self.current_frame_id]
         indices = np.array([ [0, 1, 2], [1, 2, 3] ])
-        return points_to_barycentric(vertices[indices[[tri_id]]], points)[0]
+        return points_to_barycentric(self.current_vertices[indices[[tri_id]]], points)[0]
 
     def gui_material(self, imgui, show_advanced=True):
         _, self.texture_alpha = imgui.slider_float('Texture alpha##texture_alpha{}'.format(self.unique_name),
diff --git a/aitviewer/renderables/bounding_boxes.py b/aitviewer/renderables/bounding_boxes.py
index f5c3553..f4ac783 100644
--- a/aitviewer/renderables/bounding_boxes.py
+++ b/aitviewer/renderables/bounding_boxes.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -50,8 +50,7 @@ def __init__(self,
 
         self.vertices = vertices
 
-        self.lines = Lines(lines=self._get_line_coords(), mode='lines', r_base=thickness, color=self.color,
-                           position=self.position)
+        self.lines = Lines(lines=self._get_line_coords(), mode='lines', r_base=thickness, color=self.color)
         self.spheres = Spheres(positions=self.vertices, radius=thickness, color=self.color)
         self._add_nodes(self.lines, self.spheres, show_in_hierarchy=False)
 
diff --git a/aitviewer/renderables/coordinate_system.py b/aitviewer/renderables/coordinate_system.py
index f9ecd48..0316b9e 100644
--- a/aitviewer/renderables/coordinate_system.py
+++ b/aitviewer/renderables/coordinate_system.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -16,23 +16,19 @@
 """
 import numpy as np
 
-from aitviewer.scene.node import Node
 from aitviewer.renderables.rigid_bodies import RigidBodies
 
 
-class CoordinateSystem(Node):
+class CoordinateSystem(RigidBodies):
     """
     Render a coordinate system using shaded cylinders.
     """
 
     def __init__(self,
                  length=1.0,
-                 icon="\u0086",
+                 icon="\u008a",
                  **kwargs):
-        super(CoordinateSystem, self).__init__(icon=icon, **kwargs)
-
         r = length / 50
         l = length
-
-        self.rb = RigidBodies(np.array([[[0.0, 0.0, 0.0]]]), np.eye(3)[np.newaxis, np.newaxis], radius=r, length=l)
-        self.add(self.rb)
+        super(CoordinateSystem, self).__init__(np.array([[[0.0, 0.0, 0.0]]]), np.eye(3)[np.newaxis, np.newaxis],
+                                               radius=r, length=l, icon=icon, **kwargs)
diff --git a/aitviewer/renderables/lines.py b/aitviewer/renderables/lines.py
index 0f49930..038f260 100644
--- a/aitviewer/renderables/lines.py
+++ b/aitviewer/renderables/lines.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -244,9 +244,15 @@ def __init__(self,
         vs, fs, ns = self.get_mesh()
         material = kwargs.get('material', Material(color=color, ambient=0.2))
         self.mesh = Meshes(vs, fs, ns, color=color, material=material, cast_shadow=cast_shadow, is_selectable=False)
-        self.mesh.position = self.position
-        self.mesh.rotation = self.rotation
-        self.add(self.mesh, has_gui=True, show_in_hierarchy=False)
+        self.add(self.mesh, show_in_hierarchy=False)
+
+    @property
+    def bounds(self):
+        return self.mesh.bounds
+
+    @property
+    def current_bounds(self):
+        return self.mesh.current_bounds
 
     @property
     def lines(self):
@@ -258,12 +264,14 @@ def lines(self, value):
 
     @property
     def current_lines(self):
-        return self._lines[self.current_frame_id]
+        idx = self.current_frame_id if self._lines.shape[0] > 1 else 0
+        return self._lines[idx]
 
     @current_lines.setter
     def current_lines(self, lines):
         assert len(lines.shape) == 2
-        self._lines[self.current_frame_id] = lines
+        idx = self.current_frame_id if self._lines.shape[0] > 1 else 0
+        self._lines[idx] = lines
 
     def gui(self, imgui):
         self.mesh.gui(imgui)
diff --git a/aitviewer/renderables/meshes.py b/aitviewer/renderables/meshes.py
index 906ce74..5cc8e5c 100644
--- a/aitviewer/renderables/meshes.py
+++ b/aitviewer/renderables/meshes.py
@@ -14,10 +14,9 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <https://www.gnu.org/licenses/>.
 """
-import os
-
 import moderngl
 import numpy as np
+import os
 import trimesh
 import tqdm
 import re
@@ -27,14 +26,15 @@
 from aitviewer.shaders import get_smooth_lit_with_edges_program
 from aitviewer.shaders import get_flat_lit_with_edges_program
 from aitviewer.shaders import get_smooth_lit_texturized_program
-from aitviewer.utils.decorators import hooked
-from aitviewer.utils.utils import compute_vertex_and_face_normals
 from aitviewer.utils import set_lights_in_program
 from aitviewer.utils import set_material_properties
+from aitviewer.utils.decorators import hooked
+from aitviewer.utils.so3 import euler2rot_numpy, rot2euler_numpy
+from aitviewer.utils.utils import compute_vertex_and_face_normals
 from functools import lru_cache
 from moderngl_window.opengl.vao import VAO
-from trimesh.triangles import points_to_barycentric
 from PIL import Image
+from trimesh.triangles import points_to_barycentric
 
 
 class Meshes(Node):
@@ -49,12 +49,12 @@ def __init__(self,
                  face_colors=None,
                  uv_coords=None,
                  path_to_texture=None,
-                 texture_alpha=1.0,
                  cast_shadow=True,
                  pickable=True,
                  flat_shading=False,
                  draw_edges=False,
                  draw_outline=False,
+                 icon="\u008d",
                  **kwargs):
         """
         Initializer.
@@ -68,13 +68,12 @@ def __init__(self,
         :param face_colors: A np array of shape (N, F, 4) overriding the uniform or vertex colors.
         :param uv_coords: A np array of shape (V, 2) if the mesh is to be textured.
         :param path_to_texture: Path to an image file that serves as the texture.
-        :param texture_alpha: Set transparency for texture.
         """
         if len(vertices.shape) == 2 and vertices.shape[-1] == 3:
             vertices = vertices[np.newaxis]
         assert len(vertices.shape) == 3
         assert len(faces.shape) == 2
-        super(Meshes, self).__init__(n_frames=vertices.shape[0], **kwargs)
+        super(Meshes, self).__init__(n_frames=vertices.shape[0], icon=icon, **kwargs)
 
         self._vertices = vertices
         self.faces = faces.astype(np.int32)
@@ -99,7 +98,6 @@ def _maybe_unsqueeze(x):
                 self.texture_image = Image.open(path_to_texture).transpose(method=Image.FLIP_TOP_BOTTOM).convert("RGB")
         else:
             self.texture_image = None
-        self.texture_alpha = texture_alpha
 
         # Enable rendering passes
         self.cast_shadow = cast_shadow
@@ -115,7 +113,7 @@ def _maybe_unsqueeze(x):
         self.norm_coloring = False
         self.normals_r = None
         self.need_upload = True
-        self._use_uniform_color = self.vertex_colors is None
+        self._use_uniform_color = self._vertex_colors is None
 
     @property
     def vertices(self):
@@ -137,11 +135,13 @@ def vertices(self, vertices):
 
     @property
     def current_vertices(self):
-        return self.vertices[self.current_frame_id]
+        idx = self.current_frame_id if self.vertices.shape[0] > 1 else 0
+        return self.vertices[idx]
 
     @current_vertices.setter
     def current_vertices(self, vertices):
-        self._vertices[self.current_frame_id] = vertices
+        idx = self.current_frame_id if self.vertices.shape[0] > 1 else 0
+        self._vertices[idx] = vertices
         self.compute_vertex_and_face_normals.cache_clear()
         self.redraw()
 
@@ -230,7 +230,8 @@ def current_vertex_colors(self):
         if self._use_uniform_color:
             return np.full((self.n_vertices, 4), self.material.color)
         else:
-            return self.vertex_colors[self.current_frame_id]
+            idx = self.current_frame_id if self.vertex_colors.shape[0] > 1 else 0
+            return self.vertex_colors[idx]
 
     @property
     def face_colors(self):
@@ -280,7 +281,7 @@ def compute_vertex_and_face_normals(self, frame_id, normalize=False):
           enforce unit length of normals anyway.
         :return: The vertex and face normals as a np arrays of shape (V, 3) and (F, 3) respectively.
         """
-        vs = self.vertices[frame_id:frame_id + 1]
+        vs = self.vertices[frame_id:frame_id + 1] if self.vertices.shape[0] > 1 else self.vertices
         vn, fn = compute_vertex_and_face_normals(vs, self.faces, self.vertex_faces, normalize)
         return vn.squeeze(0), fn.squeeze(0)
 
@@ -288,11 +289,12 @@ def compute_vertex_and_face_normals(self, frame_id, normalize=False):
     def bounds(self):
         return self.get_bounds(self.vertices)
 
+    @property
+    def current_bounds(self):
+        return self.get_bounds(self.current_vertices)
+
     def is_transparent(self):
-        if self.has_texture and self.show_texture:
-            return self.texture_alpha < 1.0
-        else:
-            return self.color[3] < 1.0
+        return self.color[3] < 1.0
 
     def on_frame_update(self):
         """Called whenever a new frame must be displayed."""
@@ -381,6 +383,7 @@ def make_renderable(self, ctx):
             self.texture_vao = ctx.vertex_array(self.texture_prog,
                                                 [(self.vbo_vertices, '3f4 /v', 'in_position'),
                                                  (self.vbo_normals, '3f4 /v', 'in_normal'),
+                                                 (self.vbo_colors, '4f4 /v', 'in_color'),
                                                  (self.vbo_uvs, '2f4 /v', 'in_uv')],
                                                 self.vbo_indices)
 
@@ -404,8 +407,6 @@ def release(self):
 
     def render(self, camera, **kwargs):
         # Check if flat shading changed, in which case we need to update the VBOs.
-        if self.has_texture:
-            self.texture_prog['texture_alpha'].value = self.texture_alpha
         vao = self._prepare_vao(camera, **kwargs)
         vao.render(moderngl.TRIANGLES)
 
@@ -425,9 +426,9 @@ def _prepare_vao(self, camera, **kwargs):
         else:
             prog, vao = (self.flat_prog, self.flat_vao) if self.flat_shading else (self.smooth_prog, self.smooth_vao)
             prog['norm_coloring'].value = self.norm_coloring
-            prog['use_uniform_color'] = self._use_uniform_color
-            prog['uniform_color'] = self.material.color
 
+        prog['use_uniform_color'] = self._use_uniform_color
+        prog['uniform_color'] = self.material.color
         prog['draw_edges'].value = 1.0 if self.draw_edges else 0.0
         prog['win_size'].value = kwargs['window_size']
 
@@ -444,41 +445,31 @@ def _show_normals(self):
         bounds = self.bounds
         diag = np.linalg.norm(bounds[:, 0] - bounds[:, 1])
 
-        length = 0.005 * max(diag, 1)
+        length = 0.005 * max(diag, 1) / self.scale
         vn = vn / np.linalg.norm(vn, axis=-1, keepdims=True) * length
 
         # Must import here because if we do it at the top we create a circular dependency.
         from aitviewer.renderables.arrows import Arrows
 
-        positions = self.vertices * self.scale
+        positions = self.vertices
         self.normals_r = Arrows(positions, positions + vn,
                                 r_base=length / 10, r_head=2 * length / 10, p=0.25, name='Normals')
-        self.normals_r.position = self.position
-        self.normals_r.rotation = self.rotation
         self.normals_r.current_frame_id = self.current_frame_id
-        self.add(self.normals_r, gui_elements=['material'])
+        self.add(self.normals_r)
 
     def gui(self, imgui):
         super(Meshes, self).gui(imgui)
 
-        if self.has_texture:
-            _, self.texture_alpha = imgui.slider_float('Texture alpha##texture_alpha{}'.format(self.unique_name),
-                                                       self.texture_alpha, 0.0, 1.0, '%.2f')
         _, self.show_texture = imgui.checkbox('Render Texture##render_texture{}'.format(self.unique_name),
                                               self.show_texture)
         _, self.norm_coloring = imgui.checkbox('Norm Coloring##norm_coloring{}'.format(self.unique_name),
                                                self.norm_coloring)
         _, self.flat_shading = imgui.checkbox('Flat shading [F]##flat_shading{}'.format(self.unique_name),
-                                               self.flat_shading)
+                                              self.flat_shading)
         _, self.draw_edges = imgui.checkbox('Draw edges [E]##draw_edges{}'.format(self.unique_name),
-                                               self.draw_edges)
+                                            self.draw_edges)
         _, self.draw_outline = imgui.checkbox('Draw outline##draw_outline{}'.format(self.unique_name),
-                                               self.draw_outline)
-
-        # TODO: Add  export workflow for all nodes
-        if imgui.button('Export OBJ##export_{}'.format(self.unique_name)):
-            mesh = trimesh.Trimesh(vertices=self.current_vertices, faces=self.faces, process=False)
-            mesh.export('../export/' + self.name + '.obj')
+                                              self.draw_outline)
 
         if self.normals_r is None:
             if imgui.button('Show Normals ##show_normals{}'.format(self.unique_name)):
@@ -489,6 +480,17 @@ def gui_context_menu(self, imgui):
         _, self.draw_edges = imgui.menu_item("Draw edges", "E", selected=self.draw_edges, enabled=True)
         _, self.draw_outline = imgui.menu_item("Draw outline", selected=self.draw_outline)
 
+    def gui_io(self, imgui):
+        if imgui.button('Export OBJ##export_{}'.format(self.unique_name)):
+            mesh = trimesh.Trimesh(vertices=self.current_vertices, faces=self.faces, process=False)
+            mesh.export('../export/' + self.name + '.obj')
+
+    def key_event(self, key, wnd_keys):
+        if key == wnd_keys.F:
+            self.flat_shading = not self.flat_shading
+        elif key == wnd_keys.E:
+            self.draw_edges = not self.draw_edges
+
 
 class VariableTopologyMeshes(Node):
     """
@@ -513,8 +515,10 @@ def __init__(self,
         :param faces: A list of length N with np arrays of shape (F_n, 3).
         :param vertex_normals: An optional list of length N with np arrays of shape (V_n, 3).
         :param face_normals: An optional list of length N with np arrays of shape (F_n, 3).
-        :param vertex_colors: An optional list of length N with np arrays of shape (V_n, 4) overriding the uniform color.
-        :param face_colors: An optional list of length N with np arrays of shape (F_n, 4) overriding the uniform or vertex colors.
+        :param vertex_colors: An optional list of length N with np arrays of shape (V_n, 4) overriding the
+          uniform color.
+        :param face_colors: An optional list of length N with np arrays of shape (F_n, 4) overriding the uniform
+          or vertex colors.
         :param uv_coords: An optional list of length N with np arrays of shape (V_n, 2) if the mesh is to be textured.
         :param texture_paths: An optional list of length N containing paths to the texture as an image file.
         :param preload: Whether or not to pre-load all the meshes. This increases loading time and memory consumption,
@@ -695,8 +699,7 @@ def current_mesh(self):
                 m = self._construct_mesh_at_frame(self.current_frame_id)
 
                 # Set mesh position and scale
-                m.position = self.position
-                m.scale = self.scale
+                m.update_transform(self.model_matrix)
 
                 # Set mesh material
                 m.material = self.material
@@ -715,7 +718,12 @@ def current_mesh(self):
 
     @property
     def bounds(self):
-        return self.current_mesh.get_bounds(self.current_mesh.vertices)
+        # TODO: this currently only returns the current mesh bounds for performance/simplicity.
+        return self.current_mesh.bounds
+
+    @property
+    def current_bounds(self):
+        return self.current_mesh.current_bounds
 
     def closest_vertex_in_triangle(self, tri_id, point):
         return self.current_mesh.closest_vertex_in_triangle(tri_id, point)
@@ -763,32 +771,32 @@ def gui_context_menu(self, imgui):
         _, self.draw_edges = imgui.menu_item("Draw edges", "E", selected=self.draw_edges, enabled=True)
         _, self.draw_outline = imgui.menu_item("Draw outline", selected=self.draw_outline)
 
-    def gui_position(self, imgui):
+    def gui_affine(self, imgui):
+        """ Render GUI for affine transformations"""
         # Position controls
-        u, pos = imgui.drag_float3('Position##pos{}'.format(self.unique_name), *self.position, 0.1, format='%.2f')
-        if u:
+        up, pos = imgui.drag_float3('Position##pos{}'.format(self.unique_name), *self.position, 0.1, format='%.2f')
+        if up:
             self.position = pos
-            # If meshes are already loaded go through all and update the positions
-            if self.preload:
-                for m in self._all_meshes:
-                    m.position = pos
-            # Otherwise only update the current mesh, other meshes will be updated when loaded
-            else:
-                self.current_mesh.position = pos
 
-    def gui_scale(self, imgui):
+        # Rotation controls
+        euler_angles = rot2euler_numpy(self.rotation[np.newaxis], degrees=True)[0]
+        ur, euler_angles = imgui.drag_float3('Rotation##pos{}'.format(self.unique_name), *euler_angles, 0.1,
+                                             format='%.2f')
+        if ur:
+            self.rotation = euler2rot_numpy(np.array(euler_angles)[np.newaxis], degrees=True)[0]
+
         # Scale controls
-        u, scale = imgui.drag_float('Scale##scale{}'.format(self.unique_name), self.scale, 0.01, min_value=0.001,
-                                    max_value=10.0, format='%.3f')
-        if u:
+        us, scale = imgui.drag_float('Scale##scale{}'.format(self.unique_name), self.scale, 0.01, min_value=0.001,
+                                     max_value=10.0, format='%.3f')
+        if us:
             self.scale = scale
-            # If meshes are already loaded go through all and update the scale factors
+
+        if up or ur or us:
             if self.preload:
                 for m in self._all_meshes:
-                    m.scale = scale
-            # Otherwise only update the current mesh, other meshes will be updated when loaded
+                    m.update_transform(self.model_matrix)
             else:
-                self.current_mesh.scale = scale
+                self.current_mesh.update_transform((self.model_matrix))
 
     def gui_material(self, imgui, show_advanced=True):
         # Color Control
@@ -804,7 +812,7 @@ def gui_material(self, imgui, show_advanced=True):
                 self.current_mesh.color = color
                 self._override_color = True
 
-        _, self.show_texture  = imgui.checkbox('Render Texture', self.show_texture)
+        _, self.show_texture = imgui.checkbox('Render Texture', self.show_texture)
         _, self.norm_coloring = imgui.checkbox('Norm Coloring', self.norm_coloring)
         _, self.flat_shading = imgui.checkbox('Flat shading [F]', self.flat_shading)
         _, self.draw_edges = imgui.checkbox('Draw edges [E]', self.draw_edges)
@@ -843,6 +851,12 @@ def gui_material(self, imgui, show_advanced=True):
 
                 imgui.tree_pop()
 
+    def key_event(self, key, wnd_keys):
+        if key == wnd_keys.F:
+            self.flat_shading = not self.flat_shading
+        elif key == wnd_keys.E:
+            self.draw_edges = not self.draw_edges
+
     @hooked
     def release(self):
         if self.preload:
@@ -851,4 +865,4 @@ def release(self):
         else:
             m = self._current_mesh.get(self.current_frame_id, None)
             if m is not None:
-                m.release()
\ No newline at end of file
+                m.release()
diff --git a/aitviewer/renderables/multi_view_system.py b/aitviewer/renderables/multi_view_system.py
index af599f6..15166e0 100644
--- a/aitviewer/renderables/multi_view_system.py
+++ b/aitviewer/renderables/multi_view_system.py
@@ -72,7 +72,8 @@ def __init__(self, camera_info_path, camera_images_path, cols, rows, viewer, **k
             extrinsics = camera_info['extrinsics'][i]
             dist_coeffs = camera_info['dist_coeffs'][i]
 
-            camera = OpenCVCamera(intrinsics, extrinsics, cols, rows, dist_coeffs=dist_coeffs, viewer=viewer, is_selectable=False)
+            camera = OpenCVCamera(intrinsics, extrinsics, cols, rows, dist_coeffs=dist_coeffs, viewer=viewer,
+                                  is_selectable=False)
             self.add(camera, show_in_hierarchy=False)
             self.cameras.append(camera)
 
@@ -84,6 +85,7 @@ def __init__(self, camera_info_path, camera_images_path, cols, rows, viewer, **k
         camera_center = np.mean(positions, 0)
         max_dist = np.max(np.apply_along_axis(lambda x: np.linalg.norm(x - camera_center), 1, positions))
         self.billboard_distance = max_dist * 2
+        self.camera_positions = positions
 
         self.viewer = viewer
         self.camera_info = camera_info
@@ -122,7 +124,8 @@ def sort_key(x):
             return
 
         # Create a new billboard for the currently active camera.
-        billboard = Billboard.from_camera_and_distance(self.cameras[camera_index], self.billboard_distance, self.cols, self.rows, paths)
+        billboard = Billboard.from_camera_and_distance(self.cameras[camera_index], self.billboard_distance, self.cols,
+                                                       self.rows, paths)
 
         # Set the current frame index if we have an image for it.
         if self.current_frame_id < len(paths):
@@ -234,6 +237,14 @@ def cameras_enabled(self, enabled):
         for c in self.cameras:
             c.enabled = enabled
 
+    @property
+    def bounds(self):
+        return self.get_bounds(self.camera_positions)
+
+    @property
+    def current_bounds(self):
+        return self.bounds
+
     def _gui_checkboxes(self, imgui):
         _, self.cameras_enabled = imgui.checkbox("Show cameras", self.cameras_enabled)
         _, self.billboards_enabled = imgui.checkbox("Show billboard", self.billboards_enabled)
diff --git a/aitviewer/renderables/plane.py b/aitviewer/renderables/plane.py
index 211cca7..a292f64 100644
--- a/aitviewer/renderables/plane.py
+++ b/aitviewer/renderables/plane.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -35,6 +35,7 @@ def __init__(self,
                  v2,
                  size=10.0,
                  color=(0.5, 0.5, 0.5, 1.0),
+                 icon="\u008b",
                  **kwargs):
         """
         Initializer.
@@ -44,11 +45,11 @@ def __init__(self,
         :param size: Size of the plane.
         :param color: Color of the plane.
         """
-        super(Plane, self).__init__(color=color, **kwargs)
+        super(Plane, self).__init__(color=color, icon=icon, **kwargs)
         if np.dot(v1, v2) > 0.00001:
             raise ValueError('v1 and v2 are not orthogonal.')
 
-        self.center = center
+        self.plane_center = center
         self.v1 = v1 / np.linalg.norm(v1)
         self.v2 = v2 / np.linalg.norm(v2)
         self.size = size
@@ -59,10 +60,10 @@ def __init__(self,
         self.backface_culling = False
 
     def _get_renderable_data(self):
-        p0 = self.center + self.v1 * self.size - self.v2 * self.size
-        p1 = self.center + self.v1 * self.size + self.v2 * self.size
-        p2 = self.center - self.v1 * self.size + self.v2 * self.size
-        p3 = self.center - self.v1 * self.size - self.v2 * self.size
+        p0 = self.plane_center + self.v1 * self.size - self.v2 * self.size
+        p1 = self.plane_center + self.v1 * self.size + self.v2 * self.size
+        p2 = self.plane_center - self.v1 * self.size + self.v2 * self.size
+        p3 = self.plane_center - self.v1 * self.size - self.v2 * self.size
         normal = np.cross(self.v2, self.v1)
         normal = np.tile(normal[np.newaxis], (4, 1))
         return np.row_stack([p1, p0, p2, p3]), normal
@@ -116,6 +117,7 @@ def __init__(self,
                  plane='xz',
                  height=0.0,
                  tiling=True,
+                 icon="\u008b",
                  **kwargs):
         """
         Initializer.
@@ -128,7 +130,7 @@ def __init__(self,
         :param kwargs: Remaining kwargs.
         """
         assert plane in ['xz', 'xy', 'yz']
-        super(ChessboardPlane, self).__init__(**kwargs)
+        super(ChessboardPlane, self).__init__(icon=icon, **kwargs)
         self.side_length = side_length
         self.n_tiles = n_tiles
         self.c1 = np.array(color1)
@@ -199,16 +201,17 @@ def render(self, camera, **kwargs):
     def bounds(self):
         return self.get_bounds(self.vertices)
 
-    def gui(self, imgui):
-        self.gui_position(imgui)
-        self.gui_rotation(imgui)
-        self.gui_scale(imgui)
+    @property
+    def current_bounds(self):
+        return self.bounds
 
+    def gui(self, imgui):
         _, self.c1 = imgui.color_edit4("Color 1##color{}'".format(self.unique_name), *self.c1, show_alpha=True)
         _, self.c2 = imgui.color_edit4("Color 2##color{}'".format(self.unique_name), *self.c2, show_alpha=True)
         _, self.tiling = imgui.checkbox('Toggle Tiling', self.tiling)
         _, self.n_tiles = imgui.drag_int('Number of tiles', self.n_tiles, 1.0, 1, 200)
 
+
 class Chessboard(Node):
     """A plane that is textured like a chessboard."""
     def __init__(self,
@@ -246,9 +249,8 @@ def __init__(self,
         self.c2_idxs = c2_idxs
 
         self.mesh = Meshes(vs, fs, face_colors=fc)
-        self.mesh.position = self.position
         self.mesh.backface_culling = False
-        self.add(self.mesh, has_gui=False, show_in_hierarchy=False)
+        self.add(self.mesh, show_in_hierarchy=False)
 
     # noinspection PyAttributeOutsideInit
     def _construct_board(self):
@@ -317,10 +319,6 @@ def _update_colors(self):
         self.mesh.face_colors = self.fcs_tiled
 
     def gui(self, imgui):
-        self.gui_position(imgui)
-        self.gui_rotation(imgui)
-        self.gui_scale(imgui)
-
         u, c1 = imgui.color_edit4("Color 1##color{}'".format(self.unique_name), *self.c1, show_alpha=True)
         if u:
             self.c1 = c1
@@ -334,4 +332,3 @@ def gui(self, imgui):
         u, self.tiling = imgui.checkbox('Toggle Tiling', self.tiling)
         if u:
             self._update_colors()
-
diff --git a/aitviewer/renderables/point_clouds.py b/aitviewer/renderables/point_clouds.py
index 0bee551..85ae820 100644
--- a/aitviewer/renderables/point_clouds.py
+++ b/aitviewer/renderables/point_clouds.py
@@ -28,7 +28,15 @@ class PointClouds(Node):
     Draw a point clouds man!
     """
 
-    def __init__(self, points, colors=None, point_size=5.0, color=(0.0, 0.0, 1.0, 1.0), z_up=False, **kwargs):
+    def __init__(self,
+                 points,
+                 colors=None,
+                 point_size=5.0,
+                 color=(0.0, 0.0, 1.0, 1.0),
+                 z_up=False,
+                 icon="\u008c",
+                 pickable=True,
+                 **kwargs):
         """
         A sequence of point clouds. Each point cloud can have a varying number of points.
         :param points: Sequence of points (F, P, 3)
@@ -41,14 +49,17 @@ def __init__(self, points, colors=None, point_size=5.0, color=(0.0, 0.0, 1.0, 1.
             assert len(colors) == len(points)
 
         self.points = points
-        super(PointClouds, self).__init__(n_frames=len(self.points), color=color, **kwargs)
+        super(PointClouds, self).__init__(n_frames=len(self.points), color=color, icon=icon, **kwargs)
+
+        self.fragmap = pickable
+        self.outline = True
 
         self.colors = colors
         self.point_size = point_size
         self.max_n_points = max([p.shape[0] for p in self.points])
 
         self.vao = VAO("points", mode=moderngl.POINTS)
-        
+
         if z_up:
             self.rotation = np.matmul(np.array([[1, 0, 0], [0, 0, 1], [0, -1, 0]]), self.rotation)
 
@@ -82,21 +93,28 @@ def colors(self, colors):
 
     @Node.color.setter
     def color(self, color):
-        alpha_changed = np.abs((np.array(color) - np.array(self._colors[0])))[-1] > 0
+        """Update the color of the point cloud."""
+        # This is a bit ill-defined because point clouds can have per-point colors, in which case we probably do
+        # not want to override them with a single uniform color. We disallow this for now. The function is still useful
+        # though to change the alpha, even if a point cloud has per-point colors.
         self.material.color = color
         if self.is_renderable:
-            # If alpha changed, don't update all colors
-            if alpha_changed:
-                for i in range(self.n_frames):
-                    self.colors[i][..., -1] = color[-1]
+            single_color = isinstance(self.colors[0], tuple) and len(self.colors[0]) == 4
+            if single_color:
+                self.colors = tuple(color)
             else:
-                self.colors = color
-
+                # Only update the colors if the alpha changed. Take any frame and any point to check if the alpha
+                # changed because we always change every frame and every point.
+                alpha_changed = abs(color[-1] - self.colors[0][0, -1]) > 0
+                if alpha_changed:
+                    for i in range(self.n_frames):
+                        self.colors[i][..., -1] = color[-1]
         self.redraw()
 
     @property
     def current_points(self):
-        return self.points[self.current_frame_id]
+        idx = self.current_frame_id if len(self.points) > 1 else 0
+        return self.points[idx]
 
     @property
     def current_colors(self):
@@ -104,10 +122,23 @@ def current_colors(self):
             n_points = self.current_points.shape[0]
             return np.full((n_points, 4), self.colors[0])
         else:
-            return self.colors[self.current_frame_id]
+            idx = self.current_frame_id if len(self.colors) > 1 else 0
+            return self.colors[idx]
 
     @property
     def bounds(self):
+        if len(self.points) == 0:
+            return np.array([[0, 0], [0, 0], [0, 0]])
+
+        bounds = np.array([[np.inf, np.NINF], [np.inf, np.NINF], [np.inf, np.NINF]])
+        for i in range(len(self.points)):
+            b = self.get_bounds(self.points[i])
+            bounds[:, 0] = np.minimum(bounds[:, 0], b[:, 0])
+            bounds[:, 1] = np.maximum(bounds[:, 1], b[:, 1])
+        return bounds
+
+    @property
+    def current_bounds(self):
         return self.get_bounds(self.current_points)
 
     def on_frame_update(self):
@@ -136,6 +167,7 @@ def _clear_buffer(self):
         self.vbo_points.clear()
         self.vbo_colors.clear()
 
+    # noinspection PyAttributeOutsideInit
     @Node.once
     def make_renderable(self, ctx):
         ctx.point_size = self.point_size
@@ -147,12 +179,20 @@ def make_renderable(self, ctx):
         self.vao.buffer(self.vbo_points, '3f', ['in_position'])
         self.vao.buffer(self.vbo_colors, '4f', ['in_color'])
 
+        self.positions_vao = VAO('{}:positions'.format(self.unique_name), mode=moderngl.POINTS)
+        self.positions_vao.buffer(self.vbo_points, '3f', ['in_position'])
+
     def render(self, camera, **kwargs):
         self.set_camera_matrices(self.prog, camera, **kwargs)
         # Draw only as many points as we have set in the buffer.
         self.vao.render(self.prog, vertices=len(self.current_points))
 
+    def render_positions(self, prog):
+        if self.is_renderable:
+            self.positions_vao.render(prog, vertices=len(self.current_points))
+
     @hooked
     def release(self):
         if self.is_renderable:
-            self.vao.release()
\ No newline at end of file
+            self.vao.release()
+            self.positions_vao.release(buffer=False)
diff --git a/aitviewer/renderables/rigid_bodies.py b/aitviewer/renderables/rigid_bodies.py
index f6f5d0f..588311d 100644
--- a/aitviewer/renderables/rigid_bodies.py
+++ b/aitviewer/renderables/rigid_bodies.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@ def __init__(self,
                  length=0.2,
                  radius_cylinder=None,
                  color=(0.0, 1.0, 0.5, 1.0),
+                 icon="\u0086",
                  **kwargs):
         """
         Initializer.
@@ -44,13 +45,13 @@ def __init__(self,
         """
         self.rb_pos = rb_pos[np.newaxis] if rb_pos.ndim == 2 else rb_pos
         self.rb_ori = rb_ori[np.newaxis] if rb_ori.ndim == 3 else rb_ori
-        super(RigidBodies, self).__init__(n_frames=self.rb_pos.shape[0], color=color, **kwargs)
+        super(RigidBodies, self).__init__(n_frames=self.rb_pos.shape[0], color=color, icon=icon, **kwargs)
 
         self.radius = radius
         self.length = length
 
-        self.spheres = Spheres(rb_pos, radius=radius, color=color, position=self.position, is_selectable=False)
-        self._add_node(self.spheres, has_gui=False, show_in_hierarchy=False)
+        self.spheres = Spheres(rb_pos, radius=radius, color=color, is_selectable=False)
+        self._add_node(self.spheres, show_in_hierarchy=False)
 
         self.coords = []
         r_base = radius_cylinder or length / 50
@@ -63,9 +64,7 @@ def __init__(self,
             color[i] = 1.0
             axs = Arrows(self.rb_pos, self.rb_pos + line, r_base=r_base, r_head=r_head, color=tuple(color),
                          is_selectable=False)
-            axs.position = self.position
-            axs.rotation = self.rotation
-            self._add_node(axs, has_gui=False, show_in_hierarchy=False)
+            self._add_node(axs, show_in_hierarchy=False)
             self.coords.append(axs)
 
     @Node.color.setter
@@ -75,19 +74,31 @@ def color(self, color):
 
     @property
     def current_rb_pos(self):
-        return self.rb_pos[self.current_frame_id]
+        idx = self.current_frame_id if self.rb_pos.shape[0] > 1 else 0
+        return self.rb_pos[idx]
 
     @current_rb_pos.setter
     def current_rb_pos(self, pos):
-        self.rb_pos[self.current_frame_id] = pos
+        idx = self.current_frame_id if self.rb_pos.shape[0] > 1 else 0
+        self.rb_pos[idx] = pos
 
     @property
     def current_rb_ori(self):
-        return self.rb_ori[self.current_frame_id]
+        idx = self.current_frame_id if self.rb_ori.shape[0] > 1 else 0
+        return self.rb_ori[idx]
 
     @current_rb_ori.setter
     def current_rb_ori(self, ori):
-        self.rb_ori[self.current_frame_id] = ori
+        idx = self.current_frame_id if self.rb_ori.shape[0] > 1 else 0
+        self.rb_ori[idx] = ori
+
+    @property
+    def bounds(self):
+        return self.get_bounds(self.rb_pos)
+
+    @property
+    def current_bounds(self):
+        return self.get_bounds(self.current_rb_pos)
 
     def redraw(self, **kwargs):
         if kwargs.get('current_frame_only', False):
@@ -97,7 +108,6 @@ def redraw(self, **kwargs):
                 line = self.rb_ori[..., :, i][self.current_frame_id]
                 line = line / np.linalg.norm(line, axis=-1, keepdims=True) * self.length
                 axs = self.coords[i]
-                axs.position = self.position
                 axs.current_origins = self.current_rb_pos
                 axs.current_tips = self.current_rb_pos + line
         else:
@@ -107,7 +117,6 @@ def redraw(self, **kwargs):
                 line = self.rb_ori[..., :, i]
                 line = line / np.linalg.norm(line, axis=-1, keepdims=True) * self.length
                 axs = self.coords[i]
-                axs.position = self.position
                 axs.origins = self.rb_pos
                 axs.tips = self.rb_pos + line
 
diff --git a/aitviewer/renderables/skeletons.py b/aitviewer/renderables/skeletons.py
index 891a778..b989068 100644
--- a/aitviewer/renderables/skeletons.py
+++ b/aitviewer/renderables/skeletons.py
@@ -31,6 +31,7 @@ def __init__(self,
                  joint_connections,
                  radius=0.01,
                  color=(1.0, 177 / 255, 1 / 255, 1.0),
+                 icon="\u0089",
                  **kwargs):
         """
         Initializer.
@@ -44,7 +45,7 @@ def __init__(self,
         if not isinstance(joint_connections, np.ndarray):
             joint_connections = np.array(joint_connections)
 
-        super(Skeletons, self).__init__(n_frames=joint_positions.shape[0], color=color, **kwargs)
+        super(Skeletons, self).__init__(n_frames=joint_positions.shape[0], color=color, icon=icon, **kwargs)
 
         self._joint_positions = joint_positions
         self.joint_connections = joint_connections
@@ -58,7 +59,7 @@ def __init__(self,
         material = Material(color=color)
         self.spheres = Spheres(joint_positions, radius=radius, material=material, is_selectable=False)
         self.lines = Lines(lines=self.joint_positions[:, self.skeleton].reshape(len(self), -1, 3),
-                           mode='lines', r_base=radius, r_tip=radius / 10.0,material=material, is_selectable=False)
+                           mode='lines', r_base=radius, r_tip=radius / 10.0, material=material, is_selectable=False)
         self._add_nodes(self.spheres, self.lines, show_in_hierarchy=False)
 
     @property
@@ -75,12 +76,14 @@ def joint_positions(self, joint_positions):
 
     @property
     def current_joint_positions(self):
-        return self._joint_positions[self.current_frame_id]
+        idx = self.current_frame_id if self._joint_positions.shape[0] > 1 else 0
+        return self._joint_positions[idx]
 
     @current_joint_positions.setter
     def current_joint_positions(self, positions):
         assert len(positions.shape) == 2
-        self._joint_positions[self.current_frame_id] = positions
+        idx = self.current_frame_id if self._joint_positions.shape[0] > 1 else 0
+        self._joint_positions[idx] = positions
 
     def redraw(self, **kwargs):
         if kwargs.get('current_frame_only', False):
@@ -95,6 +98,10 @@ def redraw(self, **kwargs):
     def bounds(self):
         return self.get_bounds(self.joint_positions)
 
+    @property
+    def current_bounds(self):
+        return self.get_bounds(self.current_joint_positions)
+
     @Node.color.setter
     def color(self, color):
         self.material.color = color
diff --git a/aitviewer/renderables/smpl.py b/aitviewer/renderables/smpl.py
index 9ecf35b..65109ae 100644
--- a/aitviewer/renderables/smpl.py
+++ b/aitviewer/renderables/smpl.py
@@ -61,6 +61,7 @@ def __init__(self,
                  show_joint_angles=False,
                  z_up=False,
                  post_fk_func=None,
+                 icon="\u0093",
                  **kwargs):
         """
         Initializer.
@@ -89,7 +90,14 @@ def __init__(self,
         :param kwargs: Remaining arguments for rendering.
         """
         assert len(poses_body.shape) == 2
-        super(SMPLSequence, self).__init__(n_frames=poses_body.shape[0], **kwargs)
+
+        # Set model icon
+        if smpl_layer.model_type == 'mano':
+            icon = "\u0092"
+        elif smpl_layer.model_type == 'flame':
+            icon = "\u0091"
+
+        super(SMPLSequence, self).__init__(n_frames=poses_body.shape[0], icon=icon, gui_material=False, **kwargs)
 
         self.smpl_layer = smpl_layer
         self.post_fk_func = post_fk_func
@@ -130,7 +138,8 @@ def __init__(self,
             self.trans = trans - trans[0:1]
 
         # Edit mode
-        self._edit_mode = False
+        self.gui_modes.update({'edit': {'title': ' Edit', 'fn': self.gui_mode_edit, 'icon': '\u0081'}})
+
         self._edit_joint = None
         self._edit_pose = None
         self._edit_pose_dirty = False
@@ -139,16 +148,9 @@ def __init__(self,
         self.vertices, self.joints, self.faces, self.skeleton = self.fk()
 
         if self._is_rigged:
-            # Must first add skeleton, otherwise transparency does not work correctly.
-            # Overriding given color with a custom color for the skeleton.
-            kwargs = self._render_kwargs.copy()
-            kwargs['color'] = (1.0, 177 / 255, 1 / 255, 1.0)
-            kwargs['name'] = 'Skeleton'
-            self.skeleton_seq = Skeletons(self.joints, self.skeleton, **kwargs)
-            self.skeleton_seq.position = self.position
-            self.skeleton_seq.rotation = self.rotation
-
-            self._add_node(self.skeleton_seq, gui_elements=['material'])
+            self.skeleton_seq = Skeletons(self.joints, self.skeleton, gui_affine=False,
+                                          color=(1.0, 177 / 255, 1 / 255, 1.0), name='Skeleton')
+            self._add_node(self.skeleton_seq)
 
         # First convert the relative joint angles to global joint angles in rotation matrix form.
         if self.smpl_layer.model_type != "flame":
@@ -161,28 +163,32 @@ def __init__(self,
         if self._z_up:
             self.rotation = np.matmul(np.array([[1, 0, 0], [0, 0, 1], [0, -1, 0]]), self.rotation)
 
-        self.rbs = RigidBodies(self.joints, global_oris, length=0.1, name='Joint Angles')
-        self.rbs.position = self.position
-        self.rbs.rotation = self.rotation
-        self._add_node(self.rbs, enabled=self._show_joint_angles, gui_elements=['material'])
+        self.rbs = RigidBodies(self.joints, global_oris, length=0.1, gui_affine=False, name='Joint Angles')
+        self._add_node(self.rbs, enabled=self._show_joint_angles)
 
-        kwargs = self._render_kwargs.copy()
-        kwargs['name'] = 'Mesh'
-        kwargs['color'] = kwargs.get('color', (160 / 255, 160 / 255, 160 / 255, 1.0))
-        self.mesh_seq = Meshes(self.vertices, self.faces, is_selectable=False, **kwargs)
-        self.mesh_seq.position = self.position
-        self.mesh_seq.rotation = self.rotation
-        self._add_node(self.mesh_seq, gui_elements=['material'])
+        self.mesh_seq = Meshes(self.vertices, self.faces, is_selectable=False, gui_affine=False,
+                               color=kwargs.get('color', (160 / 255, 160 / 255, 160 / 255, 1.0)), name='Mesh')
+        self._add_node(self.mesh_seq)
 
         # Save view mode state to restore when exiting edit mode.
         self._view_mode_color = self.mesh_seq.color
         self._view_mode_joint_angles = self._show_joint_angles
 
     @classmethod
-    def from_amass(cls, npz_data_path, start_frame=None, end_frame=None, log=True, fps_out=None, z_up=True, **kwargs):
+    def from_amass(cls,
+                   npz_data_path,
+                   smpl_layer=None,
+                   start_frame=None,
+                   end_frame=None,
+                   log=True,
+                   fps_out=None,
+                   z_up=True,
+                   **kwargs):
         """Load a sequence downloaded from the AMASS website."""
+
         body_data = np.load(npz_data_path)
-        smpl_layer = SMPLLayer(model_type='smplh', gender=body_data['gender'].item(), device=C.device)
+        if smpl_layer is None:
+            smpl_layer = SMPLLayer(model_type='smplh', gender=body_data['gender'].item(), device=C.device)
 
         if log:
             print('Data keys available: {}'.format(list(body_data.keys())))
@@ -289,11 +295,26 @@ def from_npz(cls, file: Union[IO, str], smpl_layer: SMPLLayer=None, **kwargs):
 
     def export_to_npz(self, file: Union[IO, str]):
         np.savez(file,
-            poses_body=c2c(self.poses_body),
-            poses_root=c2c(self.poses_root),
-            betas=c2c(self.betas),
-            trans=c2c(self.trans),
-        )
+                 poses_body=c2c(self.poses_body),
+                 poses_root=c2c(self.poses_root),
+                 betas=c2c(self.betas),
+                 trans=c2c(self.trans))
+
+    @property
+    def color(self):
+        return self.mesh_seq.color
+
+    @color.setter
+    def color(self, color):
+        self.mesh_seq.color = color
+
+    @property
+    def bounds(self):
+        return self.mesh_seq.bounds
+
+    @property
+    def current_bounds(self):
+        return self.mesh_seq.current_bounds
 
     @property
     def vertex_normals(self):
@@ -303,6 +324,10 @@ def vertex_normals(self):
     def poses(self):
         return torch.cat((self.poses_root, self.poses_body), dim=-1)
 
+    @property
+    def _edit_mode(self):
+        return self.selected_mode == 'edit'
+
     def fk(self, current_frame_only=False):
         """Get joints and/or vertices from the poses."""
         if current_frame_only:
@@ -464,22 +489,15 @@ def redraw(self, **kwargs):
     def edit_mode(self):
         return self._edit_mode
 
-    @edit_mode.setter
-    def edit_mode(self, enabled):
-        if enabled == self._edit_mode:
-            return
-
-        if not enabled:
-            self._edit_mode = False
+    @property
+    def selected_mode(self):
+        return self._selected_mode
 
-            self.mesh_seq.backface_fragmap = False
-            self.mesh_seq.color = self._view_mode_color
+    @selected_mode.setter
+    def selected_mode(self, selected_mode):
+        self._selected_mode = selected_mode
 
-            self.rbs.color = (0, 1, 0.5, 1.0)
-            self.rbs.enabled = self._view_mode_joint_angles
-            self.rbs.is_selectable = True
-        else:
-            self._edit_mode = True
+        if self.selected_mode == 'edit':
             self.rbs.enabled = True
             self.rbs.is_selectable = False
             self._edit_pose = self.poses[self.current_frame_id].clone()
@@ -489,6 +507,13 @@ def edit_mode(self, enabled):
             self.rbs.color = (1, 0, 0.5, 1.0)
             self._view_mode_color = self.mesh_seq.color
             self.mesh_seq.color = (*self._view_mode_color[:3], min(self._view_mode_color[3], 0.5))
+        else:
+            self.mesh_seq.backface_fragmap = False
+            self.mesh_seq.color = self._view_mode_color
+
+            self.rbs.color = (0, 1, 0.5, 1.0)
+            self.rbs.enabled = self._view_mode_joint_angles
+            self.rbs.is_selectable = True
 
         self.redraw(current_frame_only=True)
 
@@ -521,59 +546,47 @@ def _gui_joint(self, imgui, j, tree=None):
                     self._gui_joint(imgui, c, tree)
                 imgui.tree_pop()
 
-    def gui(self, imgui):
-        super().gui_animation(imgui)
-        super().gui_position(imgui)
-        super().gui_rotation(imgui)
-
-        if imgui.radio_button("View mode", not self.edit_mode):
-            self.edit_mode = False
-        if imgui.radio_button("Edit mode", self.edit_mode):
-            self.edit_mode = True
-
-        imgui.spacing()
-
-        if self.edit_mode:
-            skel = self.smpl_layer.skeletons()['body'].cpu().numpy()
+    def gui_mode_edit(self, imgui):
+        skel = self.smpl_layer.skeletons()['body'].cpu().numpy()
 
-            tree = {}
-            for i in range(skel.shape[1]):
-                if skel[0, i] != -1:
-                    tree.setdefault(skel[0, i], []).append(skel[1, i])
+        tree = {}
+        for i in range(skel.shape[1]):
+            if skel[0, i] != -1:
+                tree.setdefault(skel[0, i], []).append(skel[1, i])
 
-            if not tree:
-                return
+        if not tree:
+            return
 
-            if self._edit_joint is None:
-                self._gui_joint(imgui, 0, tree)
-            else:
-                self._gui_joint(imgui, self._edit_joint)
+        if self._edit_joint is None:
+            self._gui_joint(imgui, 0, tree)
+        else:
+            self._gui_joint(imgui, self._edit_joint)
 
-            if imgui.button("Apply"):
-                self.poses_root[self.current_frame_id] = self._edit_pose[:3]
-                self.poses_body[self.current_frame_id] = self._edit_pose[3:]
-                self._edit_pose_dirty = False
-                self.redraw(current_frame_only=True)
-            imgui.same_line()
-            if imgui.button("Apply to all"):
-                edit_rots = Rotation.from_rotvec(np.reshape(self._edit_pose, (-1, 3)))
-                base_rots = Rotation.from_rotvec(np.reshape(self.poses[self.current_frame_id], (-1, 3)))
-                relative = edit_rots * base_rots.inv()
-                for i in range(self.n_frames):
-                    root = Rotation.from_rotvec(np.reshape(self.poses_root[i], (-1, 3)))
-                    self.poses_root[i] = torch.from_numpy((relative[0] * root).as_rotvec().flatten())
-
-                    body = Rotation.from_rotvec(np.reshape(self.poses_body[i], (-1, 3)))
-                    self.poses_body[i] = torch.from_numpy((relative[1:] * body).as_rotvec().flatten())
-                self._edit_pose_dirty = False
-                self.redraw()
-            imgui.same_line()
-            if imgui.button("Reset"):
-                self._edit_pose = self.poses[self.current_frame_id]
-                self._edit_pose_dirty = False
-                self.redraw(current_frame_only=True)
+        if imgui.button("Apply"):
+            self.poses_root[self.current_frame_id] = self._edit_pose[:3]
+            self.poses_body[self.current_frame_id] = self._edit_pose[3:]
+            self._edit_pose_dirty = False
+            self.redraw(current_frame_only=True)
+        imgui.same_line()
+        if imgui.button("Apply to all"):
+            edit_rots = Rotation.from_rotvec(np.reshape(self._edit_pose.cpu().numpy(), (-1, 3)))
+            base_rots = Rotation.from_rotvec(np.reshape(self.poses[self.current_frame_id].cpu().numpy(), (-1, 3)))
+            relative = edit_rots * base_rots.inv()
+            for i in range(self.n_frames):
+                root = Rotation.from_rotvec(np.reshape(self.poses_root[i].cpu().numpy(), (-1, 3)))
+                self.poses_root[i] = torch.from_numpy((relative[0] * root).as_rotvec().flatten())
+
+                body = Rotation.from_rotvec(np.reshape(self.poses_body[i].cpu().numpy(), (-1, 3)))
+                self.poses_body[i] = torch.from_numpy((relative[1:] * body).as_rotvec().flatten())
+            self._edit_pose_dirty = False
+            self.redraw()
+        imgui.same_line()
+        if imgui.button("Reset"):
+            self._edit_pose = self.poses[self.current_frame_id]
+            self._edit_pose_dirty = False
+            self.redraw(current_frame_only=True)
 
-        imgui.spacing()
+    def gui_io(self, imgui):
         if imgui.button("Export sequence to NPZ"):
             dir = os.path.join(C.export_dir, "SMPL")
             os.makedirs(dir, exist_ok=True)
@@ -586,10 +599,10 @@ def gui_context_menu(self, imgui):
             self._gui_joint(imgui, self._edit_joint)
         else:
             if imgui.radio_button("View mode", not self.edit_mode):
-                self.edit_mode = False
+                self.selected_mode = 'view'
                 imgui.close_current_popup()
             if imgui.radio_button("Edit mode", self.edit_mode):
-                self.edit_mode = True
+                self.selected_mode = 'edit'
                 imgui.close_current_popup()
 
     def on_selection(self, node, tri_id):
diff --git a/aitviewer/renderables/spheres.py b/aitviewer/renderables/spheres.py
index 68443b4..ad0ccb1 100644
--- a/aitviewer/renderables/spheres.py
+++ b/aitviewer/renderables/spheres.py
@@ -111,11 +111,23 @@ def __init__(self,
         # A mesh representing the spheres for a single frame
         self.mesh = Meshes(self.sphere_vertices, self.sphere_faces, self.sphere_normals, material=self.material,
                            cast_shadow=False, is_selectable=False)
-        self.mesh.position = self.position
-        self.mesh.rotation = self.rotation
 
         self.add(self.mesh, show_in_hierarchy=False)
 
+    @property
+    def bounds(self):
+        bounds = self.get_bounds(self.sphere_positions)
+        bounds[:, 0] -= self.radius
+        bounds[:, 1] += self.radius
+        return bounds
+
+    @property
+    def current_bounds(self):
+        bounds = self.get_bounds(self.current_sphere_positions)
+        bounds[:, 0] -= self.radius
+        bounds[:, 1] += self.radius
+        return bounds
+
     @property
     def vertex_colors(self):
         return np.full((self.n_spheres * self.n_vertices, 4), self.color)
@@ -126,12 +138,14 @@ def vertex_colors(self, vertex_colors):
 
     @property
     def current_sphere_positions(self):
-        return self.sphere_positions[self.current_frame_id]
+        idx = self.current_frame_id if self.sphere_positions.shape[0] > 1 else 0
+        return self.sphere_positions[idx]
 
     @current_sphere_positions.setter
     def current_sphere_positions(self, positions):
         assert len(positions.shape) == 2
-        self.sphere_positions[self.current_frame_id] = positions
+        idx = self.current_frame_id if self.sphere_positions.shape[0] > 1 else 0
+        self.sphere_positions[idx] = positions
 
     def on_frame_update(self):
         self.redraw()
@@ -139,7 +153,7 @@ def on_frame_update(self):
     def redraw(self, **kwargs):
         current_pos = self.sphere_positions[self.current_frame_id]
         vertices = np.reshape(self.sphere_vertices, [-1, self.n_vertices, 3]) * self.radius + current_pos[:, np.newaxis]
-        self.mesh._vertices =np.reshape(vertices, [-1, 3])[np.newaxis]
+        self.mesh._vertices = np.reshape(vertices, [-1, 3])[np.newaxis]
         super().redraw(**kwargs)
 
     @Node.once
diff --git a/aitviewer/renderables/star.py b/aitviewer/renderables/star.py
new file mode 100644
index 0000000..3186235
--- /dev/null
+++ b/aitviewer/renderables/star.py
@@ -0,0 +1,154 @@
+"""
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+"""
+import numpy as np
+import torch
+
+from aitviewer.configuration import CONFIG as C
+from aitviewer.models.star import STARLayer
+from aitviewer.renderables.smpl import SMPLSequence
+from aitviewer.utils import to_numpy as c2c
+
+
+class STARSequence(SMPLSequence):
+    """
+    Represents a temporal sequence of SMPL poses using the STAR model.
+    """
+
+    def __init__(self,
+                 poses_body,
+                 smpl_layer,
+                 poses_root,
+                 betas=None,
+                 trans=None,
+                 device=C.device,
+                 include_root=True,
+                 normalize_root=False,
+                 is_rigged=True,
+                 show_joint_angles=False,
+                 z_up=False,
+                 post_fk_func=None,
+                 **kwargs):
+
+        super(STARSequence, self).__init__(poses_body, smpl_layer, poses_root, betas, trans, device=device,
+                                           include_root=include_root, normalize_root=normalize_root,
+                                           is_rigged=is_rigged, show_joint_angles=show_joint_angles, z_up=z_up,
+                                           post_fk_func=post_fk_func, **kwargs)
+
+    def fk(self, current_frame_only=False):
+        """Get joints and/or vertices from the poses."""
+        if current_frame_only:
+            # Use current frame data.
+            if self._edit_mode:
+                poses_root = self._edit_pose[:3][None, :]
+                poses_body = self._edit_pose[3:][None, :]
+            else:
+                poses_body = self.poses_body[self.current_frame_id][None, :]
+                poses_root = self.poses_root[self.current_frame_id][None, :]
+
+            trans = self.trans[self.current_frame_id][None, :]
+
+            if self.betas.shape[0] == self.n_frames:
+                betas = self.betas[self.current_frame_id][None, :]
+            else:
+                betas = self.betas
+        else:
+            # Use the whole sequence.
+            if self._edit_mode:
+                poses_root = self.poses_root.clone()
+                poses_body = self.poses_body.clone()
+                poses_root[self.current_frame_id] = self._edit_pose[:3]
+                poses_body[self.current_frame_id] = self._edit_pose[3:]
+            else:
+                poses_body = self.poses_body
+                poses_root = self.poses_root
+            trans = self.trans
+            betas = self.betas
+
+        verts, joints = self.smpl_layer(poses_root=poses_root,
+                                        poses_body=poses_body,
+                                        betas=betas,
+                                        trans=trans,
+                                        normalize_root=self._normalize_root
+                                        )
+
+        skeleton = self.smpl_layer.skeletons()['body'].T
+        faces = self.smpl_layer.faces
+        joints = joints[:, :skeleton.shape[0]]
+
+        if current_frame_only:
+            return c2c(verts)[0], c2c(joints)[0], c2c(faces), c2c(skeleton)
+        else:
+            return c2c(verts), c2c(joints), c2c(faces), c2c(skeleton)
+
+    @classmethod
+    def from_amass(cls,
+                   npz_data_path,
+                   start_frame=None,
+                   end_frame=None,
+                   sub_frames=None,
+                   log=True,
+                   fps_out=None,
+                   load_betas=False,
+                   z_up=True,
+                   **kwargs):
+        """Load a sequence downloaded from the AMASS website."""
+
+        # User SMPL sequence loader and re-parse data
+        seq = super().from_amass(npz_data_path, start_frame=start_frame, end_frame=end_frame, log=log, fps_out=fps_out,
+                                 **kwargs)
+
+        # STAR has no hands, but includes wrists
+        poses_body = torch.cat((seq.poses_body, seq.poses_left_hand[:, :3], seq.poses_right_hand[:, :3]), dim=-1)
+        poses_root = seq.poses_root
+        trans = seq.trans
+        betas = None
+
+        if load_betas:
+            print("WARNING: Loading betas from AMASS into STAR requires an optimization procedure. " +
+                  "See https://github.com/ahmedosman/STAR/tree/master/convertors")
+            betas = seq.betas
+
+        if sub_frames is not None:
+            poses_root = poses_root[sub_frames]
+            poses_body = poses_body[sub_frames]
+            trans = trans[sub_frames]
+
+        return cls(poses_body=poses_body,
+                   smpl_layer=STARLayer(device=C.device),
+                   poses_root=poses_root,
+                   betas=betas,
+                   trans=trans,
+                   include_root=seq._include_root,
+                   is_rigged=seq._is_rigged,
+                   z_up=z_up,
+                   color=seq.color,
+                   **kwargs)
+
+    @classmethod
+    def from_3dpw(cls):
+        raise ValueError('STAR does not support loading from 3DPW.')
+
+    @classmethod
+    def t_pose(cls, model=None, betas=None, frames=1, **kwargs):
+        """Creates a SMPL sequence whose single frame is a SMPL mesh in T-Pose."""
+
+        if model is None:
+            model = STARLayer(device=C.device)
+
+        poses_body = np.zeros([frames, model.n_joints_body * 3])
+        poses_root = np.zeros([frames, 3])
+        return cls(poses_body=poses_body, smpl_layer=model, poses_root=poses_root, betas=betas, **kwargs)
diff --git a/aitviewer/resources/fonts/Custom.sfd b/aitviewer/resources/fonts/Custom.sfd
index 5b60087..67d0f06 100644
--- a/aitviewer/resources/fonts/Custom.sfd
+++ b/aitviewer/resources/fonts/Custom.sfd
@@ -22,7 +22,7 @@ OS2Version: 0
 OS2_WeightWidthSlopeOnly: 0
 OS2_UseTypoMetrics: 1
 CreationTime: 1592304593
-ModificationTime: 1595794688
+ModificationTime: 1664131406
 OS2TypoAscent: 0
 OS2TypoAOffset: 1
 OS2TypoDescent: 0
@@ -44,10 +44,10 @@ NameList: AGL For New Fonts
 DisplaySize: -36
 AntiAlias: 1
 FitToEm: 0
-WinInfo: 0 29 9
+WinInfo: 0 37 10
 BeginPrivate: 0
 EndPrivate
-BeginChars: 256 155
+BeginChars: 256 174
 
 StartChar: exclam
 Encoding: 33 33 0
@@ -3318,25 +3318,22 @@ Flags: HW
 LayerCount: 2
 Fore
 SplineSet
-1272 858 m 1
- 1622 1206 l 1
- 1622 248 l 1
- 1272 596 l 1
- 1272 290 l 2
- 1272 266 1264 244 1246 228 c 128
- 1228 212 1206 204 1182 204 c 2
- 134 204 l 2
- 110 204 88 212 72 228 c 128
- 56 244 48 266 48 290 c 2
- 48 1166 l 2
- 48 1190 56 1212 72 1228 c 128
- 88 1244 110 1252 134 1252 c 2
- 1182 1252 l 2
- 1206 1252 1228 1244 1246 1228 c 128
- 1264 1212 1272 1190 1272 1166 c 2
- 1272 858 l 1
+1905.87207031 1156.92773438 m 2
+ 1984.09570312 1201.77636719 2048 1164.91210938 2048.015625 1074.55957031 c 2
+ 2048.015625 152.959960938 l 2
+ 2048.015625 63.0400390625 1983.91992188 26.240234375 1905.88769531 71.0400390625 c 2
+ 1494.015625 307.83984375 l 1
+ 1494.015625 231.440429688 l 2
+ 1494.00683594 95.8056640625 1383.890625 -14.3115234375 1248.25585938 -14.3203125 c 2
+ 245.759765625 -14.3203125 l 2
+ 110.125 -14.3115234375 0.0087890625 95.8056640625 0 231.440429688 c 2
+ 0 996.576171875 l 2
+ 0.03515625 1132.18847656 110.141601562 1242.27929688 245.759765625 1242.28808594 c 2
+ 1248.24023438 1242.28808594 l 2
+ 1383.875 1242.27929688 1493.99121094 1132.16210938 1494 996.528320312 c 2
+ 1494 920.127929688 l 1
+ 1905.87207031 1156.92773438 l 2
 EndSplineSet
-Validated: 1
 EndChar
 
 StartChar: uni0085
@@ -3475,88 +3472,1617 @@ StartChar: uni0088
 Encoding: 136 136 154
 Width: 1656
 VWidth: 1780
-Flags: HWO
-LayerCount: 2
-Fore
-SplineSet
-1419 840 m 1
- 1419 1352 l 1
- 227 1352 l 1
- 227 840 l 1
- 395 840 l 1
- 395 672 l 1
- 567 672 l 1
- 567 500 l 1
- 739 500 l 1
- 739 672 l 1
- 907 672 l 1
- 907 500 l 1
- 1079 500 l 1
- 1079 672 l 1
- 1251 672 l 1
- 1251 840 l 1
- 1419 840 l 1
-1251 244 m 1
- 1251 416 l 1
- 1079 416 l 1
- 1079 244 l 1
- 1251 244 l 1
-907 244 m 1
- 907 416 l 1
- 739 416 l 1
- 739 244 l 1
- 907 244 l 1
-567 244 m 1
- 567 416 l 1
- 395 416 l 1
- 395 244 l 1
- 567 244 l 1
-1419 1524 m 2
- 1464.33300781 1524 1504.33300781 1506.66699219 1539 1472 c 128
- 1573.66699219 1437.33300781 1591 1397.33300781 1591 1352 c 2
- 1591 160 l 2
- 1591 114.666992188 1573.66699219 74.6669921875 1539 40 c 128
- 1504.33300781 5.3330078125 1464.33300781 -12 1419 -12 c 2
- 227 -12 l 2
- 181.666992188 -12 141.666992188 5.3330078125 107 40 c 128
- 72.3330078125 74.6669921875 55 114.666992188 55 160 c 2
- 55 1352 l 2
- 55 1397.33300781 72.3330078125 1437.33300781 107 1472 c 128
- 141.666992188 1506.66699219 181.666992188 1524 227 1524 c 2
- 1419 1524 l 2
-395 1012 m 1
- 567 1012 l 1
- 567 840 l 1
- 395 840 l 1
- 395 1012 l 1
-1079 1012 m 1
- 1251 1012 l 1
- 1251 840 l 1
- 1079 840 l 1
- 1079 1012 l 1
-739 1012 m 1
- 907 1012 l 1
- 907 840 l 1
- 1079 840 l 1
- 1079 672 l 1
- 907 672 l 1
- 907 840 l 1
- 739 840 l 1
- 739 672 l 1
- 567 672 l 1
- 567 840 l 1
- 739 840 l 1
- 739 1012 l 1
-1251 672 m 1
- 1251 500 l 1
- 1419 500 l 1
- 1419 672 l 1
- 1251 672 l 1
-395 672 m 1
- 227 672 l 1
- 227 500 l 1
- 395 500 l 1
- 395 672 l 1
+Flags: HW
+LayerCount: 2
+Fore
+SplineSet
+901.696289062 494.528320312 m 1
+ 901.696289062 739.15234375 l 1
+ 1143.47167969 739.15234375 l 1
+ 1143.47167969 494.528320312 l 1
+ 901.696289062 494.528320312 l 1
+657.072265625 249.904296875 m 1
+ 657.072265625 494.528320312 l 1
+ 901.696289062 494.528320312 l 1
+ 901.696289062 249.904296875 l 1
+ 657.072265625 249.904296875 l 1
+1143.47167969 249.904296875 m 1
+ 1143.47167969 494.528320312 l 1
+ 1388.09570312 494.528320312 l 1
+ 1388.09570312 249.904296875 l 1
+ 1143.47167969 249.904296875 l 1
+1388.09570312 494.528320312 m 1
+ 1388.09570312 739.15234375 l 1
+ 1632.71972656 739.15234375 l 1
+ 1632.71972656 494.528320312 l 1
+ 1388.09570312 494.528320312 l 1
+415.295898438 494.528320312 m 1
+ 415.295898438 739.15234375 l 1
+ 657.072265625 739.15234375 l 1
+ 657.072265625 494.528320312 l 1
+ 415.295898438 494.528320312 l 1
+170.671875 -410 m 2
+ 169.676757812 -410.017578125 168.6796875 -410.026367188 167.680664062 -410.026367188 c 0
+ 121.44921875 -410.026367188 79.685546875 -390.958984375 49.7763671875 -360.223632812 c 0
+ 19.041015625 -330.314453125 -0.060546875 -288.509765625 -0.060546875 -242.278320312 c 0
+ -0.060546875 -241.279296875 -0.017578125 -240.323242188 0 -239.328125 c 2
+ 0 1467.328125 l 2
+ -0.017578125 1468.32324219 -0.0263671875 1469.3203125 -0.0263671875 1470.31933594 c 0
+ -0.0263671875 1516.55078125 19.041015625 1558.31445312 49.7763671875 1588.22363281 c 0
+ 79.685546875 1618.95898438 121.490234375 1638.06054688 167.721679688 1638.06054688 c 0
+ 168.720703125 1638.06054688 169.676757812 1638.01757812 170.671875 1638 c 2
+ 1877.328125 1638 l 2
+ 1878.32324219 1638.01757812 1879.3203125 1638.02636719 1880.31933594 1638.02636719 c 0
+ 1926.55078125 1638.02636719 1968.31445312 1618.95898438 1998.22363281 1588.22363281 c 0
+ 2028.95898438 1558.31445312 2048.06054688 1516.50976562 2048.06054688 1470.27832031 c 0
+ 2048.06054688 1469.27929688 2048.01757812 1468.32324219 2048 1467.328125 c 2
+ 2048 -239.328125 l 2
+ 2048.00097656 -239.573242188 2048.00195312 -239.818359375 2048.00195312 -240.063476562 c 0
+ 2048.00195312 -333.862304688 1971.84863281 -410.015625 1878.04980469 -410.015625 c 0
+ 1877.8046875 -410.015625 1877.57324219 -410.000976562 1877.328125 -410 c 2
+ 170.671875 -410 l 2
+415.295898438 -239.328125 m 1
+ 657.072265625 -239.328125 l 1
+ 657.072265625 5.2958984375 l 1
+ 415.295898438 5.2958984375 l 1
+ 415.295898438 -239.328125 l 1
+901.696289062 -239.328125 m 1
+ 1143.47167969 -239.328125 l 1
+ 1143.47167969 5.2958984375 l 1
+ 901.696289062 5.2958984375 l 1
+ 901.696289062 -239.328125 l 1
+1877.34375 -239.328125 m 1
+ 1877.34375 -239.328125 l 1
+170.671875 5.2958984375 m 1
+ 415.295898438 5.2958984375 l 1
+ 415.295898438 249.904296875 l 1
+ 657.072265625 249.904296875 l 1
+ 657.072265625 5.2802734375 l 1
+ 901.696289062 5.2802734375 l 1
+ 901.696289062 249.904296875 l 1
+ 1143.47167969 249.904296875 l 1
+ 1143.47167969 5.2802734375 l 1
+ 1388.09570312 5.2802734375 l 1
+ 1388.09570312 249.904296875 l 1
+ 1632.71972656 249.904296875 l 1
+ 1632.71972656 5.2802734375 l 1
+ 1877.34375 5.2802734375 l 1
+ 1877.34375 249.904296875 l 1
+ 1632.70410156 249.904296875 l 1
+ 1632.70410156 494.528320312 l 1
+ 1877.328125 494.528320312 l 1
+ 1877.328125 1467.328125 l 1
+ 170.671875 1467.328125 l 1
+ 170.671875 494.528320312 l 1
+ 415.295898438 494.528320312 l 1
+ 415.295898438 249.904296875 l 1
+ 170.671875 249.904296875 l 1
+ 170.671875 5.2958984375 l 1
+170.671875 -239.328125 m 1
+ 170.671875 -239.328125 l 1
+1877.328125 494.528320312 m 1
+ 1877.328125 494.528320312 l 1
+1388.09570312 5.2958984375 m 1
+ 1388.09570312 -239.328125 l 1
+ 1632.71972656 -239.328125 l 1
+ 1632.71972656 5.2958984375 l 1
+ 1388.09570312 5.2958984375 l 1
+EndSplineSet
+EndChar
+
+StartChar: uni0089
+Encoding: 137 137 155
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+1003.39160156 553.50390625 m 1
+ 1003.328125 553.423828125 l 1
+ 1081.22167969 543.84765625 1141.78027344 477.40625 1141.78027344 396.94140625 c 0
+ 1141.78027344 309.90625 1071.11914062 239.245117188 984.084960938 239.245117188 c 0
+ 897.689453125 239.245117188 827.428710938 308.87109375 826.400390625 395.024414062 c 0
+ 826.994140625 470.270507812 879.31640625 533.323242188 949.471679688 550.416015625 c 1
+ 949.471679688 1019.6640625 l 1
+ 1003.39160156 1019.6640625 l 1
+ 1003.39160156 553.50390625 l 1
+1115.0078125 1076.3203125 m 1
+ 1115.0078125 1076.25585938 l 1
+ 1130.40039062 1085.48828125 1145.79199219 1094.71972656 1161.16796875 1105.48828125 c 2
+ 1627.91992188 541.103515625 l 2
+ 1646.33398438 548.918945312 1666.68554688 553.340820312 1687.91992188 553.408203125 c 0
+ 1688.41894531 553.413085938 1688.89160156 553.415039062 1689.39160156 553.415039062 c 0
+ 1776.81542969 553.415039062 1847.79199219 482.438476562 1847.79199219 395.014648438 c 0
+ 1847.79199219 307.591796875 1776.81542969 236.615234375 1689.39160156 236.615234375 c 0
+ 1601.97070312 236.615234375 1530.99609375 307.587890625 1530.9921875 395.0078125 c 0
+ 1531.01660156 441.453125 1551.18066406 483.189453125 1583.29589844 511.935546875 c 2
+ 1115.0078125 1076.3203125 l 1
+1649.47167969 -94.1923828125 m 1
+ 1649.44042969 -94.17578125 l 1
+ 1660.6953125 -91.634765625 1672.31054688 -90.1787109375 1684.32910156 -90.1787109375 c 0
+ 1771.75195312 -90.1787109375 1842.72851562 -161.155273438 1842.72851562 -248.578125 c 0
+ 1842.72851562 -336.001953125 1771.75195312 -406.978515625 1684.32910156 -406.978515625 c 0
+ 1608.32421875 -406.978515625 1544.75097656 -353.33203125 1529.44042969 -281.872070312 c 0
+ 1527.73535156 -270.749023438 1526.70214844 -259.547851562 1526.36816406 -248.032226562 c 0
+ 1526.38378906 -213.750976562 1537.25292969 -181.813476562 1555.59960938 -155.727539062 c 2
+ 1103.31152344 221.200195312 l 1
+ 1118.49609375 230.93359375 1131.83691406 242.745117188 1143.31152344 256.576171875 c 1
+ 1592.54394531 -118.799804688 l 2
+ 1609.54882812 -107.452148438 1628.94921875 -98.9453125 1649.47167969 -94.1923828125 c 1
+982.944335938 1134.92773438 m 1
+ 982.944335938 1134.95996094 l 1
+ 846.896484375 1134.95996094 735.135742188 1259.44042969 735.135742188 1385.18359375 c 0
+ 735.135742188 1510.92773438 711.34375 1636.65625 982.944335938 1637.984375 c 0
+ 1254.54394531 1639.31152344 1233.16796875 1513.34375 1233.16796875 1387.58398438 c 0
+ 1233.16796875 1261.82421875 1118.94433594 1134.92773438 982.944335938 1134.92773438 c 1
+257.15234375 236.591796875 m 1
+ 257.15234375 236.65625 l 1
+ 170.544921875 237.737304688 100.497070312 308.2578125 100.223632812 395.055664062 c 0
+ 101.072265625 482.427734375 171.461914062 553.333984375 258.624023438 555.055664062 c 0
+ 283.484375 554.922851562 307.122070312 548.771484375 327.856445312 538.127929688 c 2
+ 774.255859375 1119.18359375 l 2
+ 789.565429688 1109.60058594 805.534179688 1100.83398438 821.952148438 1093.02441406 c 1
+ 370.927734375 505.82421875 l 2
+ 398.515625 478.08984375 415.555664062 439.870117188 415.555664062 397.700195312 c 0
+ 415.555664062 397.330078125 415.5546875 396.9609375 415.551757812 396.591796875 c 0
+ 414.704101562 309.219726562 344.313476562 238.314453125 257.15234375 236.591796875 c 1
+804.84765625 281.18359375 m 1
+ 804.83203125 281.120117188 l 2
+ 814.9453125 266.180664062 826.146484375 252.418945312 838.671875 239.51953125 c 1
+ 384.83203125 -158.879882812 l 2
+ 403.225585938 -185.114257812 413.975585938 -217.20703125 413.975585938 -251.650390625 c 0
+ 413.975585938 -340.83984375 341.565429688 -413.25 252.375 -413.25 c 0
+ 163.185546875 -413.25 90.775390625 -340.83984375 90.775390625 -251.650390625 c 0
+ 90.775390625 -162.4609375 163.185546875 -90.0498046875 252.375 -90.0498046875 c 0
+ 262.409179688 -90.0498046875 272.23046875 -90.966796875 281.759765625 -92.7197265625 c 0
+ 305.901367188 -97.16015625 328.553710938 -106.8515625 347.919921875 -120.416015625 c 2
+ 804.84765625 281.18359375 l 1
+EndSplineSet
+EndChar
+
+StartChar: uni008A
+Encoding: 138 138 156
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+1024.43164062 1245.31152344 m 1
+ 938.676757812 1256.72558594 872.248046875 1330.16210938 872.248046875 1419.01757812 c 0
+ 872.248046875 1420.50683594 872.266601562 1421.99121094 872.303710938 1423.47167969 c 0
+ 872.302734375 1423.77148438 872.349609375 1424.05566406 872.349609375 1424.35546875 c 0
+ 872.349609375 1523.03808594 951.75 1603.32226562 1050.09570312 1604.75195312 c 0
+ 1148.96582031 1604.27929688 1229.15917969 1524.08496094 1229.63183594 1425.21582031 c 0
+ 1227.33886719 1338.46582031 1166.97460938 1265.99023438 1086.08007812 1245.31152344 c 1
+ 1085.10351562 717.15234375 l 1
+ 1024.43164062 717.15234375 l 1
+ 1024.43164062 1245.31152344 l 1
+1829.51953125 -24.3203125 m 2
+ 1829.55175781 -24.3837890625 l 2
+ 1842.31152344 -21.501953125 1855.48046875 -19.849609375 1869.10644531 -19.849609375 c 0
+ 1968.18554688 -19.849609375 2048.62597656 -100.290039062 2048.62597656 -199.370117188 c 0
+ 2048.62597656 -298.44921875 1968.18554688 -378.889648438 1869.10644531 -378.889648438 c 0
+ 1782.9453125 -378.889648438 1710.88085938 -318.059570312 1693.55175781 -237.040039062 c 0
+ 1691.61621094 -224.418945312 1690.44238281 -211.708007812 1690.06445312 -198.639648438 c 0
+ 1690.078125 -159.794921875 1702.39453125 -123.60546875 1723.18359375 -94.0478515625 c 2
+ 1210.73632812 333.0078125 l 1
+ 1227.9375 344.041015625 1243.04980469 357.428710938 1256.04785156 373.103515625 c 1
+ 1765.02441406 -52.2080078125 l 2
+ 1784.28808594 -39.3466796875 1806.26757812 -29.705078125 1829.51953125 -24.3203125 c 2
+854.383789062 373.103515625 m 1
+ 854.383789062 373.072265625 l 2
+ 865.865234375 356.169921875 878.57421875 340.604492188 892.784179688 326.015625 c 1
+ 378.543945312 -125.423828125 l 2
+ 399.379882812 -155.143554688 411.556640625 -191.499023438 411.556640625 -230.516601562 c 0
+ 411.556640625 -331.556640625 329.525390625 -413.588867188 228.485351562 -413.588867188 c 0
+ 127.4453125 -413.588867188 45.4130859375 -331.556640625 45.4130859375 -230.516601562 c 0
+ 45.4130859375 -129.4765625 127.4453125 -47.4443359375 228.485351562 -47.4443359375 c 0
+ 239.841796875 -47.4443359375 250.95703125 -48.4814453125 261.744140625 -50.4638671875 c 0
+ 289.090820312 -55.49609375 314.75 -66.4736328125 336.688476562 -81.83984375 c 2
+ 854.383789062 373.103515625 l 1
+883.631835938 505.18359375 m 0
+ 883.631835938 602.385742188 962.4296875 681.18359375 1059.63183594 681.18359375 c 0
+ 1156.83398438 681.18359375 1235.63183594 602.385742188 1235.63183594 505.18359375 c 0
+ 1235.63183594 407.981445312 1156.83398438 329.18359375 1059.63183594 329.18359375 c 0
+ 962.4296875 329.18359375 883.631835938 407.981445312 883.631835938 505.18359375 c 0
+EndSplineSet
+EndChar
+
+StartChar: uni008B
+Encoding: 139 139 157
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+327.919921875 1319.95214844 m 1
+ 944.975585938 1319.95214844 l 1
+ 944.975585938 677.6640625 l 1
+ 177.200195312 677.6640625 l 1
+ 327.919921875 1319.95214844 l 1
+151.82421875 534.288085938 m 1
+ 943.120117188 534.288085938 l 1
+ 943.120117188 -60.7041015625 l 1
+ 1.103515625 -60.7041015625 l 1
+ 151.82421875 534.288085938 l 1
+1113.71191406 1319.95214844 m 1
+ 1730.76757812 1319.95214844 l 1
+ 1883.48828125 677.6484375 l 1
+ 1113.71191406 677.6484375 l 1
+ 1113.71191406 1319.95214844 l 1
+1129.71191406 534.288085938 m 1
+ 1906.51171875 534.288085938 l 1
+ 2047.984375 -60.7041015625 l 1
+ 1129.71191406 -60.7041015625 l 1
+ 1129.71191406 534.288085938 l 1
+EndSplineSet
+EndChar
+
+StartChar: uni008C
+Encoding: 140 140 158
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+141.248046875 183.215820312 m 0
+ 140.73828125 183.2109375 140.227539062 183.208007812 139.716796875 183.208007812 c 0
+ 61.759765625 183.208007812 -1.53125 246.499023438 -1.53125 324.456054688 c 0
+ -1.53125 402.413085938 61.759765625 465.704101562 139.716796875 465.704101562 c 0
+ 140.227539062 465.704101562 140.73828125 465.701171875 141.248046875 465.696289062 c 0
+ 141.7578125 465.701171875 142.268554688 465.704101562 142.779296875 465.704101562 c 0
+ 220.736328125 465.704101562 284.02734375 402.413085938 284.02734375 324.456054688 c 0
+ 284.02734375 246.499023438 220.736328125 183.208007812 142.779296875 183.208007812 c 0
+ 142.268554688 183.208007812 141.7578125 183.2109375 141.248046875 183.215820312 c 0
+141.248046875 1355.51953125 m 1
+ 140.384765625 1355.50390625 139.518554688 1355.49609375 138.651367188 1355.49609375 c 0
+ 62.08984375 1355.49609375 -0.068359375 1417.65429688 -0.068359375 1494.21582031 c 0
+ -0.068359375 1495.078125 -0.015625 1495.89355469 0 1496.75195312 c 0
+ -0.015625 1497.61523438 -0.0234375 1498.48144531 -0.0234375 1499.34863281 c 0
+ -0.0234375 1575.91015625 62.134765625 1638.06835938 138.696289062 1638.06835938 c 0
+ 139.563476562 1638.06835938 140.384765625 1638.015625 141.248046875 1638 c 0
+ 142.111328125 1638.015625 142.977539062 1638.0234375 143.844726562 1638.0234375 c 0
+ 220.40625 1638.0234375 282.564453125 1575.86523438 282.564453125 1499.30371094 c 0
+ 282.564453125 1498.43652344 282.51171875 1497.61523438 282.49609375 1496.75195312 c 0
+ 282.51171875 1495.88867188 282.51953125 1495.02246094 282.51953125 1494.15527344 c 0
+ 282.51953125 1417.59375 220.361328125 1355.43554688 143.799804688 1355.43554688 c 0
+ 142.932617188 1355.43554688 142.111328125 1355.48828125 141.248046875 1355.50390625 c 2
+ 141.248046875 1355.51953125 l 1
+582.624023438 -410 m 0
+ 505.380859375 -409.166992188 442.927734375 -346.19921875 442.927734375 -268.760742188 c 0
+ 442.927734375 -190.803710938 506.21875 -127.512695312 584.17578125 -127.512695312 c 0
+ 622.765625 -127.512695312 657.759765625 -143.0078125 683.263671875 -168.127929688 c 0
+ 709.28125 -193.752929688 725.419921875 -229.379882812 725.419921875 -268.74609375 c 0
+ 725.419921875 -346.703125 662.12890625 -409.994140625 584.172851562 -409.994140625 c 0
+ 583.654296875 -409.994140625 583.140625 -410.005859375 582.624023438 -410 c 0
+582.624023438 762.303710938 m 1
+ 504.6171875 762.990234375 441.499023438 826.53125 441.499023438 904.698242188 c 0
+ 441.499023438 943.376953125 456.918945312 978.502929688 481.984375 1004.17578125 c 0
+ 508.06640625 1029.31542969 543.53125 1044.78417969 582.58203125 1044.78417969 c 0
+ 621.631835938 1044.78417969 657.166015625 1029.31542969 683.248046875 1004.17578125 c 0
+ 709.264648438 978.55078125 725.404296875 942.923828125 725.404296875 903.557617188 c 0
+ 725.404296875 825.600585938 662.11328125 762.309570312 584.15625 762.309570312 c 0
+ 583.638671875 762.309570312 583.124023438 762.298828125 582.608398438 762.303710938 c 1
+ 582.624023438 762.303710938 l 1
+1024 183.215820312 m 0
+ 948.6328125 184.600585938 887.874023438 246.216796875 887.874023438 321.912109375 c 0
+ 887.874023438 398.473632812 950.032226562 460.631835938 1026.59375 460.631835938 c 0
+ 1103.15527344 460.631835938 1165.31347656 398.473632812 1165.31347656 321.912109375 c 0
+ 1165.31347656 283.631835938 1149.74902344 248.93359375 1124.63964844 223.82421875 c 0
+ 1099.85546875 198.71875 1065.43359375 183.150390625 1027.40332031 183.150390625 c 0
+ 1026.25097656 183.150390625 1025.14550781 183.1875 1024 183.215820312 c 0
+1024 1355.51953125 m 0
+ 1023.13671875 1355.50390625 1022.27050781 1355.49609375 1021.40332031 1355.49609375 c 0
+ 944.841796875 1355.49609375 882.68359375 1417.65429688 882.68359375 1494.21582031 c 0
+ 882.68359375 1495.08398438 882.736328125 1495.90429688 882.751953125 1496.76757812 c 0
+ 882.74609375 1497.27734375 882.744140625 1497.78808594 882.744140625 1498.29882812 c 0
+ 882.744140625 1576.25585938 946.03515625 1639.546875 1023.9921875 1639.546875 c 0
+ 1101.94921875 1639.546875 1165.24023438 1576.25585938 1165.24023438 1498.29882812 c 0
+ 1165.24023438 1497.78808594 1165.23730469 1497.27734375 1165.23242188 1496.76757812 c 0
+ 1165.24804688 1495.90429688 1165.25585938 1495.0390625 1165.25585938 1494.171875 c 0
+ 1165.25585938 1417.61035156 1103.09765625 1355.45214844 1026.53613281 1355.45214844 c 0
+ 1025.67382812 1355.45214844 1024.85839844 1355.50390625 1024 1355.51953125 c 0
+1465.37597656 -410 m 0
+ 1390.00878906 -408.615234375 1329.25 -346.999023438 1329.25 -271.303710938 c 0
+ 1329.25 -194.7421875 1391.40820312 -132.583984375 1467.96972656 -132.583984375 c 0
+ 1544.53125 -132.583984375 1606.69042969 -194.7421875 1606.69042969 -271.303710938 c 0
+ 1606.69042969 -309.583984375 1591.125 -344.282226562 1566.015625 -369.391601562 c 0
+ 1541.23144531 -394.497070312 1506.80957031 -410.065429688 1468.77929688 -410.065429688 c 0
+ 1467.62695312 -410.065429688 1466.52148438 -410.028320312 1465.37597656 -410 c 0
+1465.37597656 762.303710938 m 0
+ 1387.39941406 763.024414062 1324.31640625 826.552734375 1324.31640625 904.698242188 c 0
+ 1324.31640625 943.375976562 1339.73535156 978.502929688 1364.79980469 1004.17578125 c 0
+ 1390.24609375 1027.92285156 1424.39355469 1042.46289062 1461.91015625 1042.46289062 c 0
+ 1540.50292969 1042.46289062 1604.31054688 978.655273438 1604.31054688 900.0625 c 0
+ 1604.31054688 862.545898438 1589.81054688 828.358398438 1566.06445312 802.912109375 c 0
+ 1541.28027344 777.805664062 1506.85644531 762.237304688 1468.82617188 762.237304688 c 0
+ 1467.65820312 762.237304688 1466.53710938 762.274414062 1465.37597656 762.303710938 c 0
+1906.75195312 183.215820312 m 0
+ 1831.38574219 184.600585938 1770.62597656 246.216796875 1770.62597656 321.912109375 c 0
+ 1770.62597656 398.473632812 1832.78417969 460.631835938 1909.34570312 460.631835938 c 0
+ 1985.90722656 460.631835938 2048.06640625 398.473632812 2048.06640625 321.912109375 c 0
+ 2048.06640625 283.631835938 2032.50097656 248.93359375 2007.39160156 223.82421875 c 0
+ 1982.60742188 198.71875 1948.18554688 183.150390625 1910.15527344 183.150390625 c 0
+ 1909.00292969 183.150390625 1907.89746094 183.1875 1906.75195312 183.215820312 c 0
+1906.75195312 1355.51953125 m 1
+ 1905.88867188 1355.50390625 1905.02246094 1355.49609375 1904.15527344 1355.49609375 c 0
+ 1827.59375 1355.49609375 1765.43554688 1417.65429688 1765.43554688 1494.21582031 c 0
+ 1765.43554688 1495.08398438 1765.48828125 1495.90429688 1765.50390625 1496.76757812 c 0
+ 1765.49902344 1497.27734375 1765.49609375 1497.78808594 1765.49609375 1498.29882812 c 0
+ 1765.49609375 1576.25585938 1828.78710938 1639.546875 1906.74414062 1639.546875 c 0
+ 1984.70117188 1639.546875 2047.9921875 1576.25585938 2047.9921875 1498.29882812 c 0
+ 2047.9921875 1497.78808594 2047.98925781 1497.27734375 2047.984375 1496.76757812 c 0
+ 2048 1495.90429688 2048.0078125 1495.0390625 2048.0078125 1494.171875 c 0
+ 2048.0078125 1417.609375 1985.84960938 1355.45117188 1909.28808594 1355.45117188 c 0
+ 1908.42089844 1355.45117188 1907.59960938 1355.50390625 1906.73632812 1355.51953125 c 1
+ 1906.75195312 1355.51953125 l 1
+EndSplineSet
+EndChar
+
+StartChar: uni008D
+Encoding: 141 141 159
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+2029.70605469 603.3984375 m 1
+ 1525.78613281 -254.987304688 l 1
+ 508.73828125 -261.50390625 l 1
+ 7.0107421875 607.514648438 l 1
+ 522.252929688 1483.0234375 l 1
+ 1539.30175781 1489.54101562 l 1
+ 2041.02148438 620.536132812 l 1
+ 2029.70605469 603.3984375 l 1
+1397.58300781 1144.41113281 m 1
+ 1475.08886719 1444.50488281 l 1
+ 609.2734375 1437.80371094 l 1
+ 1397.58300781 1144.41113281 l 1
+1961.58886719 606.564453125 m 1
+ 1428.97167969 1067.1640625 l 1
+ 1325.13671875 69.4130859375 l 1
+ 1961.58886719 606.564453125 l 1
+1383.07128906 1077.70703125 m 1
+ 447.935546875 657.376953125 l 1
+ 1278.52636719 58.3359375 l 1
+ 1383.07128906 1077.70703125 l 1
+1244.48828125 25.4189453125 m 1
+ 439.639648438 607.490234375 l 1
+ 549.37109375 -185.001953125 l 1
+ 1244.48828125 25.4189453125 l 1
+440.876953125 706.307617188 m 1
+ 1350.14648438 1111.7578125 l 1
+ 553.640625 1405.68359375 l 1
+ 440.876953125 706.307617188 l 1
+385.739257812 682.41796875 m 1
+ 480.536132812 1321.0390625 l 1
+ 76.3134765625 634.26171875 l 1
+ 385.739257812 682.41796875 l 1
+386.504882812 635.138671875 m 1
+ 70.8134765625 588.684570312 l 1
+ 486.420898438 -131.168945312 l 1
+ 386.504882812 635.138671875 l 1
+1294.75097656 -6.56640625 m 1
+ 601.587890625 -215.860351562 l 1
+ 1477.65234375 -208.543945312 l 1
+ 1294.75097656 -6.56640625 l 1
+1513.15039062 -182.764648438 m 1
+ 1922.98925781 512.557617188 l 1
+ 1332.54589844 15.2353515625 l 1
+ 1513.15039062 -182.764648438 l 1
+1437.02929688 1121.9765625 m 1
+ 1958.78125 671.139648438 l 1
+ 1520.20507812 1430.77539062 l 1
+ 1437.02929688 1121.9765625 l 1
+1543.89550781 1497.55078125 m 1
+ 2050.42382812 620.21875 l 1
+ 2036.4921875 599.131835938 l 1
+ 1530.30371094 -263.001953125 l 1
+ 504.129882812 -269.522460938 l 1
+ -2.26953125 607.587890625 l 1
+ 0.09765625 611.615234375 l 1
+ 517.720703125 1491.06640625 l 1
+ 1543.89550781 1497.55078125 l 1
+2031.63964844 620.754882812 m 1
+ 1534.67089844 1481.52832031 l 1
+ 526.809570312 1475.06835938 l 1
+ 16.1298828125 607.495117188 l 1
+ 513.193359375 -253.444335938 l 1
+ 1521.03320312 -246.978515625 l 1
+ 2023.04003906 607.80859375 l 1
+ 2031.63964844 620.754882812 l 1
+1517.50390625 1451.42285156 m 1
+ 1965.71972656 675.090820312 l 1
+ 1953.56347656 665.041992188 l 1
+ 1427.96386719 1119.31054688 l 1
+ 1517.50390625 1451.42285156 l 1
+1928.16699219 708.1328125 m 1
+ 1522.90332031 1410.07128906 l 1
+ 1446.01855469 1124.83886719 l 1
+ 1928.16699219 708.1328125 l 1
+1485.44433594 1452.55273438 m 1
+ 1403.06835938 1133.75878906 l 1
+ 606.506835938 1430.27539062 l 1
+ 609.232421875 1445.77929688 l 1
+ 1485.44433594 1452.55273438 l 1
+1392.00585938 1154.93652344 m 1
+ 1464.78222656 1436.46679688 l 1
+ 652.841796875 1430.18261719 l 1
+ 1392.00585938 1154.93652344 l 1
+1973.94824219 606.438476562 m 1
+ 1315.19824219 50.4658203125 l 1
+ 1422.59960938 1083.16113281 l 1
+ 1973.94824219 606.438476562 l 1
+1335.19335938 88.2822265625 m 1
+ 1949.30273438 606.565429688 l 1
+ 1435.35546875 1050.98925781 l 1
+ 1335.19335938 88.2822265625 l 1
+1929.93066406 508.56640625 m 1
+ 1514.609375 -196.075195312 l 1
+ 1321.10546875 16.076171875 l 1
+ 1917.95605469 518.779296875 l 1
+ 1929.93066406 508.56640625 l 1
+1511.76269531 -169.321289062 m 1
+ 1892.34765625 476.318359375 l 1
+ 1344.06542969 14.5146484375 l 1
+ 1511.76269531 -169.321289062 l 1
+1392.44238281 1090.69140625 m 1
+ 1285.06542969 43.7470703125 l 1
+ 432.00390625 658.987304688 l 1
+ 1392.44238281 1090.69140625 l 1
+1271.97363281 72.91796875 m 1
+ 1373.73242188 1064.66601562 l 1
+ 463.861328125 655.745117188 l 1
+ 1271.97363281 72.91796875 l 1
+1371.375 1112.43066406 m 1
+ 430.590820312 693.051757812 l 1
+ 547.262695312 1416.63378906 l 1
+ 1371.375 1112.43066406 l 1
+451.11328125 719.681640625 m 1
+ 1328.92675781 1111.07226562 l 1
+ 559.967773438 1394.85253906 l 1
+ 451.11328125 719.681640625 l 1
+1495.53027344 -216.309570312 m 1
+ 601.6328125 -223.778320312 l 1
+ 599.259765625 -208.115234375 l 1
+ 1297.26660156 2.6455078125 l 1
+ 1495.53027344 -216.309570312 l 1
+657.331054688 -207.41796875 m 1
+ 1459.77832031 -200.720703125 l 1
+ 1292.25390625 -15.712890625 l 1
+ 657.331054688 -207.41796875 l 1
+1262.29003906 22.39453125 m 1
+ 542.642578125 -195.427734375 l 1
+ 429.0546875 624.897460938 l 1
+ 1262.29003906 22.39453125 l 1
+556.03125 -174.74609375 m 1
+ 1226.76757812 28.3056640625 l 1
+ 450.149414062 589.927734375 l 1
+ 556.03125 -174.74609375 l 1
+488.509765625 1319.8046875 m 1
+ 392.740234375 675.411132812 l 1
+ 60.8017578125 623.75390625 l 1
+ 473.58984375 1325.10253906 l 1
+ 488.509765625 1319.8046875 l 1
+378.75 689.338867188 m 1
+ 466.651367188 1281.50488281 l 1
+ 91.7822265625 644.78125 l 1
+ 378.75 689.338867188 l 1
+393.4140625 644.2265625 m 1
+ 494.404296875 -130.180664062 l 1
+ 479.5390625 -135.216796875 l 1
+ 58.0107421875 594.891601562 l 1
+ 393.4140625 644.2265625 l 1
+473.291992188 -92.5244140625 m 1
+ 379.591796875 625.9921875 l 1
+ 83.611328125 582.420898438 l 1
+ 473.291992188 -92.5244140625 l 1
+EndSplineSet
+EndChar
+
+StartChar: uni008E
+Encoding: 142 142 160
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+778.16015625 1552.03222656 m 1
+ 778.127929688 1552.03222656 l 1
+ 847.581054688 1503.07617188 933.073242188 1474.3125 1024.42578125 1474.3125 c 0
+ 1115.77929688 1474.3125 1200.46679688 1503.07617188 1269.91992188 1552.03222656 c 1
+ 1722.46386719 1344.55957031 1712.55957031 1309.15234375 2048 941.375976562 c 1
+ 1730.59179688 581.952148438 l 1
+ 1619.56835938 677.952148438 l 1
+ 1619.77636719 -313.568359375 l 1
+ 1063.26367188 -327.471679688 984.83203125 -327.471679688 428.3203125 -313.568359375 c 1
+ 428.48046875 677.919921875 l 1
+ 317.456054688 581.919921875 l 1
+ 0 941.375976562 l 1
+ 335.51953125 1309.15234375 325.51953125 1344.55957031 778.16015625 1552.03222656 c 1
+EndSplineSet
+EndChar
+
+StartChar: uni008F
+Encoding: 143 143 161
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+762.624023438 97.8876953125 m 1
+ 762.65625 96.8642578125 l 1
+ 748.793945312 98.75 738.0390625 110.649414062 738.0390625 125.025390625 c 0
+ 738.0390625 140.709960938 750.7734375 153.444335938 766.458984375 153.444335938 c 0
+ 767.768554688 153.444335938 769.057617188 153.35546875 770.3203125 153.18359375 c 0
+ 851.073242188 142.818359375 931.228515625 137.545898438 1014.78125 137.545898438 c 0
+ 1017.86132812 137.545898438 1020.93945312 137.553710938 1024.015625 137.568359375 c 0
+ 1557.23242188 137.568359375 1991.18359375 351.34375 1991.18359375 614 c 0
+ 1991.18359375 876.65625 1557.26367188 1090.41601562 1024.015625 1090.41601562 c 0
+ 1021.79589844 1090.42382812 1020.73828125 1090.33398438 1018.515625 1090.33398438 c 0
+ 875.844726562 1090.33398438 736.963867188 1073.97753906 603.6484375 1043.04003906 c 1
+ 571.73828125 907.256835938 554.752929688 766.856445312 554.752929688 621.395507812 c 0
+ 554.752929688 618.923828125 554.7578125 616.453125 554.767578125 613.984375 c 0
+ 554.767578125 82.015625 765.200195312 -353.18359375 1024.015625 -353.18359375 c 0
+ 1161.23242188 -353.18359375 1292.04785156 -226.208007812 1382.41601562 -4.767578125 c 0
+ 1386.85546875 5.4189453125 1396.99414062 12.515625 1408.80859375 12.515625 c 0
+ 1424.703125 12.515625 1437.60839844 -0.388671875 1437.60839844 -16.2841796875 c 0
+ 1437.60839844 -19.69921875 1437.01269531 -22.9755859375 1435.91992188 -26.015625 c 0
+ 1336.3359375 -270.240234375 1186.06445312 -410.015625 1024.015625 -410.015625 c 0
+ 728.015625 -410.015625 497.616210938 39.7763671875 497.616210938 613.984375 c 0
+ 497.60546875 616.573242188 497.654296875 617.734375 497.654296875 620.326171875 c 0
+ 497.654296875 759.646484375 512.578125 895.508789062 540.912109375 1026.40039062 c 1
+ 251.887695312 943.967773438 56.81640625 790.112304688 56.81640625 613.984375 c 0
+ 60.0224609375 531.818359375 96.6533203125 458.052734375 153.616210938 406.303710938 c 1
+ 153.616210938 406.303710938 l 1
+ 204.81640625 439.904296875 l 2
+ 208.334960938 442.419921875 212.649414062 443.935546875 217.30078125 443.935546875 c 0
+ 225.323242188 443.935546875 232.325195312 439.530273438 236.015625 433.0078125 c 2
+ 345.18359375 246.368164062 l 2
+ 345.780273438 245.322265625 346.127929688 244.106445312 346.127929688 242.817382812 c 0
+ 346.127929688 238.861328125 342.916015625 235.649414062 338.959960938 235.649414062 c 0
+ 338.34765625 235.649414062 337.751953125 235.7265625 337.18359375 235.872070312 c 2
+ 81.18359375 300.896484375 l 2
+ 68.8935546875 304.026367188 59.751953125 315.157226562 59.751953125 328.416015625 c 0
+ 59.751953125 338.294921875 64.8046875 347.002929688 72.4638671875 352.095703125 c 1
+ 72.4638671875 352.095703125 l 1
+ 106.51171875 374.624023438 l 1
+ 43.0849609375 435.290039062 2.796875 520.041015625 0.015625 613.984375 c 0
+ 0.015625 824.688476562 223.48828125 1003.12011719 556.303710938 1090.14355469 c 1
+ 643.087890625 1418.3359375 819.215820312 1637.984375 1024.015625 1637.984375 c 0
+ 1134.84765625 1637.984375 1240.57617188 1572.38378906 1328.63964844 1451.44042969 c 1
+ 1370.11230469 1481.39160156 l 2
+ 1373.60351562 1483.84570312 1377.87792969 1485.27929688 1382.46582031 1485.27929688 c 0
+ 1393.83007812 1485.27929688 1403.14648438 1476.4375 1403.90429688 1465.26367188 c 2
+ 1416.70410156 1234.86425781 l 2
+ 1416.70605469 1234.80175781 1416.69824219 1234.74707031 1416.69824219 1234.68359375 c 0
+ 1416.69824219 1230.72753906 1413.48730469 1227.515625 1409.53027344 1227.515625 c 0
+ 1408.234375 1227.515625 1407.01757812 1227.86132812 1405.96777344 1228.46386719 c 2
+ 1233.16796875 1334.19238281 l 2
+ 1224.97363281 1339.1796875 1219.50097656 1348.1640625 1219.50097656 1358.45019531 c 0
+ 1219.50097656 1367.96484375 1224.1875 1376.39355469 1231.37597656 1381.55175781 c 1
+ 1231.37597656 1381.55175781 l 1
+ 1282.57617188 1418.3515625 l 1
+ 1205.77636719 1524.08007812 1115.91992188 1581.16796875 1024.015625 1581.16796875 c 0
+ 852.223632812 1581.16796875 701.952148438 1389.984375 620.047851562 1105.77636719 c 1
+ 748.142578125 1132.93457031 879.475585938 1147.26660156 1015.61132812 1147.26660156 c 0
+ 1018.4140625 1147.26660156 1021.21582031 1147.25976562 1024.015625 1147.24804688 c 0
+ 1598.22363281 1147.24804688 2048.015625 913.0078125 2048.015625 614 c 0
+ 2048.015625 314.9921875 1598.22363281 80.751953125 1024.015625 80.751953125 c 0
+ 1022.91113281 80.75 1023.87695312 80.67578125 1022.77246094 80.67578125 c 0
+ 934.575195312 80.67578125 847.736328125 86.5361328125 762.624023438 97.8876953125 c 1
+1547.04003906 892.51171875 m 2
+ 1582.55957031 912.879882812 1611.58398438 896.143554688 1611.55175781 855.120117188 c 2
+ 1611.55175781 436.671875 l 2
+ 1611.55175781 395.83984375 1582.44824219 379.103515625 1547.02441406 399.471679688 c 2
+ 1360.015625 506.975585938 l 1
+ 1360.015625 472.288085938 l 2
+ 1360.00683594 410.70703125 1310.01269531 360.712890625 1248.43164062 360.704101562 c 2
+ 793.232421875 360.704101562 l 2
+ 731.651367188 360.712890625 681.657226562 410.70703125 681.6484375 472.288085938 c 2
+ 681.6484375 819.711914062 l 2
+ 681.666015625 881.28515625 731.65625 931.271484375 793.232421875 931.280273438 c 2
+ 1248.46386719 931.280273438 l 2
+ 1310.04492188 931.271484375 1360.0390625 881.276367188 1360.04785156 819.696289062 c 2
+ 1360.04785156 785.0078125 l 1
+ 1547.04003906 892.51171875 l 2
+EndSplineSet
+EndChar
+
+StartChar: uni0090
+Encoding: 144 144 162
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+731.024414062 321.024414062 m 1
+ 731.024414062 904.127929688 l 1
+ 1314.12792969 904.127929688 l 1
+ 1314.12792969 321.024414062 l 1
+ 731.024414062 321.024414062 l 1
+901.696289062 491.696289062 m 1
+ 1143.47167969 491.696289062 l 1
+ 1143.47167969 733.471679688 l 1
+ 901.696289062 733.471679688 l 1
+ 901.696289062 491.696289062 l 1
+682.671875 -410 m 1
+ 682.671875 -182.448242188 l 1
+ 398.223632812 -182.448242188 l 2
+ 398.040039062 -182.448242188 397.856445312 -182.44921875 397.671875 -182.44921875 c 0
+ 350.850585938 -182.44921875 308.604492188 -162.799804688 278.751953125 -131.248046875 c 0
+ 247.200195312 -101.395507812 227.50390625 -59.1376953125 227.50390625 -12.31640625 c 0
+ 227.50390625 -12.1328125 227.551757812 -11.9599609375 227.551757812 -11.7763671875 c 2
+ 227.551757812 272.671875 l 1
+ 0 272.671875 l 1
+ 0 443.328125 l 1
+ 227.551757812 443.328125 l 1
+ 227.551757812 796.047851562 l 1
+ 0 796.047851562 l 1
+ 0 966.704101562 l 1
+ 227.551757812 966.704101562 l 1
+ 227.551757812 1251.15234375 l 2
+ 227.551757812 1251.3359375 227.55078125 1251.51953125 227.55078125 1251.70410156 c 0
+ 227.55078125 1298.52539062 247.200195312 1340.77148438 278.751953125 1370.62402344 c 0
+ 308.604492188 1402.17578125 350.862304688 1421.87207031 397.68359375 1421.87207031 c 0
+ 397.8671875 1421.87207031 398.040039062 1421.82421875 398.223632812 1421.82421875 c 2
+ 682.671875 1421.82421875 l 1
+ 682.671875 1638 l 1
+ 853.328125 1638 l 1
+ 853.328125 1421.82421875 l 1
+ 1206.04785156 1421.82421875 l 1
+ 1206.04785156 1638 l 1
+ 1376.70410156 1638 l 1
+ 1376.70410156 1421.82421875 l 1
+ 1661.15234375 1421.82421875 l 2
+ 1661.3359375 1421.82421875 1661.51953125 1421.82519531 1661.70410156 1421.82519531 c 0
+ 1708.52539062 1421.82519531 1750.77148438 1402.17578125 1780.62402344 1370.62402344 c 0
+ 1812.17578125 1340.77148438 1831.87207031 1298.51367188 1831.87207031 1251.69238281 c 0
+ 1831.87207031 1251.50878906 1831.82421875 1251.3359375 1831.82421875 1251.15234375 c 2
+ 1831.82421875 966.704101562 l 1
+ 2048 966.704101562 l 1
+ 2048 796.047851562 l 1
+ 1831.82421875 796.047851562 l 1
+ 1831.82421875 443.328125 l 1
+ 2048 443.328125 l 1
+ 2048 272.671875 l 1
+ 1831.82421875 272.671875 l 1
+ 1831.82421875 -11.7763671875 l 2
+ 1829.55175781 -104.912109375 1754.28808594 -180.17578125 1661.15234375 -182.448242188 c 2
+ 1376.70410156 -182.448242188 l 1
+ 1376.70410156 -410 l 1
+ 1206.04785156 -410 l 1
+ 1206.04785156 -182.448242188 l 1
+ 853.328125 -182.448242188 l 1
+ 853.328125 -410 l 1
+ 682.671875 -410 l 1
+1661.16796875 -11.7763671875 m 1
+ 1661.16796875 1251.15234375 l 1
+ 398.223632812 1251.15234375 l 1
+ 398.223632812 -11.7763671875 l 1
+ 1661.16796875 -11.7763671875 l 1
+1024 614 m 1024
+EndSplineSet
+EndChar
+
+StartChar: uni0091
+Encoding: 145 145 163
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+1424.04785156 -410 m 1
+ 1151.53613281 -410 l 1
+ 879.0078125 -277.872070312 l 1
+ 639.536132812 -46.6240234375 l 1
+ 598.223632812 143.311523438 l 1
+ 540.416015625 217.616210938 l 1
+ 416.559570312 283.696289062 l 1
+ 312.688476562 546.608398438 l 1
+ 367.0078125 688.3203125 l 1
+ 416.608398438 714.3203125 l 1
+ 367.0078125 828.719726562 l 1
+ 367.0078125 1216.83203125 l 1
+ 540.431640625 1448.04785156 l 1
+ 879.087890625 1638 l 1
+ 1333.28027344 1588.40039062 l 1
+ 1630.57617188 1365.42382812 l 1
+ 1737.91992188 1043.36035156 l 1
+ 1737.91992188 828.65625 l 1
+ 1781.58398438 779.055664062 l 1
+ 1795.72753906 605.631835938 l 1
+ 1737.91992188 456.9921875 l 1
+ 1737.91992188 143.200195312 l 1
+ 1547.984375 -277.984375 l 1
+ 1424.04785156 -410 l 1
+1106.9921875 -84.591796875 m 1
+ 1106.9921875 -84.591796875 l 1
+ 1170.87988281 -107.120117188 l 1
+ 1385.37597656 -107.120117188 l 1
+ 1519.95214844 -54.736328125 l 1
+ 1433.74414062 -54.736328125 l 1
+ 1227.24804688 -84.576171875 l 1
+ 1106.9921875 -84.591796875 l 1
+1014.06445312 517.6796875 m 1
+ 1014.06445312 517.6796875 l 1
+ 979.171875 516.095703125 946.422851562 504.879882812 918.9921875 486.799804688 c 0
+ 919.103515625 486.463867188 930.528320312 449.055664062 949.072265625 437.935546875 c 0
+ 950.671875 436.944335938 952.671875 435.711914062 954.896484375 434.3203125 c 0
+ 981.306640625 415.5078125 1013.31054688 403.522460938 1047.59960938 401.232421875 c 0
+ 1047.66796875 401.231445312 1047.73632812 401.231445312 1047.80566406 401.231445312 c 0
+ 1060.66210938 401.231445312 1073.02148438 403.59765625 1084.40039062 407.856445312 c 0
+ 1117.29199219 418.118164062 1145.0859375 439.5859375 1163.34375 468 c 1
+ 1126.27246094 485.83984375 1086.80273438 501.166992188 1046.79980469 513.103515625 c 0
+ 1036.55664062 516.071289062 1025.73046875 517.662109375 1014.53613281 517.662109375 c 0
+ 1014.38085938 517.662109375 1014.25097656 517.680664062 1014.09570312 517.6796875 c 2
+ 1014.06445312 517.6796875 l 1
+1587.37597656 554.48046875 m 2
+ 1586.00195312 554.515625 1584.62304688 554.533203125 1583.24023438 554.533203125 c 0
+ 1566.40429688 554.533203125 1550.23730469 551.846679688 1535.02441406 546.944335938 c 0
+ 1500.84277344 532.095703125 1470.56542969 510.545898438 1445.53613281 483.631835938 c 1
+ 1506.06445312 462.559570312 l 2
+ 1521.51171875 457.493164062 1538.03027344 454.690429688 1555.15234375 454.639648438 c 0
+ 1556.25390625 454.606445312 1557.36035156 454.588867188 1558.46972656 454.588867188 c 0
+ 1582.19824219 454.588867188 1604.16503906 462.409179688 1621.88769531 475.536132812 c 0
+ 1670.16015625 512.65625 1655.87207031 546.608398438 1655.72753906 546.944335938 c 0
+ 1633.59570312 551.66015625 1610.85546875 554.248046875 1587.37597656 554.463867188 c 2
+ 1587.37597656 554.48046875 l 2
+EndSplineSet
+EndChar
+
+StartChar: uni0092
+Encoding: 146 146 164
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+253.727539062 1125.16796875 m 1
+ 523.280273438 1062.95996094 l 1
+ 841.215820312 1097.51953125 l 1
+ 1101.15234375 1097.51953125 l 1
+ 1277.328125 1062.95996094 l 1
+ 1387.44042969 1076.41601562 l 1
+ 1453.50390625 1076.41601562 l 1
+ 1481.82421875 997.759765625 l 1
+ 1349.69628906 909.6796875 l 1
+ 1204.97558594 878.223632812 l 1
+ 1158.57617188 885.103515625 l 1
+ 988.975585938 941.280273438 l 1
+ 1088.17578125 885.103515625 l 1
+ 1415.6796875 815.295898438 l 1
+ 1601.28027344 771.280273438 l 1
+ 1755.45605469 717.776367188 l 1
+ 1941.05566406 661.15234375 l 1
+ 2048.015625 573.072265625 l 1
+ 1994.52832031 497.568359375 l 1
+ 1824.63964844 497.568359375 l 1
+ 1533.15234375 573.072265625 l 1
+ 1400.95996094 618.112304688 l 1
+ 1268.81640625 645.82421875 l 1
+ 1522.70410156 547.919921875 l 1
+ 1714.60839844 459.807617188 l 1
+ 1953.69628906 330.83203125 l 1
+ 1972.57617188 239.631835938 l 1
+ 1843.59960938 208.17578125 l 1
+ 1453.50390625 349.744140625 l 1
+ 1183.10351562 439.055664062 l 1
+ 1471.10351562 286.81640625 l 1
+ 1730.30371094 120.080078125 l 1
+ 1730.30371094 120.080078125 1717.72753906 79.18359375 1714.57617188 66.591796875 c 0
+ 1711.42382812 54 1620.17578125 19.408203125 1620.17578125 19.408203125 c 1
+ 1374.28808594 153.48828125 l 1
+ 1037.95214844 286.799804688 l 1
+ 977.423828125 286.799804688 l 1
+ 897.568359375 310.799804688 l 1
+ 1220.62402344 -30.912109375 l 1
+ 1248.94433594 -106.416015625 l 1
+ 1164 -147.311523438 l 1
+ 805.360351562 107.50390625 l 1
+ 723.568359375 186.16015625 l 1
+ 361.791992188 349.711914062 l 1
+ 103.82421875 538.51171875 l 1
+ 0 629.711914062 l 1
+ 103.82421875 909.711914062 l 1
+ 253.727539062 1125.16796875 l 1
+EndSplineSet
+EndChar
+
+StartChar: uni0093
+Encoding: 147 147 165
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+957.055664062 1638 m 1
+ 1033.53613281 1638 l 1
+ 1098.68847656 1555.85644531 l 1
+ 1098.68847656 1414.22363281 l 1
+ 1073.20019531 1349.07226562 l 1
+ 1217.6640625 1300.91210938 l 1
+ 1315.26367188 1214.16015625 l 1
+ 1514.63964844 1077.13574219 l 1
+ 1651.02441406 969.48828125 l 1
+ 1815.31152344 856.17578125 l 1
+ 1874.51171875 799.200195312 l 1
+ 1843.59960938 791.024414062 l 1
+ 1785.18359375 807.024414062 l 1
+ 1726.9921875 819.295898438 l 1
+ 1644.19238281 892.448242188 l 1
+ 1485.95214844 959.072265625 l 1
+ 1308.24023438 1077.07226562 l 1
+ 1217.59960938 1150.671875 l 1
+ 1175.10351562 994.879882812 l 1
+ 1197.75976562 856.080078125 l 1
+ 1175.10351562 790.927734375 l 1
+ 1217.59960938 581.328125 l 1
+ 1217.59960938 357.551757812 l 1
+ 1197.77636719 221.551757812 l 1
+ 1197.77636719 3.4404296875 l 1
+ 1175.12011719 -209.0078125 l 1
+ 1237.44042969 -279.82421875 l 1
+ 1285.59960938 -339.311523438 l 1
+ 1237.44042969 -381.807617188 l 1
+ 1129.79199219 -308.208007812 l 1
+ 1073.13574219 -279.887695312 l 1
+ 1098.62402344 -172.255859375 l 1
+ 1059.02441406 51.7763671875 l 1
+ 1073.18359375 179.248046875 l 1
+ 1005.20019531 465.34375 l 1
+ 949.568359375 255.120117188 l 1
+ 915.583984375 121.984375 l 1
+ 880.576171875 -24.7041015625 l 1
+ 860.751953125 -186.208007812 l 1
+ 906.080078125 -361.83984375 l 1
+ 940.064453125 -410 l 1
+ 860.751953125 -410 l 1
+ 787.15234375 -381.6796875 l 1
+ 770.16015625 -279.711914062 l 1
+ 787.15234375 -228.719726562 l 1
+ 744.65625 23.3916015625 l 1
+ 787.15234375 179.18359375 l 1
+ 787.15234375 598.383789062 l 1
+ 815.471679688 791.0078125 l 1
+ 815.471679688 943.967773438 l 1
+ 787.15234375 1050.97558594 l 1
+ 648.240234375 892.48046875 l 1
+ 529.375976562 725.856445312 l 1
+ 563.360351562 657.872070312 l 1
+ 563.360351562 629.6640625 l 1
+ 542.736328125 621.6640625 l 1
+ 516.944335938 633.568359375 l 1
+ 486.83203125 581.375976562 l 1
+ 464.17578125 558.719726562 l 1
+ 407.51953125 541.727539062 l 1
+ 384.864257812 581.375976562 l 1
+ 447.18359375 689.024414062 l 1
+ 486.83203125 819.328125 l 1
+ 512.3203125 918.528320312 l 1
+ 676.624023438 1125.37597656 l 1
+ 787.024414062 1267.0078125 l 1
+ 906 1349.15234375 l 1
+ 922.9921875 1380.30371094 l 1
+ 906 1425.63183594 l 1
+ 880.51171875 1470.95996094 l 1
+ 892.431640625 1555.93554688 l 1
+ 906.015625 1612.59179688 l 1
+ 957.055664062 1638 l 1
+955.15234375 1646 m 1
+ 1037.39160156 1646 l 1
+ 1106.671875 1558.63964844 l 1
+ 1106.671875 1412.70410156 l 1
+ 1083.69628906 1353.984375 l 1
+ 1221.72753906 1307.96777344 l 1
+ 1320.22363281 1220.46386719 l 1
+ 1519.61621094 1083.40820312 l 1
+ 1655.79199219 975.919921875 l 1
+ 1820.41601562 862.3203125 l 1
+ 1820.87988281 861.872070312 l 1
+ 1890.38378906 795.024414062 l 1
+ 1843.61621094 782.671875 l 1
+ 1783.34375 799.248046875 l 1
+ 1723.328125 811.904296875 l 1
+ 1639.93554688 885.50390625 l 1
+ 1482.20800781 951.904296875 l 1
+ 1303.24804688 1070.78417969 l 1
+ 1222.12792969 1136.68847656 l 1
+ 1183.328125 994.416015625 l 1
+ 1206.03222656 855.328125 l 1
+ 1183.42382812 790.3359375 l 1
+ 1225.63183594 582.080078125 l 1
+ 1225.63183594 356.912109375 l 1
+ 1225.55175781 356.3359375 l 1
+ 1205.80761719 220.944335938 l 1
+ 1205.80761719 2.9921875 l 1
+ 1205.80761719 2.576171875 l 1
+ 1183.51953125 -206.383789062 l 1
+ 1243.72753906 -274.81640625 l 1
+ 1296.70410156 -340.255859375 l 1
+ 1238.12792969 -391.935546875 l 1
+ 1125.79199219 -315.135742188 l 1
+ 1063.95214844 -284.223632812 l 1
+ 1090.52832031 -172.032226562 l 1
+ 1050.95996094 51.248046875 l 1
+ 1065.10351562 178.48046875 l 1
+ 1004.75195312 432.448242188 l 1
+ 957.263671875 253.024414062 l 1
+ 923.311523438 120.015625 l 1
+ 888.431640625 -26.17578125 l 1
+ 868.84765625 -185.6796875 l 1
+ 913.423828125 -358.48046875 l 1
+ 955.471679688 -418.047851562 l 1
+ 859.232421875 -418.047851562 l 1
+ 779.935546875 -387.551757812 l 1
+ 761.856445312 -279.103515625 l 1
+ 778.84765625 -228.127929688 l 1
+ 736.400390625 23.744140625 l 1
+ 779.072265625 180.208007812 l 1
+ 779.072265625 598.975585938 l 1
+ 779.15234375 599.551757812 l 1
+ 807.391601562 791.551757812 l 1
+ 807.391601562 942.975585938 l 1
+ 783.18359375 1034.44824219 l 1
+ 654.448242188 887.551757812 l 1
+ 538.608398438 725.16796875 l 1
+ 571.311523438 659.776367188 l 1
+ 571.311523438 624.192382812 l 1
+ 542.400390625 613.072265625 l 1
+ 520.17578125 623.328125 l 1
+ 493.232421875 576.576171875 l 1
+ 468.400390625 551.744140625 l 1
+ 403.727539062 532.3359375 l 1
+ 375.631835938 581.50390625 l 1
+ 439.791992188 692.3359375 l 1
+ 479.120117188 821.583984375 l 1
+ 504.9921875 922.208007812 l 1
+ 506.047851562 923.536132812 l 1
+ 670.3515625 1130.27246094 l 1
+ 781.536132812 1272.81640625 l 1
+ 899.935546875 1354.57617188 l 1
+ 914.3359375 1380.84765625 l 1
+ 898.84765625 1422.16015625 l 1
+ 872.272460938 1469.37597656 l 1
+ 884.704101562 1557.79199219 l 1
+ 899.103515625 1618.06445312 l 1
+ 955.15234375 1646 l 1
+1029.6484375 1630 m 1
+ 958.927734375 1630 l 1
+ 912.959960938 1607.02441406 l 1
+ 900.3359375 1554.36816406 l 1
+ 888.84765625 1472.44824219 l 1
+ 913.311523438 1428.94433594 l 1
+ 931.807617188 1379.61621094 l 1
+ 912.143554688 1343.56835938 l 1
+ 792.608398438 1261.04003906 l 1
+ 682.896484375 1120.36816406 l 1
+ 519.696289062 914.864257812 l 1
+ 494.528320312 817.040039062 l 1
+ 454.528320312 685.83984375 l 1
+ 454.064453125 685.040039062 l 1
+ 394.047851562 581.375976562 l 1
+ 411.263671875 551.232421875 l 1
+ 459.887695312 565.82421875 l 1
+ 480.368164062 586.303710938 l 1
+ 513.551757812 643.904296875 l 1
+ 542.912109375 630.3515625 l 1
+ 555.263671875 635.15234375 l 1
+ 555.263671875 655.952148438 l 1
+ 519.967773438 726.543945312 l 1
+ 642.112304688 897.744140625 l 1
+ 790.912109375 1067.53613281 l 1
+ 823.34375 944.9921875 l 1
+ 823.34375 790.400390625 l 1
+ 823.263671875 789.82421875 l 1
+ 795.024414062 597.82421875 l 1
+ 795.024414062 178.17578125 l 1
+ 752.719726562 22.9755859375 l 1
+ 795.248046875 -229.360351562 l 1
+ 778.255859375 -280.3515625 l 1
+ 794.17578125 -375.856445312 l 1
+ 862.17578125 -402.015625 l 1
+ 924.576171875 -402.015625 l 1
+ 898.639648438 -365.215820312 l 1
+ 852.576171875 -186.704101562 l 1
+ 872.639648438 -23.2958984375 l 1
+ 872.751953125 -22.8642578125 l 1
+ 907.791992188 123.952148438 l 1
+ 941.776367188 257.087890625 l 1
+ 997.440429688 467.375976562 l 1
+ 1012.95996094 467.18359375 l 1
+ 1081.26367188 179.744140625 l 1
+ 1067.07226562 52.0322265625 l 1
+ 1106.84765625 -172.223632812 l 1
+ 1082.43164062 -275.311523438 l 1
+ 1133.90429688 -301.055664062 l 1
+ 1236.86425781 -371.456054688 l 1
+ 1274.59179688 -338.16015625 l 1
+ 1231.39160156 -284.767578125 l 1
+ 1166.86425781 -211.440429688 l 1
+ 1189.85644531 4.080078125 l 1
+ 1189.85644531 222.272460938 l 1
+ 1189.93554688 222.84765625 l 1
+ 1209.6796875 358.240234375 l 1
+ 1209.6796875 580.639648438 l 1
+ 1166.91210938 791.616210938 l 1
+ 1189.63183594 856.927734375 l 1
+ 1167.02441406 995.440429688 l 1
+ 1213.20019531 1164.75195312 l 1
+ 1313.07226562 1083.59960938 l 1
+ 1489.83984375 966.223632812 l 1
+ 1648.59179688 899.391601562 l 1
+ 1730.79980469 826.767578125 l 1
+ 1787.39160156 814.83203125 l 1
+ 1843.72753906 799.34375 l 1
+ 1858.79980469 803.328125 l 1
+ 1810.28808594 850 l 1
+ 1646.11230469 963.248046875 l 1
+ 1509.93554688 1070.71972656 l 1
+ 1310.3515625 1207.85644531 l 1
+ 1309.984375 1208.19238281 l 1
+ 1213.58398438 1293.85644531 l 1
+ 1062.68847656 1344.16015625 l 1
+ 1090.70410156 1415.74414062 l 1
+ 1090.70410156 1553.08789062 l 1
+ 1029.6484375 1630 l 1
+EndSplineSet
+EndChar
+
+StartChar: uni0094
+Encoding: 148 148 166
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+1867.74414062 1170.63964844 m 2
+ 1076.97558594 1170.63964844 l 1
+ 1107.43066406 1225.58789062 1124.77539062 1288.79003906 1124.77539062 1356.01171875 c 0
+ 1124.77539062 1406.04785156 1115.25 1454.48339844 1097.77636719 1498.3203125 c 1
+ 1867.74414062 1498.3203125 l 2
+ 1958.16992188 1498.3203125 2031.58398438 1424.90527344 2031.58398438 1334.48046875 c 0
+ 2031.58398438 1244.0546875 1958.16992188 1170.63964844 1867.74414062 1170.63964844 c 2
+408.543945312 1170.63964844 m 1
+ 163.83984375 1170.63964844 l 2
+ 73.4140625 1170.63964844 0 1244.0546875 0 1334.48046875 c 0
+ 0 1424.90527344 73.4140625 1498.3203125 163.83984375 1498.3203125 c 2
+ 387.6796875 1498.3203125 l 1
+ 370.208984375 1454.48828125 360.600585938 1406.68457031 360.600585938 1356.65332031 c 0
+ 360.600585938 1289.4375 378.028320312 1225.61523438 408.48046875 1170.671875 c 2
+ 408.543945312 1170.63964844 l 1
+1867.74414062 449.776367188 m 2
+ 1743.56835938 449.776367188 l 1
+ 1767.02734375 499.038085938 1780.16015625 554.150390625 1780.16015625 612.3046875 c 0
+ 1780.16015625 612.633789062 1780.36914062 613.287109375 1780.36816406 613.616210938 c 0
+ 1780.36914062 613.936523438 1780.36914062 614.2578125 1780.36914062 614.579101562 c 0
+ 1780.36914062 672.736328125 1767.02734375 728.17578125 1743.56835938 777.440429688 c 1
+ 1867.74414062 777.440429688 l 2
+ 1958.16503906 777.440429688 2031.57617188 704.029296875 2031.57617188 613.608398438 c 0
+ 2031.57617188 523.186523438 1958.16503906 449.776367188 1867.74414062 449.776367188 c 2
+1052.55957031 449.776367188 m 1
+ 163.83984375 449.776367188 l 2
+ 73.4189453125 449.776367188 0.0078125 523.186523438 0.0078125 613.608398438 c 0
+ 0.0078125 704.029296875 73.4189453125 777.440429688 163.83984375 777.440429688 c 2
+ 1052.57617188 777.440429688 l 1
+ 1029.11621094 728.181640625 1015.98339844 673.073242188 1015.98339844 614.921875 c 0
+ 1015.98339844 614.594726562 1015.77539062 613.943359375 1015.77636719 613.616210938 c 0
+ 1015.77539062 613.302734375 1015.77441406 612.990234375 1015.77441406 612.676757812 c 0
+ 1015.77441406 554.517578125 1029.11621094 499.075195312 1052.57617188 449.807617188 c 2
+ 1052.55957031 449.776367188 l 1
+1867.74414062 -271.120117188 m 2
+ 1219.29589844 -271.120117188 l 1
+ 1242.75585938 -221.857421875 1255.88867188 -166.74609375 1255.88867188 -108.590820312 c 0
+ 1255.88867188 -108.26171875 1256.09667969 -107.608398438 1256.09570312 -107.280273438 c 0
+ 1256.09667969 -106.958984375 1256.09765625 -106.638671875 1256.09765625 -106.317382812 c 0
+ 1256.09765625 -48.16015625 1242.75585938 7.279296875 1219.29589844 56.5439453125 c 1
+ 1867.74414062 56.5439453125 l 2
+ 1958.16503906 56.5439453125 2031.57617188 -16.8662109375 2031.57617188 -107.288085938 c 0
+ 2031.57617188 -197.708984375 1958.16503906 -271.120117188 1867.74414062 -271.120117188 c 2
+528.288085938 -271.120117188 m 1
+ 163.83984375 -271.120117188 l 2
+ 73.4189453125 -271.120117188 0.0078125 -197.708984375 0.0078125 -107.288085938 c 0
+ 0.0078125 -16.8662109375 73.4189453125 56.5439453125 163.83984375 56.5439453125 c 2
+ 528.303710938 56.5439453125 l 1
+ 504.844726562 7.2822265625 491.711914062 -47.830078125 491.711914062 -105.985351562 c 0
+ 491.711914062 -106.30859375 491.502929688 -106.95703125 491.50390625 -107.280273438 c 0
+ 491.502929688 -107.592773438 491.502929688 -107.90625 491.502929688 -108.21875 c 0
+ 491.502929688 -166.37890625 504.844726562 -221.821289062 528.303710938 -271.087890625 c 2
+ 528.288085938 -271.120117188 l 1
+448 1334 m 0
+ 448 1501.89453125 580.5234375 1638 744 1638 c 0
+ 907.4765625 1638 1040 1501.89453125 1040 1334 c 0
+ 1040 1166.10546875 907.4765625 1030 744 1030 c 0
+ 580.5234375 1030 448 1166.10546875 448 1334 c 0
+1104 582 m 0
+ 1104 741.057617188 1236.5234375 870 1400 870 c 0
+ 1563.4765625 870 1696 741.057617188 1696 582 c 0
+ 1696 422.942382812 1563.4765625 294 1400 294 c 0
+ 1236.5234375 294 1104 422.942382812 1104 582 c 0
+608 -106 m 0
+ 608 61.89453125 736.942382812 198 896 198 c 0
+ 1055.05761719 198 1184 61.89453125 1184 -106 c 0
+ 1184 -273.89453125 1055.05761719 -410 896 -410 c 0
+ 736.942382812 -410 608 -273.89453125 608 -106 c 0
+EndSplineSet
+EndChar
+
+StartChar: uni0095
+Encoding: 149 149 167
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+0 969.551757812 m 0
+ -0.001953125 970.041992188 -0.001953125 970.532226562 -0.001953125 971.022460938 c 0
+ -0.001953125 1213.56445312 196.911132812 1410.47851562 439.453125 1410.47851562 c 0
+ 439.944335938 1410.47851562 440.40625 1410.44921875 440.896484375 1410.44824219 c 2
+ 1433.59960938 1410.44824219 l 1
+ 1457.13085938 1475.59960938 1498.21191406 1531.92578125 1551.6484375 1574 c 0
+ 1601.1328125 1613.96484375 1664.08398438 1637.90722656 1732.58300781 1637.90722656 c 0
+ 1733.37304688 1637.90722656 1734.31542969 1638.00683594 1735.10351562 1638 c 0
+ 1736.93066406 1638.03320312 1738.76074219 1638.04980469 1740.59472656 1638.04980469 c 0
+ 1825.66992188 1638.04980469 1902.421875 1602.58398438 1956.97558594 1545.55175781 c 0
+ 2013.22363281 1489.95019531 2048.09570312 1412.77050781 2048.09570312 1327.50976562 c 0
+ 2048.09570312 1325.74023438 2048.03027344 1324.03417969 2048 1322.27246094 c 0
+ 2048.02148438 1320.79882812 2048.03222656 1319.32324219 2048.03222656 1317.84570312 c 0
+ 2048.03222656 1233.61914062 2013.15136719 1157.5546875 1956.97558594 1103.24804688 c 0
+ 1902.13183594 1047.03222656 1825.56152344 1012.10742188 1740.89941406 1012.10742188 c 0
+ 1738.93652344 1012.10742188 1737.05761719 1012.18652344 1735.10351562 1012.22363281 c 0
+ 1734.31054688 1012.21777344 1733.515625 1012.21386719 1732.72070312 1012.21386719 c 0
+ 1664.22167969 1012.21386719 1601.11621094 1036.25878906 1551.63183594 1076.22363281 c 0
+ 1498.20214844 1118.30078125 1457.12597656 1174.62597656 1433.59960938 1239.77636719 c 1
+ 440.896484375 1239.77636719 l 2
+ 440.654296875 1239.77636719 440.413085938 1239.77734375 440.170898438 1239.77734375 c 0
+ 291.41015625 1239.77734375 170.634765625 1119.00195312 170.634765625 970.241210938 c 0
+ 170.634765625 969.0390625 170.65625 967.90234375 170.671875 966.704101562 c 0
+ 170.627929688 964.75 170.60546875 962.791015625 170.60546875 960.827148438 c 0
+ 170.60546875 888.041015625 201.205078125 822.465820312 250.3203125 776.127929688 c 0
+ 298.209960938 728.625976562 364.12109375 699.266601562 436.834960938 699.266601562 c 0
+ 438.201171875 699.266601562 439.53515625 699.307617188 440.896484375 699.328125 c 2
+ 1607.10351562 699.328125 l 2
+ 1610 699.38671875 1612.90332031 699.415039062 1615.81347656 699.415039062 c 0
+ 1735.00683594 699.415039062 1842.70703125 650.393554688 1920 571.328125 c 0
+ 1999.06542969 494.034179688 2048.16992188 386.21875 2048.16992188 267.025390625 c 0
+ 2048.16992188 264.12109375 2048.05761719 261.338867188 2048 258.448242188 c 0
+ 2048.05859375 255.551757812 2048.08789062 252.6484375 2048.08789062 249.739257812 c 0
+ 2048.08789062 130.544921875 1999.06640625 22.845703125 1920 -54.4482421875 c 0
+ 1842.70703125 -133.513671875 1734.890625 -182.619140625 1615.69726562 -182.619140625 c 0
+ 1612.78710938 -182.619140625 1610 -182.505859375 1607.10351562 -182.448242188 c 2
+ 614.400390625 -182.448242188 l 1
+ 590.869140625 -247.599609375 549.788085938 -303.92578125 496.3515625 -346 c 0
+ 446.8671875 -385.96484375 383.916015625 -409.907226562 315.416992188 -409.907226562 c 0
+ 314.622070312 -409.907226562 313.673828125 -410.006835938 312.879882812 -410 c 0
+ 311.59375 -410.015625 310.306640625 -410.024414062 309.016601562 -410.024414062 c 0
+ 224.192382812 -410.024414062 147.498046875 -375.184570312 92.431640625 -318.975585938 c 0
+ 36.015625 -262.258789062 1.130859375 -184.104492188 1.130859375 -97.8662109375 c 0
+ 1.130859375 74.572265625 140.684570312 214.73828125 312.879882812 215.776367188 c 0
+ 313.470703125 215.779296875 314.060546875 215.78125 314.65234375 215.78125 c 0
+ 383.78125 215.78125 447.548828125 191.763671875 497.776367188 151.776367188 c 0
+ 551.560546875 110.481445312 592.439453125 53.8583984375 614.400390625 -11.7763671875 c 1
+ 1607.10351562 -11.7763671875 l 2
+ 1608.13476562 -11.7880859375 1609.16796875 -11.7939453125 1610.20117188 -11.7939453125 c 0
+ 1757.67285156 -11.7939453125 1877.40136719 107.934570312 1877.40136719 255.40625 c 0
+ 1877.40136719 256.440429688 1877.33984375 257.416992188 1877.328125 258.448242188 c 0
+ 1877.33984375 259.479492188 1877.34570312 260.51171875 1877.34570312 261.544921875 c 0
+ 1877.34570312 409.016601562 1757.6171875 528.745117188 1610.14550781 528.745117188 c 0
+ 1609.11132812 528.745117188 1608.13476562 528.68359375 1607.10351562 528.671875 c 2
+ 440.879882812 528.671875 l 2
+ 440.395507812 528.670898438 439.911132812 528.669921875 439.42578125 528.669921875 c 0
+ 196.883789062 528.669921875 -0.0302734375 725.583007812 -0.0302734375 968.125976562 c 0
+ -0.0302734375 968.610351562 -0.001953125 969.067382812 0 969.551757812 c 0
+1592.89648438 1322.27246094 m 0
+ 1592.89355469 1321.93066406 1592.89257812 1321.58984375 1592.89257812 1321.24804688 c 0
+ 1592.89257812 1282.828125 1609.31152344 1248.25292969 1635.56835938 1224.14355469 c 0
+ 1661.25976562 1198.63183594 1696.63671875 1182.86035156 1735.66894531 1182.86035156 c 0
+ 1736.44238281 1182.86035156 1737.19726562 1182.88378906 1737.96777344 1182.89648438 c 0
+ 1738.44335938 1182.890625 1738.91992188 1182.88867188 1739.39648438 1182.88867188 c 0
+ 1777.39453125 1182.88867188 1811.69824219 1198.703125 1836.09570312 1224.14355469 c 0
+ 1861.57714844 1249.08007812 1877.40039062 1283.84765625 1877.40039062 1322.28125 c 0
+ 1877.40039062 1323.24316406 1877.36328125 1324.16308594 1877.34375 1325.12011719 c 0
+ 1877.34570312 1325.40234375 1877.34667969 1325.68554688 1877.34667969 1325.96777344 c 0
+ 1877.34667969 1364.56933594 1861.57324219 1399.50976562 1836.09570312 1424.671875 c 0
+ 1811.453125 1450.96777344 1776.41601562 1467.40820312 1737.56933594 1467.40820312 c 0
+ 1736.73535156 1467.40820312 1735.95019531 1467.359375 1735.12011719 1467.34375 c 0
+ 1734.97265625 1467.34472656 1734.82421875 1467.34472656 1734.67675781 1467.34472656 c 0
+ 1695.65527344 1467.34472656 1660.44726562 1450.96777344 1635.56835938 1424.671875 c 0
+ 1609.23730469 1399.26757812 1592.85058594 1363.62304688 1592.85058594 1324.18066406 c 0
+ 1592.85058594 1323.53417969 1592.88769531 1322.91699219 1592.89648438 1322.27246094 c 0
+170.671875 -99.9521484375 m 0
+ 170.668945312 -100.29296875 170.66796875 -100.633789062 170.66796875 -100.975585938 c 0
+ 170.66796875 -139.396484375 187.087890625 -173.970703125 213.34375 -198.080078125 c 0
+ 239.036132812 -223.591796875 274.413085938 -239.36328125 313.444335938 -239.36328125 c 0
+ 314.21875 -239.36328125 314.97265625 -239.340820312 315.744140625 -239.328125 c 0
+ 316.219726562 -239.333007812 316.696289062 -239.3359375 317.172851562 -239.3359375 c 0
+ 355.170898438 -239.3359375 389.473632812 -223.520507812 413.872070312 -198.080078125 c 0
+ 439.353515625 -173.143554688 455.17578125 -138.375976562 455.17578125 -99.9423828125 c 0
+ 455.17578125 -98.9814453125 455.139648438 -98.060546875 455.120117188 -97.103515625 c 0
+ 455.122070312 -96.8212890625 455.122070312 -96.5390625 455.122070312 -96.255859375 c 0
+ 455.122070312 -57.654296875 439.348632812 -22.71484375 413.872070312 2.4482421875 c 0
+ 389.228515625 28.744140625 354.19140625 45.1845703125 315.345703125 45.1845703125 c 0
+ 314.51171875 45.1845703125 313.7265625 45.134765625 312.896484375 45.1201171875 c 0
+ 312.748046875 45.1201171875 312.600585938 45.12109375 312.453125 45.12109375 c 0
+ 273.431640625 45.12109375 238.22265625 28.744140625 213.34375 2.4482421875 c 0
+ 187.012695312 -22.95703125 170.625976562 -58.6015625 170.625976562 -98.04296875 c 0
+ 170.625976562 -98.689453125 170.663085938 -99.3076171875 170.671875 -99.9521484375 c 0
+1735.10351562 1325.10351562 m 1024
+312.896484375 -97.103515625 m 1024
+EndSplineSet
+EndChar
+
+StartChar: uni0096
+Encoding: 150 150 168
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+1985.77636719 1342.9921875 m 1
+ 1985.79199219 1343.04003906 l 2
+ 2020.08984375 1342.94335938 2047.91894531 1315.11328125 2048.015625 1280.81640625 c 2
+ 2048.015625 19.4404296875 l 2
+ 2048.015625 -14.7841796875 1985.79199219 -42.7841796875 1985.79199219 -42.7841796875 c 1
+ 62.2236328125 -216.576171875 l 1
+ 62.2236328125 -216.576171875 0 -188.576171875 0 -154.3515625 c 2
+ 0 1444.43164062 l 2
+ 0.0966796875 1478.72949219 27.9267578125 1506.55957031 62.2236328125 1506.65625 c 2
+ 1985.77636719 1342.9921875 l 1
+400.400390625 1171.40820312 m 2
+ 400.448242188 175.696289062 l 2
+ 400.448242188 175.072265625 401.072265625 174.448242188 401.072265625 173.200195312 c 0
+ 401.30078125 168.665039062 402.170898438 164.25 403.568359375 160.127929688 c 0
+ 404.09765625 156.432617188 405.172851562 152.833984375 406.671875 149.551757812 c 0
+ 408.31640625 146.489257812 410.184570312 143.583984375 412.272460938 140.83203125 c 0
+ 414.663085938 136.869140625 417.575195312 133.342773438 420.9921875 130.255859375 c 0
+ 421.616210938 129.631835938 421.616210938 129.0078125 422.240234375 128.383789062 c 0
+ 425.197265625 126.296875 428.3125 124.424804688 431.568359375 122.784179688 c 0
+ 434.325195312 120.907226562 437.244140625 119.239257812 440.288085938 117.807617188 c 0
+ 447.267578125 115.188476562 454.844726562 113.650390625 462.688476562 113.456054688 c 2
+ 1707.28027344 180.192382812 l 2
+ 1714.71386719 180.383789062 1721.88183594 181.927734375 1728.43164062 184.543945312 c 0
+ 1730.12597656 185.0625 1731.81054688 185.698242188 1733.40820312 186.416015625 c 0
+ 1734.65625 187.040039062 1736.51171875 187.040039062 1737.75976562 188.288085938 c 0
+ 1739.79296875 189.779296875 1741.64355469 191.423828125 1743.36035156 193.263671875 c 0
+ 1747.07910156 195.485351562 1750.39746094 198.188476562 1753.31152344 201.360351562 c 0
+ 1755.80664062 204.53125 1758.08105469 207.833984375 1760.16015625 211.311523438 c 0
+ 1762.0546875 214.6796875 1763.734375 218.243164062 1765.13574219 221.887695312 c 0
+ 1766.734375 225.524414062 1767.63671875 229.555664062 1767.63671875 233.780273438 c 0
+ 1767.63671875 233.965820312 1767.63574219 234.151367188 1767.63183594 234.3359375 c 0
+ 1768.52832031 236.905273438 1769.171875 239.6484375 1769.50390625 242.431640625 c 2
+ 1769.50390625 1104.71972656 l 2
+ 1769.40722656 1139.01757812 1741.57714844 1166.84765625 1707.28027344 1166.94433594 c 2
+ 462.624023438 1233.63183594 l 2
+ 428.326171875 1233.53515625 400.497070312 1205.70507812 400.400390625 1171.40820312 c 2
+833.50390625 673.568359375 m 1
+ 833.551757812 673.551757812 l 1
+ 815.6328125 672.841796875 799.633789062 664.55078125 788.751953125 651.759765625 c 2
+ 524.896484375 343.727539062 l 1
+ 524.896484375 1109.13574219 l 1
+ 1644.89648438 1042.46386719 l 1
+ 1644.89648438 469.263671875 l 1
+ 1450.25585938 766.288085938 l 2
+ 1439.75976562 785.341796875 1419.49707031 798.193359375 1396.22363281 798.193359375 c 0
+ 1380.10546875 798.193359375 1365.42285156 791.99609375 1354.43164062 781.856445312 c 2
+ 1040.17578125 494.975585938 l 1
+ 880.17578125 655.51953125 l 2
+ 868.912109375 666.71484375 853.427734375 673.629882812 836.30859375 673.629882812 c 0
+ 835.368164062 673.629882812 834.43359375 673.609375 833.50390625 673.568359375 c 1
+1042.68847656 1000.27246094 m 0
+ 964.3515625 1000.27246094 882.688476562 923.599609375 882.688476562 846.927734375 c 0
+ 882.688476562 770.255859375 964.3515625 693.583984375 1042.68847656 693.583984375 c 0
+ 1121.02441406 693.583984375 1196.03222656 770.255859375 1196.03222656 846.927734375 c 0
+ 1196.03222656 923.599609375 1121.08789062 1000.27246094 1042.68847656 1000.27246094 c 0
+EndSplineSet
+EndChar
+
+StartChar: uni0097
+Encoding: 151 151 169
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+299.51953125 -110.48046875 m 1
+ 207.21875 -17.8740234375 132.900390625 91.447265625 80.6396484375 213.360351562 c 0
+ 29.0234375 333.56640625 0.43359375 465.9609375 0.43359375 604.981445312 c 0
+ 0.43359375 607.80078125 -0.0234375 611.185546875 0 614 c 0
+ -0.0234375 616.814453125 -0.03515625 619.630859375 -0.03515625 622.450195312 c 0
+ -0.03515625 761.470703125 29.0234375 894.43359375 80.6396484375 1014.63964844 c 0
+ 132.900390625 1136.55273438 207.21875 1245.87402344 299.51953125 1338.48046875 c 1
+ 409.599609375 1228.40039062 l 1
+ 331.022460938 1149.59863281 267.61328125 1056.74316406 222.719726562 953.200195312 c 0
+ 178.498046875 851.44921875 153.974609375 739.190429688 153.974609375 621.249023438 c 0
+ 153.974609375 618.998046875 153.58203125 616.247070312 153.599609375 614 c 0
+ 153.578125 611.497070312 153.567382812 608.9921875 153.567382812 606.484375 c 0
+ 153.567382812 489.375 178.012695312 377.369140625 221.440429688 276.080078125 c 0
+ 266.481445312 171.9140625 330.3515625 78.5966796875 409.599609375 -0.400390625 c 1
+ 299.51953125 -110.48046875 l 1
+588.799804688 178.799804688 m 1
+ 533.796875 234.12890625 489.4765625 299.364257812 458.240234375 372.080078125 c 0
+ 427.104492188 444.444335938 409.85546875 524.16796875 409.85546875 607.888671875 c 0
+ 409.85546875 609.817382812 409.58203125 612.075195312 409.599609375 614 c 0
+ 409.58203125 615.924804688 409.572265625 617.850585938 409.572265625 619.779296875 c 0
+ 409.572265625 703.500976562 427.104492188 783.555664062 458.240234375 855.919921875 c 0
+ 489.4765625 928.635742188 533.796875 993.87109375 588.799804688 1049.20019531 c 1
+ 698.879882812 939.120117188 l 2
+ 657.598632812 897.596679688 624.1875 848.827148438 600.3203125 794.48046875 c 0
+ 576.565429688 738.885742188 563.4140625 677.696289062 563.4140625 613.458984375 c 0
+ 563.4140625 550.377929688 576.088867188 489.590820312 599.040039062 434.799804688 c 0
+ 623.057617188 379.831054688 656.928710938 330.599609375 698.879882812 288.879882812 c 1
+ 588.799804688 178.799804688 l 1
+1024 409.200195312 m 0
+ 911.122070312 409.533203125 819.533203125 501.122070312 819.200195312 614 c 0
+ 819.200195312 614.078125 819.200195312 614.155273438 819.200195312 614.233398438 c 0
+ 819.200195312 727.1328125 910.860351562 818.793945312 1023.75976562 818.793945312 c 0
+ 1080.33007812 818.793945312 1131.59375 795.806640625 1168.63964844 758.639648438 c 0
+ 1205.80957031 721.689453125 1228.83300781 670.524414062 1228.83300781 614.025390625 c 0
+ 1228.83300781 557.525390625 1205.80957031 506.310546875 1168.63964844 469.360351562 c 0
+ 1132.76855469 432.234375 1082.47167969 409.134765625 1026.82128906 409.134765625 c 0
+ 1025.86523438 409.134765625 1024.95214844 409.186523438 1024 409.200195312 c 0
+1459.20019531 178.799804688 m 1
+ 1349.12011719 288.879882812 l 2
+ 1390.40136719 330.403320312 1423.8125 379.172851562 1447.6796875 433.51953125 c 0
+ 1471.43457031 489.114257812 1484.5859375 550.303710938 1484.5859375 614.541015625 c 0
+ 1484.5859375 677.622070312 1471.91113281 738.409179688 1448.95996094 793.200195312 c 0
+ 1424.94238281 848.168945312 1391.07128906 897.400390625 1349.12011719 939.120117188 c 1
+ 1459.20019531 1049.20019531 l 1
+ 1514.203125 993.87109375 1558.5234375 928.635742188 1589.75976562 855.919921875 c 0
+ 1620.89550781 783.555664062 1638.14453125 703.83203125 1638.14453125 620.111328125 c 0
+ 1638.14453125 618.182617188 1638.41796875 615.924804688 1638.40039062 614 c 0
+ 1638.41113281 612.490234375 1638.41699219 610.979492188 1638.41699219 609.466796875 c 0
+ 1638.41699219 526.184570312 1620.88183594 446.544921875 1589.75976562 374.639648438 c 0
+ 1558.13964844 301.383789062 1513.87988281 235.358398438 1459.20019531 178.799804688 c 1
+1748.48046875 -110.48046875 m 1
+ 1638.40039062 -0.400390625 l 1
+ 1716.97753906 78.4013671875 1780.38671875 171.256835938 1825.28027344 274.799804688 c 0
+ 1869.50195312 376.55078125 1894.02539062 488.809570312 1894.02539062 606.750976562 c 0
+ 1894.02539062 609.001953125 1894.41796875 611.752929688 1894.40039062 614 c 0
+ 1894.421875 616.502929688 1894.43261719 619.0078125 1894.43261719 621.515625 c 0
+ 1894.43261719 738.625 1869.98730469 850.630859375 1826.55957031 951.919921875 c 0
+ 1781.51855469 1056.0859375 1717.6484375 1149.40332031 1638.40039062 1228.40039062 c 1
+ 1748.48046875 1338.48046875 l 1
+ 1840.4609375 1244.64355469 1914.71777344 1134.53320312 1967.36035156 1012.08007812 c 0
+ 2018.96386719 892.331054688 2047.55761719 760.374023438 2047.55761719 621.791015625 c 0
+ 2047.55761719 619.389648438 2048.01757812 616.397460938 2048 614 c 0
+ 2048.01757812 611.602539062 2048.02539062 609.203125 2048.02539062 606.801757812 c 0
+ 2048.02539062 468.21875 2018.96386719 335.668945312 1967.36035156 215.919921875 c 0
+ 1914.71777344 93.466796875 1840.4609375 -16.6435546875 1748.48046875 -110.48046875 c 1
+EndSplineSet
+EndChar
+
+StartChar: uni0098
+Encoding: 152 152 170
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+1485.90429688 52 m 0
+ 1175.77246094 52.052734375 924.001953125 303.856445312 924.001953125 614 c 0
+ 924.001953125 924.17578125 1175.82519531 1176 1486.00195312 1176 c 0
+ 1796.14550781 1176 2047.94726562 924.227539062 2048 614.095703125 c 0
+ 2047.8671875 303.927734375 1796.07226562 52.1328125 1485.90429688 52 c 0
+982.143554688 181.232421875 m 1
+ 949.036132812 173.280273438 914.478515625 169.068359375 878.946289062 169.068359375 c 0
+ 878.841796875 169.068359375 878.96875 169.040039062 878.864257812 169.040039062 c 0
+ 877.956054688 169.034179688 877.047851562 169.032226562 876.138671875 169.032226562 c 0
+ 629.401367188 169.032226562 429.083007812 369.350585938 429.083007812 616.087890625 c 0
+ 429.083007812 862.825195312 629.401367188 1063.14355469 876.138671875 1063.14355469 c 0
+ 877.047851562 1063.14355469 877.956054688 1063.14160156 878.864257812 1063.13574219 c 0
+ 879.1875 1063.13671875 879.510742188 1063.13671875 879.833984375 1063.13671875 c 0
+ 916.53125 1063.13671875 951.969726562 1058.609375 986.064453125 1050.14355469 c 1
+ 859.165039062 947.141601562 778.012695312 789.928710938 778.012695312 613.911132812 c 0
+ 778.012695312 439.760742188 857.4765625 284.387695312 982.064453125 181.34375 c 2
+ 982.143554688 181.232421875 l 1
+463.583984375 259.744140625 m 2
+ 432.158203125 250.796875 398.98828125 246.005859375 364.708984375 246.005859375 c 0
+ 364.625 246.005859375 364.612304688 245.952148438 364.528320312 245.952148438 c 0
+ 163.447265625 246.078125 0.234375 409.369140625 0.234375 610.479492188 c 0
+ 0.234375 811.66796875 163.573242188 975.0078125 364.76171875 975.0078125 c 0
+ 395.184570312 975.0078125 424.57421875 971.244140625 452.83203125 964.208007812 c 1
+ 351.094726562 882.267578125 285.954101562 756.6875 285.954101562 616.012695312 c 0
+ 285.954101562 470.395507812 355.694335938 341.321289062 463.631835938 259.711914062 c 1
+ 463.583984375 259.744140625 l 2
+EndSplineSet
+EndChar
+
+StartChar: uni0099
+Encoding: 153 153 171
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+1024 218.3515625 m 0
+ 1023.22070312 218.34765625 1022.48535156 218.301757812 1021.70507812 218.301757812 c 0
+ 804.612304688 218.301757812 628.361328125 394.552734375 628.361328125 611.645507812 c 0
+ 628.361328125 612.430664062 628.36328125 613.215820312 628.368164062 614 c 0
+ 628.36328125 614.779296875 628.317382812 615.514648438 628.317382812 616.294921875 c 0
+ 628.317382812 833.387695312 804.569335938 1009.63867188 1021.66113281 1009.63867188 c 0
+ 1022.44140625 1009.63867188 1023.22070312 1009.63671875 1024 1009.63183594 c 0
+ 1024.77929688 1009.63671875 1025.51464844 1009.68261719 1026.29492188 1009.68261719 c 0
+ 1243.38769531 1009.68261719 1419.63867188 833.430664062 1419.63867188 616.338867188 c 0
+ 1419.63867188 615.55859375 1419.63671875 614.779296875 1419.63183594 614 c 0
+ 1419.63671875 613.215820312 1419.68261719 612.474609375 1419.68261719 611.689453125 c 0
+ 1419.68261719 394.596679688 1243.43066406 218.344726562 1026.33886719 218.344726562 c 0
+ 1025.55859375 218.344726562 1024.77929688 218.34765625 1024 218.3515625 c 0
+1024 353.328125 m 2
+ 1024 353.34375 l 1
+ 1024.8125 353.336914062 1025.58105469 353.288085938 1026.39550781 353.288085938 c 0
+ 1168.93945312 353.288085938 1284.66699219 469.015625 1284.66699219 611.560546875 c 0
+ 1284.66699219 612.374023438 1284.66308594 613.1875 1284.65625 614 c 0
+ 1284.66308594 614.8125 1284.71191406 615.581054688 1284.71191406 616.395507812 c 0
+ 1284.71191406 758.939453125 1168.984375 874.666992188 1026.43945312 874.666992188 c 0
+ 1025.62597656 874.666992188 1024.8125 874.663085938 1024 874.65625 c 0
+ 1023.1875 874.663085938 1022.41894531 874.711914062 1021.60449219 874.711914062 c 0
+ 879.060546875 874.711914062 763.333007812 758.984375 763.333007812 616.439453125 c 0
+ 763.333007812 615.625976562 763.336914062 614.8125 763.34375 614 c 0
+ 763.3359375 613.182617188 763.288085938 612.408203125 763.288085938 611.588867188 c 0
+ 763.288085938 469.044921875 879.015625 353.31640625 1021.55957031 353.31640625 c 0
+ 1022.37402344 353.31640625 1023.1875 353.3203125 1024 353.328125 c 2
+1024 -84.1923828125 m 0
+ 1020.72949219 -84.22265625 1017.45507812 -84.2373046875 1014.17773438 -84.2373046875 c 0
+ 789.239257812 -84.2373046875 580.047851562 -12.31640625 409.599609375 108.975585938 c 0
+ 229.037109375 235.020507812 87.0791015625 408.90625 0 614 c 1
+ 87.0810546875 819.087890625 229.0390625 992.967773438 409.599609375 1119.0078125 c 0
+ 580.047851562 1240.30078125 788.451171875 1311.65722656 1013.38964844 1311.65722656 c 0
+ 1016.66699219 1311.65722656 1020.72949219 1312.20605469 1024 1312.17578125 c 0
+ 1027.27050781 1312.20605469 1030.54492188 1312.22167969 1033.82226562 1312.22167969 c 0
+ 1258.76074219 1312.22167969 1467.95214844 1240.30078125 1638.40039062 1119.0078125 c 0
+ 1818.9609375 992.967773438 1960.91894531 819.087890625 2048 614 c 1
+ 1960.92089844 408.90625 1818.96289062 235.020507812 1638.40039062 108.975585938 c 0
+ 1467.95214844 -12.31640625 1259.54882812 -83.6728515625 1034.61035156 -83.6728515625 c 0
+ 1031.33300781 -83.6728515625 1027.27050781 -84.22265625 1024 -84.1923828125 c 0
+1024 614 m 1024
+1024 55.4560546875 m 0
+ 1025.98925781 55.443359375 1027.97949219 55.4375 1029.97167969 55.4375 c 0
+ 1218.46484375 55.4375 1394.86914062 111.853515625 1541.82421875 207.887695312 c 0
+ 1697.05273438 307.030273438 1821.24121094 446.77734375 1901.39160156 614 c 1
+ 1821.23925781 781.216796875 1697.05078125 920.958007812 1541.82421875 1020.09570312 c 0
+ 1394.86425781 1116.140625 1219.30273438 1171.9921875 1030.79882812 1171.9921875 c 0
+ 1028.81542969 1171.9921875 1025.98046875 1172.55664062 1024 1172.54394531 c 0
+ 1022.01074219 1172.55664062 1020.02050781 1172.5625 1018.02832031 1172.5625 c 0
+ 829.53515625 1172.5625 653.130859375 1116.14648438 506.17578125 1020.11230469 c 0
+ 350.60546875 920.59375 225.802734375 781.015625 144.288085938 614 c 1
+ 225.803710938 446.985351562 350.60546875 307.40625 506.17578125 207.887695312 c 0
+ 653.130859375 111.853515625 828.68359375 56.0078125 1017.17675781 56.0078125 c 0
+ 1019.16894531 56.0078125 1022.01074219 55.443359375 1024 55.4560546875 c 0
+EndSplineSet
+EndChar
+
+StartChar: uni009A
+Encoding: 154 154 172
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+460.799804688 -182.448242188 m 1
+ 0 278.3515625 l 1
+ 460.799804688 739.15234375 l 1
+ 580.272460938 619.6796875 l 1
+ 324.272460938 363.6796875 l 1
+ 1137.77636719 363.6796875 l 1
+ 1137.77636719 193.024414062 l 1
+ 324.272460938 193.024414062 l 1
+ 580.272460938 -62.9755859375 l 1
+ 460.799804688 -182.448242188 l 1
+1393.77636719 193.024414062 m 0
+ 1347.21289062 194.16015625 1309.58398438 231.788085938 1308.44824219 278.3515625 c 0
+ 1308.42675781 279.109375 1308.41601562 279.870117188 1308.41601562 280.631835938 c 0
+ 1308.41601562 303.881835938 1318.26660156 324.801757812 1334.04785156 339.50390625 c 0
+ 1349.2578125 354.458984375 1370.11230469 363.690429688 1393.10644531 363.690429688 c 0
+ 1393.32910156 363.690429688 1393.55371094 363.681640625 1393.77636719 363.6796875 c 0
+ 1394.67578125 363.708984375 1395.57910156 363.723632812 1396.48632812 363.723632812 c 0
+ 1442.13183594 363.723632812 1479.19042969 326.665039062 1479.19042969 281.01953125 c 0
+ 1479.19042969 280.112304688 1479.1328125 279.251953125 1479.10351562 278.3515625 c 0
+ 1479.10546875 278.129882812 1479.10644531 277.908203125 1479.10644531 277.685546875 c 0
+ 1479.10644531 254.69140625 1469.88378906 233.833007812 1454.92773438 218.624023438 c 0
+ 1440.22558594 202.841796875 1419.26464844 192.966796875 1396.015625 192.966796875 c 0
+ 1395.25292969 192.966796875 1394.53320312 193.002929688 1393.77636719 193.024414062 c 0
+1735.10351562 193.024414062 m 0
+ 1688.54003906 194.16015625 1650.91210938 231.788085938 1649.77636719 278.3515625 c 0
+ 1649.75488281 279.109375 1649.74414062 279.870117188 1649.74414062 280.631835938 c 0
+ 1649.74414062 303.881835938 1659.59375 324.801757812 1675.37597656 339.50390625 c 0
+ 1690.58496094 354.458984375 1711.44042969 363.690429688 1734.43457031 363.690429688 c 0
+ 1734.65722656 363.690429688 1734.88183594 363.681640625 1735.10351562 363.6796875 c 0
+ 1736.00390625 363.708984375 1736.90722656 363.723632812 1737.81445312 363.723632812 c 0
+ 1783.45996094 363.723632812 1820.51855469 326.665039062 1820.51855469 281.01953125 c 0
+ 1820.51855469 280.112304688 1820.4609375 279.251953125 1820.43164062 278.3515625 c 0
+ 1820.43359375 278.129882812 1820.43457031 277.908203125 1820.43457031 277.685546875 c 0
+ 1820.43457031 254.69140625 1811.2109375 233.833007812 1796.25585938 218.624023438 c 0
+ 1781.55371094 202.841796875 1760.59277344 192.966796875 1737.34375 192.966796875 c 0
+ 1736.58105469 192.966796875 1735.86132812 193.002929688 1735.10351562 193.024414062 c 0
+1587.20019531 716.400390625 m 1
+ 1467.72753906 835.872070312 l 1
+ 1723.72753906 1091.87207031 l 1
+ 910.223632812 1091.87207031 l 1
+ 910.223632812 1262.52832031 l 1
+ 1723.72753906 1262.52832031 l 1
+ 1467.72753906 1518.52832031 l 1
+ 1587.20019531 1638 l 1
+ 2048 1177.20019531 l 1
+ 1587.20019531 716.400390625 l 1
+312.896484375 1091.87207031 m 0
+ 266.325195312 1093 228.688476562 1130.63085938 227.551757812 1177.20019531 c 0
+ 227.53125 1177.95800781 227.520507812 1178.71777344 227.520507812 1179.48046875 c 0
+ 227.520507812 1202.72949219 237.370117188 1223.64941406 253.15234375 1238.3515625 c 0
+ 268.361328125 1253.30761719 289.216796875 1262.53808594 312.2109375 1262.53808594 c 0
+ 312.432617188 1262.53808594 312.658203125 1262.52929688 312.879882812 1262.52832031 c 0
+ 313.78515625 1262.55761719 314.694335938 1262.57226562 315.606445312 1262.57226562 c 0
+ 361.251953125 1262.57226562 398.310546875 1225.51367188 398.310546875 1179.86816406 c 0
+ 398.310546875 1178.9609375 398.252929688 1178.09960938 398.223632812 1177.20019531 c 0
+ 398.225585938 1176.97851562 398.2265625 1176.75585938 398.2265625 1176.53417969 c 0
+ 398.2265625 1153.54003906 389.002929688 1132.68164062 374.047851562 1117.47167969 c 0
+ 359.345703125 1101.69042969 338.384765625 1091.81445312 315.135742188 1091.81445312 c 0
+ 314.373046875 1091.81445312 313.653320312 1091.85058594 312.896484375 1091.87207031 c 0
+654.223632812 1091.87207031 m 0
+ 607.66015625 1093.0078125 570.032226562 1130.63671875 568.896484375 1177.20019531 c 0
+ 568.875 1177.95800781 568.864257812 1178.71777344 568.864257812 1179.48046875 c 0
+ 568.864257812 1202.72949219 578.713867188 1223.64941406 594.49609375 1238.3515625 c 0
+ 609.705078125 1253.30761719 630.560546875 1262.53808594 653.5546875 1262.53808594 c 0
+ 653.77734375 1262.53808594 654.001953125 1262.52929688 654.223632812 1262.52832031 c 0
+ 655.124023438 1262.55664062 656.02734375 1262.57128906 656.934570312 1262.57128906 c 0
+ 702.580078125 1262.57128906 739.638671875 1225.51367188 739.638671875 1179.8671875 c 0
+ 739.638671875 1178.9609375 739.581054688 1178.09960938 739.551757812 1177.20019531 c 0
+ 739.553710938 1176.97851562 739.5546875 1176.75585938 739.5546875 1176.53417969 c 0
+ 739.5546875 1153.54003906 730.331054688 1132.68164062 715.375976562 1117.47167969 c 0
+ 700.673828125 1101.69042969 679.712890625 1091.81445312 656.462890625 1091.81445312 c 0
+ 655.701171875 1091.81445312 654.981445312 1091.85058594 654.223632812 1091.87207031 c 0
+EndSplineSet
+EndChar
+
+StartChar: uni009B
+Encoding: 155 155 173
+Width: 2048
+Flags: H
+LayerCount: 2
+Fore
+SplineSet
+921.120117188 -384.288085938 m 1
+ 213.791992188 30.4638671875 l 2
+ 184.393554688 46.96484375 160.509765625 71.388671875 144.671875 101.200195312 c 0
+ 129.370117188 129.571289062 120.682617188 162.02734375 120.682617188 196.49609375 c 0
+ 120.682617188 196.81640625 120.55859375 197.328125 120.559570312 197.6484375 c 2
+ 120.559570312 1023.91992188 l 2
+ 120.55859375 1024.21289062 120.55859375 1024.50683594 120.55859375 1024.79980469 c 0
+ 120.55859375 1059.26855469 129.370117188 1091.91699219 144.671875 1120.28808594 c 0
+ 160.509765625 1150.09960938 184.393554688 1174.5234375 213.791992188 1191.02441406 c 2
+ 924.3359375 1612.27246094 l 2
+ 953.825195312 1628.57714844 987.728515625 1637.86132812 1023.78222656 1637.86132812 c 0
+ 1059.8359375 1637.86132812 1094.17480469 1628.57714844 1123.6640625 1612.27246094 c 2
+ 1834.19238281 1191.10351562 l 2
+ 1863.59082031 1174.60351562 1887.47363281 1150.1796875 1903.31152344 1120.36816406 c 0
+ 1918.61425781 1091.99707031 1927.30175781 1059.54101562 1927.30175781 1025.07226562 c 0
+ 1927.30175781 1024.75195312 1927.42578125 1024.24023438 1927.42382812 1023.91992188 c 2
+ 1927.42382812 197.6484375 l 2
+ 1927.42480469 197.432617188 1927.42480469 197.216796875 1927.42480469 197.000976562 c 0
+ 1927.42480469 162.18359375 1917.98046875 129.333984375 1901.69628906 101.200195312 c 0
+ 1884.93652344 71.5400390625 1860.62011719 47.2236328125 1830.95996094 30.4638671875 c 2
+ 1114 -384.288085938 l 2
+ 1085.63867188 -400.572265625 1052.77441406 -409.88671875 1017.75097656 -409.88671875 c 0
+ 982.728515625 -409.88671875 949.450195312 -400.572265625 921.087890625 -384.288085938 c 1
+ 921.120117188 -384.288085938 l 1
+927.51953125 -162.448242188 m 1
+ 927.51953125 557.727539062 l 1
+ 316.65625 908.127929688 l 1
+ 316.65625 204.032226562 l 1
+ 927.51953125 -162.448242188 l 1
+1120.43164062 -162.448242188 m 1
+ 1734.51171875 204.064453125 l 1
+ 1734.51171875 908.17578125 l 1
+ 1120.43164062 557.776367188 l 1
+ 1120.43164062 -162.448242188 l 1
+1024 731.34375 m 1
+ 1634.86425781 1084.94433594 l 1
+ 1024 1435.48828125 l 1
+ 413.135742188 1085.08789062 l 1
+ 1024 731.34375 l 1
+1024 650.959960938 m 1024
+1024 731.34375 m 1024
+1120.44824219 557.727539062 m 1024
+927.536132812 557.727539062 m 1024
 EndSplineSet
 EndChar
 EndChars
diff --git a/aitviewer/resources/fonts/Custom.ttf b/aitviewer/resources/fonts/Custom.ttf
index 51550ca..0bd4ee5 100644
Binary files a/aitviewer/resources/fonts/Custom.ttf and b/aitviewer/resources/fonts/Custom.ttf differ
diff --git a/aitviewer/scene/camera.py b/aitviewer/scene/camera.py
index 6819f4b..15c2a21 100644
--- a/aitviewer/scene/camera.py
+++ b/aitviewer/scene/camera.py
@@ -101,7 +101,7 @@ def __init__(self, inactive_color=(0.5, 0.5, 0.5, 1), active_color=(0.6, 0.1, 0.
         :param viewer: The current viewer, if not None the gui for this object will show a button for viewing from this
          camera in the viewer
         """
-        super(Camera, self).__init__(icon='\u0084', **kwargs)
+        super(Camera, self).__init__(icon='\u0084', gui_material=False, **kwargs)
 
         # Camera object geometry
         vertices = np.array([
@@ -112,7 +112,12 @@ def __init__(self, inactive_color=(0.5, 0.5, 0.5, 1), active_color=(0.6, 0.1, 0.
             [ 1, -1, 1],
             [ 1,  1, 1],
 
-            # Triangle
+            # Triangle front
+            [ 0.5,  1.1, 1],
+            [-0.5,  1.1, 1],
+            [   0,    2, 1],
+
+            # Triangle back
             [ 0.5,  1.1, 1],
             [-0.5,  1.1, 1],
             [   0,    2, 1],
@@ -140,14 +145,14 @@ def __init__(self, inactive_color=(0.5, 0.5, 0.5, 1), active_color=(0.6, 0.1, 0.
             [ 1, 3, 2],
             [ 4, 2, 3],
             [ 5, 6, 7],
-            [ 5, 7, 6],
+            [ 8, 10, 9],
         ])
 
         self._active = False
         self.active_color = active_color
         self.inactive_color = inactive_color
 
-        self.mesh = Meshes(vertices, faces, cast_shadow=False, flat_shading=True, position=kwargs.get('position'),
+        self.mesh = Meshes(vertices, faces, cast_shadow=False, flat_shading=True,
                            rotation=kwargs.get('rotation'), is_selectable=False)
         self.mesh.color = self.inactive_color
         self.add(self.mesh, show_in_hierarchy=False)
@@ -171,6 +176,19 @@ def active(self, active):
         else:
             self.mesh.color = self.inactive_color
 
+    @Node.enabled.setter
+    def enabled(self, enabled):
+        # Call setter of the parent (Node) class.
+        super(Camera, self.__class__).enabled.fset(self, enabled)
+
+        # Also set the enabled property of the path if it exists.
+        # We must do this here because the path is not a child of the camera node,
+        # since it's position/rotation should not be updated together with the camera.
+        if self.path:
+            self.path[0].enabled = enabled
+            if self.path[1] is not None:
+                self.path[1].enabled = enabled
+
     def hide_frustum(self):
         if self.frustum:
             self.remove(self.frustum)
@@ -228,14 +246,12 @@ def transform(x):
             lines = np.apply_along_axis(transform, 1, lines)
             all_lines[i] = lines
 
-        self.frustum = Lines(all_lines, r_base=0.005, mode='lines', color=(0.1, 0.1, 0.1, 1), cast_shadow=False,
-                             position=self.position, rotation=self.rotation)
+        self.frustum = Lines(all_lines, r_base=0.005, mode='lines', color=(0.1, 0.1, 0.1, 1), cast_shadow=False)
         self.add(self.frustum, show_in_hierarchy=False)
 
         ori = np.eye(3, dtype=np.float)
         ori[:, 2] *= -1
-        self.origin = RigidBodies(np.array([0.0, 0.0, 0.0])[np.newaxis], ori[np.newaxis],
-                                  position=self.position, rotation=self.rotation)
+        self.origin = RigidBodies(np.array([0.0, 0.0, 0.0])[np.newaxis], ori[np.newaxis])
         self.add(self.origin, show_in_hierarchy=False)
 
         self.current_frame_id = frame_id
@@ -243,7 +259,9 @@ def transform(x):
     def hide_path(self):
         if self.path is not None:
             self.parent.remove(self.path[0])
-            self.parent.remove(self.path[1])
+            # The Lines part of the path may be None if the path is a single point.
+            if self.path[1] is not None:
+                self.parent.remove(self.path[1])
             self.path = None
 
     def show_path(self):
@@ -263,13 +281,20 @@ def show_path(self):
             all_oris[i] = self.rotation @ np.array([[1, 0, 0], [0, 1, 0], [0, 0, -1]])
 
         path_spheres = RigidBodies(all_points, all_oris, radius=0.01, length=0.1, color=(0.92, 0.68, 0.2, 1.0))
-        path_lines = Lines(all_points, color=(0, 0, 0, 1), r_base=0.003, mode='line_strip', cast_shadow=False)
+        # Create lines only if there is more than one frame in the sequence.
+        if self.n_frames > 1:
+            path_lines = Lines(all_points, color=(0, 0, 0, 1), r_base=0.003, mode='line_strip', cast_shadow=False)
+        else:
+            path_lines = None
 
-        # We add the the path to the parent node of the camera because we don't want the camera position and rotation to be applied to it.
+        # We add the the path to the parent node of the camera because we don't want the camera position and rotation
+        # to be applied to it.
         assert self.parent is not None, "Camera node must be added to the scene before showing the camera path."
-        self.parent.add(path_spheres, path_lines, show_in_hierarchy=False)
-        self.path = (path_spheres, path_lines)
+        self.parent.add(path_spheres, show_in_hierarchy=False, enabled=self.enabled)
+        if path_lines is not None:
+            self.parent.add(path_lines, show_in_hierarchy=False, enabled=self.enabled)
 
+        self.path = (path_spheres, path_lines)
         self.current_frame_id = frame_id
 
     def render_outline(self, ctx, camera, prog):
@@ -312,17 +337,22 @@ def gui_context_menu(self, imgui):
 class WeakPerspectiveCamera(Camera):
     """
     A sequence of weak perspective cameras.
-    The camera is positioned at (0,0,1) axis aligned and looks towards the negative z direction following the OpenGL conventions.
+    The camera is positioned at (0,0,1) axis aligned and looks towards the negative z direction following the OpenGL
+    conventions.
     """
     def __init__(self, scale, translation, cols, rows, near=C.znear, far=C.zfar, viewer=None, **kwargs):
         """ Initializer.
         :param scale: A np array of scale parameters [sx, sy] of shape (2) or a sequence of parameters of shape (N, 2)
-        :param translation: A np array of translation parameters [tx, ty] of shape (2) or a sequence of parameters of shape (N, 2)
-        :param cols: Number of columns in an image captured by this camera, used for computing the aspect ratio of the camera
-        :param rows: Number of rows in an image captured by this camera, used for computing the aspect ratio of the camera
-        :param near: Distance of the near plane from the camera
-        :param far: Distance of the far plane from the camera
-        :param viewer: the current viewer, if not None the gui for this object will show a button for viewing from this camera in the viewer
+        :param translation: A np array of translation parameters [tx, ty] of shape (2) or a sequence of parameters of
+          shape (N, 2).
+        :param cols: Number of columns in an image captured by this camera, used for computing the aspect ratio of
+          the camera.
+        :param rows: Number of rows in an image captured by this camera, used for computing the aspect ratio of
+          the camera.
+        :param near: Distance of the near plane from the camera.
+        :param far: Distance of the far plane from the camera.
+        :param viewer: the current viewer, if not None the gui for this object will show a button for viewing from
+          this camera in the viewer.
          """
         if len(scale.shape) == 1:
             scale = scale[np.newaxis]
@@ -334,7 +364,7 @@ def __init__(self, scale, translation, cols, rows, near=C.znear, far=C.zfar, vie
 
         super(WeakPerspectiveCamera, self).__init__(n_frames=scale.shape[0], viewer=viewer, **kwargs)
 
-        self.scale = scale
+        self.scale_factor = scale
         self.translation = translation
 
         self.cols = cols
@@ -361,7 +391,7 @@ def right(self):
         return self._right
 
     def update_matrices(self, width, height):
-        sx, sy = self.scale[self.current_frame_id]
+        sx, sy = self.scale_factor[self.current_frame_id]
         tx, ty = self.translation[self.current_frame_id]
 
         window_ar = width / height
@@ -410,14 +440,15 @@ class OpenCVCamera(Camera):
 
     def __init__(self, K, Rt, cols, rows, dist_coeffs=None, near=C.znear, far=C.zfar, viewer=None, **kwargs):
         """ Initializer.
-        :param K:  A np array of camera intrinsics in the format used by OpenCV (3, 3) or (N, 3, 3), one for each frame
-        :param Rt: A np array of camera extrinsics in the format used by OpenCV (3, 4) or (N, 3, 4), one for each frame
-        :param dist_coeffs: Lens distortion coefficients in the format used by OpenCV (5)
-        :param cols: Width  of the image in pixels, matching the size of the image expected by the intrinsics matrix
-        :param rows: Height of the image in pixels, matching the size of the image expected by the intrinsics matrix
-        :param near: Distance of the near plane from the camera
-        :param far: Distance of the far plane from the camera
-        :param viewer: The current viewer, if not None the gui for this object will show a button for viewing from this camera in the viewer
+        :param K:  A np array of camera intrinsics in the format used by OpenCV (3, 3) or (N, 3, 3), one for each frame.
+        :param Rt: A np array of camera extrinsics in the format used by OpenCV (3, 4) or (N, 3, 4), one for each frame.
+        :param dist_coeffs: Lens distortion coefficients in the format used by OpenCV (5).
+        :param cols: Width  of the image in pixels, matching the size of the image expected by the intrinsics matrix.
+        :param rows: Height of the image in pixels, matching the size of the image expected by the intrinsics matrix.
+        :param near: Distance of the near plane from the camera.
+        :param far: Distance of the far plane from the camera.
+        :param viewer: The current viewer, if not None the gui for this object will show a button for viewing from this
+          camera in the viewer.
          """
         self.K = K if len(K.shape) == 3 else K[np.newaxis]
         self.Rt = Rt if len(Rt.shape) == 3 else Rt[np.newaxis]
@@ -529,6 +560,40 @@ def update_matrices(self, width, height):
         self.view_matrix = V.astype('f4')
         self.view_projection_matrix = np.matmul(P, V).astype('f4')
 
+    def to_pinhole_camera(self, target_distance=5, **kwargs) -> 'PinholeCamera':
+        """
+        Returns a PinholeCamera object with positions and targets computed from this camera.
+        :param target_distance: distance from the camera at which the target of the PinholeCamera is placed.
+
+        Remarks:
+         The Pinhole camera does not currently support skew, offset from the center and non vertical up vectors.
+         Also the fov from the first intrinsic matrix is used for all frames because the PinholeCamera does not
+         support sequences of fov values.
+        """
+        # Save current frame id.
+        current_frame_id = self.current_frame_id
+
+        # Compute position and target for each frame.
+        # Pinhole camera currently does not support custom up direction.
+        positions = np.zeros((self.n_frames, 3))
+        targets = np.zeros((self.n_frames, 3))
+        for i in range(self.n_frames):
+            self.current_frame_id = i
+            positions[i] = self.position
+            targets[i] = self.position + self.forward * target_distance
+
+        # Restore current frame id.
+        self.current_frame_id = current_frame_id
+
+        # Compute intrinsics, the Pinhole camera does not currently support
+        # skew and offset from the center, so we throw away this information.
+        # Also we use the fov from the first intrinsic matrix if there is more than one
+        # because the PiholeCamera does not support sequences of fov values.
+        fov = np.rad2deg(2 * np.arctan(self.K[0, 1, 2] / self.K[0, 1, 1]))
+
+        return PinholeCamera(positions, targets, self.cols, self.rows, fov=fov,
+                             near=self.near, far=self.far, viewer=self.viewer, **kwargs)
+
     @hooked
     def gui(self, imgui):
         u, show = imgui.checkbox("Show frustum", self.frustum is not None)
@@ -553,15 +618,14 @@ class PinholeCamera(Camera):
     Your classic pinhole camera.
     """
     def __init__(self, position, target, cols, rows, fov=45, near=C.znear, far=C.zfar, viewer=None, **kwargs):
-        self._positions = position if len(position.shape) == 2 else position[np.newaxis]
-        self._targets = target if len(target.shape) == 2 else target[np.newaxis]
-        assert self._positions.shape[0] == 1 or self._targets.shape[0] == 1 or self._positions.shape[0] == self._targets.shape[0], (
-            f"position and target array shape mismatch: {self._positions.shape} and {self._targets.shape}")
+        positions = position if len(position.shape) == 2 else position[np.newaxis]
+        targets = target if len(target.shape) == 2 else target[np.newaxis]
+        assert positions.shape[0] == 1 or targets.shape[0] == 1 or positions.shape[0] == targets.shape[0], (
+            f"position and target array shape mismatch: {positions.shape} and {targets.shape}")
 
-        super(PinholeCamera, self).__init__(n_frames=self._positions.shape[0], viewer=viewer, **kwargs)
         self._world_up = np.array([0.0, 1.0, 0.0])
-        self.position = self.current_position
-        self.rotation = self.current_rotation
+        self._targets = targets
+        super(PinholeCamera, self).__init__(position=position, n_frames=targets.shape[0], viewer=viewer, **kwargs)
 
         self.cols = cols
         self.rows = rows
@@ -570,14 +634,9 @@ def __init__(self, position, target, cols, rows, fov=45, near=C.znear, far=C.zfa
         self.far = far
         self.fov = fov
 
-    @hooked
-    def on_frame_update(self):
-        self.position = self.current_position
-        self.rotation = self.current_rotation
-
     @property
     def forward(self):
-        forward = self.current_target - self.current_position
+        forward = self.current_target - self.position
         forward = forward / np.linalg.norm(forward)
         return forward / np.linalg.norm(forward)
 
@@ -596,11 +655,7 @@ def current_target(self):
         return self._targets[0] if self._targets.shape[0] == 1 else self._targets[self.current_frame_id]
 
     @property
-    def current_position(self):
-        return self._positions[0] if self._positions.shape[0] == 1 else self._positions[self.current_frame_id]
-
-    @property
-    def current_rotation(self):
+    def rotation(self):
         return np.array([-self.right, self.up, -self.forward]).T
 
     def update_matrices(self, width, height):
@@ -608,13 +663,41 @@ def update_matrices(self, width, height):
         P = perspective_projection(np.deg2rad(self.fov), width / height, self.near, self.far)
 
         # Compute view matrix.
-        V = look_at(self.current_position, self.current_target, self._world_up)
+        V = look_at(self.position, self.current_target, self._world_up)
 
         # Update camera matrices.
         self.projection_matrix = P.astype('f4')
         self.view_matrix = V.astype('f4')
         self.view_projection_matrix = np.matmul(P, V).astype('f4')
 
+    def to_opencv_camera(self, **kwargs) -> OpenCVCamera:
+        """
+        Returns a OpenCVCamera object with extrinsics and intrinsics computed from this camera.
+        """
+        # Save current frame id.
+        current_frame_id = self.current_frame_id
+
+        cols, rows = self.cols, self.rows
+        # Compute extrinsics for each frame.
+        Rts = np.zeros((self.n_frames, 3, 4))
+        for i in range(self.n_frames):
+            self.current_frame_id = i
+            self.update_matrices(cols, rows)
+            Rts[i] = self.get_view_matrix()[:3]
+
+        # Restore current frame id.
+        self.current_frame_id = current_frame_id
+
+        # Invert Y and Z to meet OpenCV conventions.
+        Rts[:, 1:3, :] *= -1.0
+
+        # Compute intrinsics.
+        f = 1. / np.tan(np.radians(self.fov / 2))
+        c0 = np.array([cols / 2.0, rows / 2.0])
+        K = np.array([[f * 0.5 * rows, 0., c0[0]], [0., f * 0.5 * rows, c0[1]], [0., 0., 1.]])
+
+        return OpenCVCamera(K, Rts, cols, rows, near=self.near, far=self.far, viewer=self.viewer, **kwargs)
+
     @hooked
     def gui(self, imgui):
         u, show = imgui.checkbox("Show frustum", self.frustum is not None)
@@ -662,13 +745,21 @@ def __init__(self, fov=45, orthographic=None, znear=C.znear, zfar=C.zfar):
         self.name = 'Camera'
         self.icon = '\u0084'
 
+        self.animating = False
+        self._animation_t = 0.0
+        self._animation_time = 0.0
+        self._animation_start_position = None
+        self._animation_end_position = None
+        self._animation_start_target = None
+        self._animation_end_target = None
+
     @property
     def position(self):
         return self._position
 
     @position.setter
     def position(self, position):
-        self._position = position
+        self._position = np.array(position, dtype=np.float32).copy()
 
     @property
     def forward(self):
@@ -840,6 +931,31 @@ def get_ray(self, x, y, width, height):
 
         return ray_origin, ray_dir
 
+    def move_with_animation(self, end_position, end_target, time=0.25):
+        self._animation_start_position = self.position.copy()
+        self._animation_end_position = np.array(end_position)
+        self._animation_start_target = self.target.copy()
+        self._animation_end_target = np.array(end_target)
+        self._animation_total_time = time
+        self._animation_t = 0.0
+        self.animating = True
+
+    def update_animation(self, dt):
+        if not self.animating:
+            return
+
+        self._animation_t += dt
+        if self._animation_t >= self._animation_total_time:
+            self.position = self._animation_end_position
+            self.target = self._animation_end_target
+            self.animating = False
+        else:
+            t = self._animation_t / self._animation_total_time
+            # Smootherstep interpolation (this polynomial has 0 first and second derivative at 0 and 1)
+            t = t * t * t * (t * (t * 6 - 15) + 10)
+            self.position = self._animation_start_position * (1 - t) + self._animation_end_position * t
+            self.target = self._animation_start_target * (1 - t) + self._animation_end_target * t
+
     def gui(self, imgui):
         _, self.is_ortho = imgui.checkbox('Orthographic Camera', self.is_ortho)
         _, self.fov = imgui.slider_float('Camera FOV##fov', self.fov, 0.1, 180.0, '%.1f')
diff --git a/aitviewer/scene/camera_utils.py b/aitviewer/scene/camera_utils.py
index 4da4abd..950dbcc 100644
--- a/aitviewer/scene/camera_utils.py
+++ b/aitviewer/scene/camera_utils.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
diff --git a/aitviewer/scene/light.py b/aitviewer/scene/light.py
index 3b9a635..5ba02a7 100644
--- a/aitviewer/scene/light.py
+++ b/aitviewer/scene/light.py
@@ -14,13 +14,13 @@
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <https://www.gnu.org/licenses/>.
 """
-from functools import lru_cache
 import numpy as np
-from aitviewer.renderables.lines import Lines
 
+from aitviewer.renderables.lines import Lines
 from aitviewer.scene.node import Node
 from aitviewer.scene.camera_utils import look_at
 from aitviewer.scene.camera_utils import orthographic_projection
+from functools import lru_cache
 
 
 class Light(Node):
@@ -94,7 +94,7 @@ def _update_debug_lines(self):
         lines = np.apply_along_axis(lambda x: (world_from_ndc @ np.append(x, 1.0))[:3] - self.position, 1, lines)
 
         if self._debug_lines is None:
-            self._debug_lines = Lines(lines, position=self.position, r_base=0.2, mode='lines', cast_shadow=False)
+            self._debug_lines = Lines(lines, r_base=0.2, mode='lines', cast_shadow=False)
             self.add(self._debug_lines)
         else:
             self._debug_lines.lines = lines
@@ -102,9 +102,7 @@ def _update_debug_lines(self):
 
     @Node.position.setter
     def position(self, position):
-        self._position = position
-        for n in self.nodes:
-            n.position = position
+        super(Light, self.__class__).position.fset(self, position)
         self._update_debug_lines()
 
     def redraw(self, **kwargs):
@@ -112,10 +110,7 @@ def redraw(self, **kwargs):
             self._debug_lines.redraw(**kwargs)
 
     def gui(self, imgui):
-        self.gui_position(imgui)
-        self.gui_material(imgui, show_advanced=False)
-
-        # Light controls
+        # Custom Light controls
         _, self.intensity_ambient = imgui.drag_float('Ambient##ambient', self.intensity_ambient, 0.01, min_value=0.0, max_value=1.0,
                                            format='%.2f')
         _, self.intensity_diffuse = imgui.drag_float('Diffuse##diffuse', self.intensity_diffuse, 0.01, min_value=0.0, max_value=1.0,
diff --git a/aitviewer/scene/material.py b/aitviewer/scene/material.py
index eca7855..e50ca43 100644
--- a/aitviewer/scene/material.py
+++ b/aitviewer/scene/material.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
diff --git a/aitviewer/scene/node.py b/aitviewer/scene/node.py
index d64401e..b2e44b8 100644
--- a/aitviewer/scene/node.py
+++ b/aitviewer/scene/node.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -34,32 +34,73 @@ def __init__(self,
                  scale=1.0,
                  color=(0.5, 0.5, 0.5, 1.0),
                  material=None,
-                 render_priority=1,
                  is_selectable=True,
+                 gui_affine=True,
+                 gui_material=True,
+                 enabled_frames=None,
                  n_frames=1):
         """
         :param name: Name of the node
         :param icon: Custom Node Icon using custom Icon font
-        :param position: Starting position in the format (X,Y,Z)
-        :param rotation: Starting rotation in rotation matrix representation (3,3)
-        :param scale: Starting scale (scalar)
+        :param position: Starting position in the format (X,Y,Z) or np array of positions with shape (F, 3)
+        :param rotation: Starting rotation in rotation matrix representation (3,3) or np array of rotations with shape (F, 3, 3)
+        :param scale: Starting scale (scalar) or np array of scale values with shape (F)
         :param color: (R,G,B,A) 0-1 formatted color value.
         :param material: Object material properties. The color specified in the material will override node color
+        :param is_selectable: If True the node is selectable when clicked on, otherwise the parent node will be selected.
+        :param gui_affine: If True the node will have transform controls (position, rotation, scale) in the GUI.
+        :param gui_material: If True the node will have material controls in the GUI.
+        :param enabled_frames: Numpy array of boolean values, the object will be enabled only in frames where the value is True,
+         the number of ones in the mask must match the number of frames of the object.
         :param n_frames: How many frames this renderable has.
         """
-
         # Transform & Animation
-        self._position = np.array([0.0, 0.0, 0.0]) if position is None else position
-        self._rotation = np.eye(3) if rotation is None else rotation
-        self._scale = scale
+        position = np.zeros(3, dtype=np.float32) if position is None else np.array(position, dtype=np.float32)
+        rotation = np.eye(3, dtype=np.float32) if rotation is None else np.array(rotation, dtype=np.float32)
+
+        self._positions = position if len(position.shape) != 1 else position[np.newaxis]
+        self._rotations = rotation if len(rotation.shape) != 2 else rotation[np.newaxis]
+        self._scales = (scale if isinstance(scale, np.ndarray) else np.array([scale])).astype(np.float32)
+
+        n_positions = self._positions.shape[0]
+        n_rotations = self._rotations.shape[0]
+        n_scales = self._scales.shape[0]
+
+        if n_frames > 1:
+            assert n_positions == 1 or n_frames == n_positions, (f"Number of position frames"
+                f" ({n_positions}) must be 1 or match number of Node frames {n_frames}")
+            assert n_rotations == 1 or n_frames == n_rotations, (f"Number of rotations frames"
+                f" ({n_rotations}) must be 1 or match number of Node frames {n_frames}")
+            assert n_scales == 1 or n_frames == n_scales, (f"Number of scales frames"
+                f" ({n_scales}) must be 1 or match number of Node frames {n_frames}")
+        else:
+            n_frames = max(n_positions, n_rotations, n_scales)
+            assert ((n_positions == 1 or n_positions == n_frames) and
+                    (n_rotations == 1 or n_rotations == n_frames) and
+                    (n_scales == 1 or n_scales == n_frames)), (f"Number of position"
+                        f"({n_positions}), rotation ({n_rotations}) and scale ({n_scales})"
+                        "frames must be 1 or match.")
+
+        # Frames
         self._n_frames = n_frames
         self._current_frame_id = 0
+        self.model_matrix = self.get_local_transform()
+        self._enabled_frames = enabled_frames
+        if self._enabled_frames is not None:
+            assert np.count_nonzero(self._enabled_frames) == n_frames, (f"Number of non-zero elements in enabled_frames"
+                f" ({np.count_nonzero(self._enabled_frames)}) must match number of frames in sequence ({n_frames})")
+            # Create an array that maps from the true frame id (counting also disabled frames) to the index of the
+            # first existing frame in the sequence.
+            self._enabled_frame_id = np.cumsum(self._enabled_frames) - 1
+
+            # Stores the true frame id (counting also disabled frames) we use this to allow going
+            # through both enabled and disabled frames from the GUI.
+            self._internal_frame_id = 0
 
         # Material
         self.material = Material(color=color) if material is None else material
 
         # Renderable Attributes
-        self.render_priority = render_priority
         self.is_renderable = False
         self.backface_culling = True
         self.backface_fragmap = False
@@ -78,46 +119,95 @@ def __init__(self,
         self.icon = icon if icon is not None else '\u0082'
         self._enabled = True
         self._expanded = False
-        self._has_gui = True
-        self._gui_elements = []
+        self.gui_controls = {
+            'affine': {'fn': self.gui_affine, 'icon': '\u009b', 'is_visible': gui_affine},
+            'material': {'fn': self.gui_material, 'icon': '\u0088', 'is_visible': gui_material},
+            'animation': {'fn': self.gui_animation, 'icon': '\u0098', 'is_visible': (lambda: self._n_frames > 1)()},
+            'io': {'fn': self.gui_io, 'icon': '\u009a', 'is_visible': (lambda: self.gui_io.__func__ is not Node.gui_io)()}
+        }
+        self.gui_modes = {
+            'view': {'title': ' View', 'fn': self.gui_mode_view, 'icon': '\u0099'}
+        }
+        self._selected_mode = 'view'
         self._show_in_hierarchy = True
         self.is_selectable = is_selectable
 
         self.nodes = []
         self.parent = None
 
+    # Selected Mode
+    @property
+    def selected_mode(self):
+        return self._selected_mode
+
+    @selected_mode.setter
+    def selected_mode(self, selected_mode):
+        self._selected_mode = selected_mode
+
     # Transform
     @property
     def position(self):
-        return self._position
+        idx = self.current_frame_id if self._positions.shape[0] > 1 else 0
+        return self._positions[idx]
 
     @position.setter
     def position(self, position):
-        self._position = position
-        for n in self.nodes:
-            n.position = position
+        idx = self.current_frame_id if self._positions.shape[0] > 1 else 0
+        self._positions[idx] = np.array(position, dtype=np.float32).copy()
+        self.update_transform(None if self.parent is None else self.parent.model_matrix)
+
+    @property
+    def positions(self):
+        return self._positions
+
+    @positions.setter
+    def positions(self, positions):
+        self._positions = positions
+        self.update_transform(None if self.parent is None else self.parent.model_matrix)
 
     @property
     def rotation(self):
-        return self._rotation
+        idx = self.current_frame_id if self._rotations.shape[0] > 1 else 0
+        return self._rotations[idx]
 
     @rotation.setter
     def rotation(self, rotation):
-        self._rotation = rotation
-        for n in self.nodes:
-            n.rotation = rotation
+        idx = self.current_frame_id if self._rotations.shape[0] > 1 else 0
+        self._rotations[idx] = rotation
+        self.update_transform(None if self.parent is None else self.parent.model_matrix)
+
+    @property
+    def rotations(self):
+        return self._rotations
+
+    @rotations.setter
+    def rotations(self, rotations):
+        self._rotations = rotations
+        self.update_transform(None if self.parent is None else self.parent.model_matrix)
 
     @property
     def scale(self):
-        return self._scale
+        idx = self.current_frame_id if self._scales.shape[0] > 1 else 0
+        return self._scales[idx]
 
     @scale.setter
     def scale(self, scale):
-        self._scale = scale
+        idx = self.current_frame_id if self._scales.shape[0] > 1 else 0
+        self._scales[idx] = scale
+        self.update_transform(None if self.parent is None else self.parent.model_matrix)
+
+    @property
+    def scales(self):
+        return self._scales
+
+    @scales.setter
+    def scales(self, scales):
+        self._scales = scales
+        self.update_transform(None if self.parent is None else self.parent.model_matrix)
 
     @staticmethod
     @lru_cache()
-    def _compute_model_matrix(pos, rot, scale):
+    def _compute_transform(pos, rot, scale):
         rotation = np.eye(4)
         rotation[:3, :3] = np.array(rot)
 
@@ -128,13 +218,23 @@ def _compute_model_matrix(pos, rot, scale):
 
         return (trans @ rotation @ scale).astype('f4')
 
-    def model_matrix(self):
-        """Construct model matrix from this node's orientation and position."""
-        return self._compute_model_matrix(
+    def get_local_transform(self):
+        """Construct local transform as a 4x4 matrix from this node's position, orientation and scale."""
+        return self._compute_transform(
                tuple(self.position),
                tuple(map(tuple, self.rotation)),
                self.scale)
 
+    def update_transform(self, parent_transform=None):
+        """Update the model matrix of this node and all of its descendants."""
+        if parent_transform is None:
+            self.model_matrix = self.get_local_transform()
+        else:
+            self.model_matrix = parent_transform.astype('f4') @ self.get_local_transform()
+
+        for n in self.nodes:
+            n.update_transform(self.model_matrix)
+
     @property
     def color(self):
         return self.material.color
@@ -146,7 +246,19 @@ def color(self, color):
     @property
     def bounds(self):
         """ The bounds in the format ((x_min, x_max), (y_min, y_max), (z_min, z_max)) """
-        return None
+        return np.array([[0, 0], [0, 0], [0, 0]])
+
+    @property
+    def current_bounds(self):
+        return np.array([[0, 0], [0, 0], [0, 0]])
+
+    @property
+    def current_center(self):
+        return self.current_bounds.mean(-1)
+
+    @property
+    def center(self):
+        return self.bounds.mean(-1)
 
     def get_bounds(self, points):
         if len(points.shape) == 2 and points.shape[-1] == 3:
@@ -160,7 +272,7 @@ def get_bounds(self, points):
             [np.nanmin(points[:, :, 2]), np.nanmax(points[:, :, 2])]])
 
         # Transform bounding box with the model matrix.
-        val = (self.model_matrix() @ np.vstack((val, np.array([1.0, 1.0]))))[:3]
+        val = (self.model_matrix @ np.vstack((val, np.array([1.0, 1.0]))))[:3]
 
         # If any of the elements is NaN return an empty bounding box.
         if np.isnan(val).any():
@@ -185,16 +297,38 @@ def current_frame_id(self):
 
     @current_frame_id.setter
     def current_frame_id(self, frame_id):
-        if self.n_frames == 1 or frame_id == self._current_frame_id:
+        # Check if the frame changed.
+        last_frame_id = self._current_frame_id if self._enabled_frames is None else self._internal_frame_id
+        if self.n_frames == 1 or frame_id == last_frame_id:
             return
+
         self.on_before_frame_update()
-        if frame_id < 0:
-            self._current_frame_id = 0
-        elif frame_id >= len(self):
-            self._current_frame_id = len(self) - 1
+        if self._enabled_frames is None:
+            if frame_id < 0:
+                self._current_frame_id = 0
+            elif frame_id >= len(self):
+                self._current_frame_id = len(self) - 1
+            else:
+                self._current_frame_id = frame_id
         else:
-            self._current_frame_id = frame_id
+            # If an enabled_frames is present use it to get the current frame.
+            if frame_id < 0:
+                self._internal_frame_id = 0
+            elif frame_id >= self._enabled_frames.shape[0]:
+                self._internal_frame_id = self._enabled_frames.shape[0] - 1
+            else:
+                self._internal_frame_id = frame_id
+            self._current_frame_id = self._enabled_frame_id[self._internal_frame_id]
+            # Update enabled using the mask.
+            self.enabled = self._enabled_frames[self._internal_frame_id]
+
+        # Update frame id of all children nodes.
+        for n in self.nodes:
+            n.current_frame_id = self._current_frame_id
+
         self.on_frame_update()
+        if self.parent and (self._positions.shape[0] > 1 or self._rotations.shape[0] > 1 or self._scales.shape[0] > 1):
+            self.update_transform(self.parent.model_matrix)
 
     def next_frame(self):
         self.current_frame_id = self.current_frame_id + 1 if self.current_frame_id < len(self) - 1 else 0
@@ -203,40 +337,35 @@ def previous_frame(self):
         self.current_frame_id = self.current_frame_id - 1 if self.current_frame_id > 0 else len(self) - 1
 
     def on_before_frame_update(self):
-        """Called when the current frame is about to change, 'self.current_frame_id' still has the id of the previous frame."""
+        """Called when the current frame is about to change, 'self.current_frame_id' still has the id of the
+           previous frame."""
         pass
 
     def on_frame_update(self):
         """Called when the current frame is changed."""
-        for n in self.nodes:
-            n.current_frame_id = self.current_frame_id
+        pass
 
     def add(self, *nodes, **kwargs):
         self._add_nodes(*nodes, **kwargs)
 
     def _add_node(self,
-                  n,
-                  has_gui=True,
-                  gui_elements=None,
+                  n: 'Node',
                   show_in_hierarchy=True,
                   expanded=False,
                   enabled=True):
         """
         Add a single node
-        :param has_gui: Whether the node has a GUI.
-        :param gui_elements: Which elements of the GUI are displayed by default.
         :param show_in_hierarchy: Whether to show the node in the scene hierarchy.
         :param expanded: Whether the node is initially expanded in the GUI.
         """
         if n is None:
             return
-        n._has_gui = has_gui
-        n._gui_elements = gui_elements if gui_elements is not None else ['animation', 'position', 'rotation', 'scale', 'material']
         n._show_in_hierarchy = show_in_hierarchy
         n._expanded = expanded
-        n._enabled = enabled
+        n._enabled = enabled if n._enabled_frames is None else n._enabled_frames[n.current_frame_id]
         self.nodes.append(n)
         n.parent = self
+        n.update_transform(self.model_matrix)
 
     def _add_nodes(self, *nodes, **kwargs):
         """Add multiple nodes"""
@@ -248,10 +377,6 @@ def remove(self, *nodes):
             n.release()
             self.nodes.remove(n)
 
-    @property
-    def has_gui(self):
-        return self._has_gui
-
     @property
     def show_in_hierarchy(self):
         return self._show_in_hierarchy
@@ -275,79 +400,81 @@ def expanded(self, expanded):
     def is_transparent(self):
         """
         Returns true if the object is transparent and should thus be sorted when rendering.
-        Subclasses should implement this method to be rendered correctly when transparent.
+        Subclassess that use a different color should implement this method to be rendered correctly when transparent.
+        """
+        return self.material.color[3] < 1.0
+
+    def gui(self, imgui):
+        """
+        Render GUI for custom node properties and controls. Implementation optional.
+        Elements rendered here will show up in the scene hierarchy
+        :param imgui: imgui context.
+        See https://pyimgui.readthedocs.io/en/latest/reference/imgui.core.html for available elements to render
         """
-        return False
+        pass
+
+    def gui_modes(self, imgui):
+        """ Render GUI with toolbar (tools) for this particular node"""
 
     def gui_animation(self, imgui):
-        # Animation Control
-        if self.n_frames > 1 and 'animation' in self._gui_elements:
-            u, fid = imgui.drag_int('Frame##r_{}'.format(self.unique_name),
-                                    self.current_frame_id, min_value=0, max_value=self.n_frames - 1)
+        """ Render GUI for animation related settings"""
+
+        if self._enabled_frames is None:
+            if self.n_frames > 1:
+                u, fid = imgui.slider_int('Frame##r_{}'.format(self.unique_name),
+                                        self.current_frame_id, min_value=0, max_value=self.n_frames - 1)
+                if u:
+                    self.current_frame_id = fid
+        else:
+            u, fid = imgui.slider_int('Frame##r_{}'.format(self.unique_name),
+                                    self._internal_frame_id, min_value=0, max_value=self._enabled_frames.shape[0] - 1)
             if u:
                 self.current_frame_id = fid
 
-    def gui_position(self, imgui):
+    def gui_affine(self, imgui):
+        """ Render GUI for affine transformations"""
         # Position controls
         u, pos = imgui.drag_float3('Position##pos{}'.format(self.unique_name), *self.position, 0.1, format='%.2f')
         if u:
             self.position = pos
 
-    def gui_rotation(self, imgui):
         # Rotation controls
         euler_angles = rot2euler_numpy(self.rotation[np.newaxis], degrees=True)[0]
         u, euler_angles = imgui.drag_float3('Rotation##pos{}'.format(self.unique_name), *euler_angles, 0.1, format='%.2f')
         if u:
             self.rotation = euler2rot_numpy(np.array(euler_angles)[np.newaxis], degrees=True)[0]
 
-    def gui_scale(self, imgui):
         # Scale controls
         u, scale = imgui.drag_float('Scale##scale{}'.format(self.unique_name), self.scale, 0.01, min_value=0.001,
                                     max_value=10.0, format='%.3f')
         if u:
             self.scale = scale
 
-    def gui_material(self, imgui, show_advanced=True):
+    def gui_material(self, imgui):
+        """ Render GUI with material properties """
+
         # Color Control
         uc, color = imgui.color_edit4("Color##color{}'".format(self.unique_name), *self.material.color, show_alpha=True)
         if uc:
             self.color = color
 
-        if show_advanced:
-            if imgui.tree_node("Advanced material##advanced_material{}'".format(self.unique_name)):
-                # Diffuse
-                ud, diffuse = imgui.slider_float('Diffuse##diffuse{}'.format(self.unique_name), self.material.diffuse,  0.0, 1.0, '%.2f')
-                if ud:
-                    self.material.diffuse = diffuse
-
-                # Ambient
-                ua, ambient = imgui.slider_float('Ambient##ambient{}'.format(self.unique_name), self.material.ambient,  0.0, 1.0, '%.2f')
-                if ua:
-                    self.material.ambient = ambient
+        # Diffuse
+        ud, diffuse = imgui.slider_float('Diffuse##diffuse{}'.format(self.unique_name), self.material.diffuse,  0.0, 1.0, '%.2f')
+        if ud:
+            self.material.diffuse = diffuse
 
-                imgui.tree_pop()
+        # Ambient
+        ua, ambient = imgui.slider_float('Ambient##ambient{}'.format(self.unique_name), self.material.ambient,  0.0, 1.0, '%.2f')
+        if ua:
+            self.material.ambient = ambient
 
-    def gui(self, imgui):
-        """
-        Render the GUI that gets displayed in the scene window. Implementation optional.
-        Elements rendered here will show up in the scene hierarchy
-        :param imgui: imgui context.
-        See https://pyimgui.readthedocs.io/en/latest/reference/imgui.core.html for available elements to render
-        """
-        if 'animation' in self._gui_elements:
-            self.gui_animation(imgui)
-
-        if 'position' in self._gui_elements:
-            self.gui_position(imgui)
-
-        if 'rotation' in self._gui_elements:
-            self.gui_rotation(imgui)
-
-        if 'scale' in self._gui_elements:
-            self.gui_scale(imgui)
+    def gui_io(self, imgui):
+        """ Render GUI for import/export """
+        pass
 
-        if 'material' in self._gui_elements:
-            self.gui_material(imgui)
+    def gui_mode_view(self, imgui):
+        """ Render custom GUI for view mode """
+        pass
 
     # Renderable
     @staticmethod
@@ -385,7 +512,7 @@ def redraw(self, **kwargs):
     def set_camera_matrices(self, prog, camera, **kwargs):
         """Set the model view projection matrix in the given program."""
         # Transpose because np is row-major but OpenGL expects column-major.
-        prog['model_matrix'].write(self.model_matrix().T.astype('f4').tobytes())
+        prog['model_matrix'].write(self.model_matrix.T.astype('f4').tobytes())
         prog['view_projection_matrix'].write(camera.get_view_projection_matrix().T.astype('f4').tobytes())
 
     def receive_shadow(self, program, **kwargs):
@@ -399,7 +526,7 @@ def receive_shadow(self, program, **kwargs):
 
             for i, light in enumerate(lights):
                 if light.shadow_enabled and light.shadow_map:
-                    light_matrix = light.mvp() @ self.model_matrix()
+                    light_matrix = light.mvp() @ self.model_matrix
                     program[f'dirLights[{i}].matrix'].write(light_matrix.T.tobytes())
 
                     # Bind shadowmap to slot i + 1, we reserve slot 0 for the mesh texture
@@ -411,11 +538,11 @@ def receive_shadow(self, program, **kwargs):
             uniform.value = 1 if uniform.array_length == 1 else [*range(1, len(lights) + 1)]
 
     def render_shadowmap(self, light_matrix, prog):
-        if not self.cast_shadow:
+        if not self.cast_shadow or self.color[3] == 0.0:
             return
 
-        prog['model_matrix'].write(self.model_matrix().T.astype('f4').tobytes())
-        prog['view_projection_matrix'].write(light_matrix.T.astype('f4').tobytes())
+        prog['model_matrix'].write(self.model_matrix.T.tobytes())
+        prog['view_projection_matrix'].write(light_matrix.T.tobytes())
 
         self.render_positions(prog)
 
@@ -424,7 +551,7 @@ def render_fragmap(self, ctx, camera, prog, uid=None):
             return
 
         p = camera.get_projection_matrix()
-        mv = camera.get_view_matrix() @ self.model_matrix()
+        mv = camera.get_view_matrix() @ self.model_matrix
 
         # Transpose because np is row-major but OpenGL expects column-major.
         prog['projection'].write(p.T.astype('f4').tobytes())
@@ -458,7 +585,7 @@ def render_depth_prepass(self, camera, **kwargs):
 
     def render_outline(self, ctx, camera, prog):
         if self.outline:
-            mvp = camera.get_view_projection_matrix() @ self.model_matrix()
+            mvp = camera.get_view_projection_matrix() @ self.model_matrix
             prog['mvp'].write(mvp.T.tobytes())
 
             if self.backface_culling:
@@ -488,3 +615,9 @@ def on_selection(self, node, tri_id):
                        (can be None if the selection wasn't a mouse event)
         """
         pass
+
+    def key_event(self, key, wnd_keys):
+        """
+        Handle shortcut key presses (if you are the selected object)
+        """
+        pass
diff --git a/aitviewer/scene/scene.py b/aitviewer/scene/scene.py
index 69f0932..4ed633e 100644
--- a/aitviewer/scene/scene.py
+++ b/aitviewer/scene/scene.py
@@ -1,5 +1,5 @@
 """
-Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -51,11 +51,12 @@ def __init__(self, **kwargs):
         self.lights.append(Light(name='Back Light',  position=(0.0, 10.0, 15.0),  color=(1.0, 1.0, 1.0, 1.0)))
         self.lights.append(Light(name='Front Light', position=(0.0, 10.0, -15.0), color=(1.0, 1.0, 1.0, 1.0),
                                  shadow_enabled=False))
+
         self.add(*self.lights)
 
         # Scene items
-        self.origin = CoordinateSystem(name="Origin", length=0.1)
-        self.add(self.origin, has_gui=False)
+        self.origin = CoordinateSystem(name="Origin", length=0.1, gui_affine=False, gui_material=False)
+        self.add(self.origin)
 
         self.floor = ChessboardPlane(100.0, 200, (0.9, 0.9, 0.9, 1.0),  (0.82, 0.82, 0.82, 1.0), name="Floor")
         self.floor.material.diffuse = 0.1
@@ -70,11 +71,12 @@ def __init__(self, **kwargs):
         self.add(self.camera_target, show_in_hierarchy=False)
 
         self.custom_font = None
+        self.properties_icon = "\u0094"
 
         # Currently selected object, None if no object is selected
         self.selected_object = None
         # Object shown in the property panel
-        self._gui_selected_object = None
+        self.gui_selected_object = None
 
         # The scene node in the GUI is expanded at the start.
         self.expanded = True
@@ -148,28 +150,37 @@ def make_renderable(self, ctx):
         for r in rs:
             r.make_renderable(self.ctx)
 
+    @property
+    def bounds(self):
+        bounds = np.array([[np.inf, np.NINF], [np.inf, np.NINF], [np.inf, np.NINF]])
+        for n in self.nodes:
+            child = n.bounds
+            bounds[:, 0] = np.minimum(bounds[:, 0], child[:, 0])
+            bounds[:, 1] = np.maximum(bounds[:, 1], child[:, 1])
+        return bounds
+
+    @property
+    def current_bounds(self):
+        bounds = np.array([[np.inf, np.NINF], [np.inf, np.NINF], [np.inf, np.NINF]])
+        for n in self.nodes:
+            child = n.current_bounds
+            bounds[:, 0] = np.minimum(bounds[:, 0], child[:, 0])
+            bounds[:, 1] = np.maximum(bounds[:, 1], child[:, 1])
+        return bounds
+
     def auto_set_floor(self):
         """Finds the minimum lower bound in the y coordinate from all the children bounds and uses that as the floor"""
-        rs = self.collect_nodes()
-        collected_bounds = []
-        for r in rs:
-            if r.bounds is not None:
-                collected_bounds.append(r.bounds)
-
-        if len(collected_bounds) > 0:
-            self.floor.position[1] = np.array(collected_bounds)[:, 1, 0].min()
+        if self.floor is not None and len(self.nodes) > 0:
+            self.floor.position[1] = self.bounds[1, 0]
+            self.floor.update_transform()
 
     def auto_set_camera_target(self):
         """Sets the camera target to the average of the center of all objects in the scene"""
-        rs = self.collect_nodes()
         centers = []
-        for r in rs:
-            if r.bounds is not None:
-                center = r.bounds.mean(-1)
-                if center.sum() != 0.0:
-                    centers.append(center)
+        for n in self.nodes:
+            centers.append(n.center)
 
-        if len(centers) > 0:
+        if isinstance(self.camera, ViewerCamera) and len(centers) > 0:
             self.camera.target = np.array(centers).mean(0)
 
     def set_lights(self, is_dark_mode=False):
@@ -218,7 +229,7 @@ def select(self, obj, selected_node=None, selected_tri_id=None):
             self.selected_object.on_selection(selected_node, selected_tri_id)
         # Always keep the last selected object in the property panel
         if obj is not None:
-            self._gui_selected_object = obj
+            self.gui_selected_object = obj
 
     def is_selected(self, obj):
         """Returns true if obj is currently selected"""
@@ -226,21 +237,56 @@ def is_selected(self, obj):
 
     def gui_selected(self, imgui):
         """GUI to edit the selected node"""
-        if self._gui_selected_object:
-            s = self._gui_selected_object
+        if self.gui_selected_object:
+            s = self.gui_selected_object
 
-            # Draw object icon and name
+            # Custom GUI Elements
+            imgui.indent(22)
             imgui.push_font(self.custom_font)
-            imgui.push_style_var(imgui.STYLE_FRAME_PADDING, (0, 2))
-            e = imgui.tree_node(f"{s.icon} {s.name}##gui_selected", imgui.TREE_NODE_LEAF | imgui.TREE_NODE_FRAME_PADDING)
-            imgui.pop_style_var()
+            imgui.text(f"{s.icon} {s.name}")
             imgui.pop_font()
 
-            # Draw gui
+            # Modes
+            if hasattr(s, 'gui_modes') and len(s.gui_modes) > 1:
+                imgui.push_font(self.custom_font)
+                imgui.spacing()
+                for i, (gm_key, gm_val) in enumerate(s.gui_modes.items()):
+                    if s.selected_mode == gm_key:
+                        imgui.push_style_color(imgui.COLOR_BUTTON, 0.26, 0.59, 0.98, 1.0)
+                    mode_clicked = imgui.button(f" {gm_val['icon']}{gm_val['title']} ")
+                    if s.selected_mode == gm_key:
+                        imgui.pop_style_color()
+                    if mode_clicked:
+                        s.selected_mode = gm_key
+                    if i != len(s.gui_modes)-1:
+                        imgui.same_line()
+                imgui.pop_font()
+
+                # Mode specific GUI
+                imgui.spacing()
+                if 'fn' in s.gui_modes[s.selected_mode]:
+                    s.gui_modes[s.selected_mode]['fn'](imgui)
+
+            # Custom GUI (i.e. Camera specific params)
             s.gui(imgui)
-
-            if e:
-                imgui.tree_pop()
+            imgui.unindent()
+
+            # General GUI elements
+            if hasattr(s, 'gui_controls'):
+                imgui.spacing(); imgui.spacing(); imgui.spacing()
+                for i, (gc_key, gc_val) in enumerate(s.gui_controls.items()):
+                    if not gc_val['is_visible']:
+                        continue
+                    imgui.begin_group()
+                    imgui.push_font(self.custom_font)
+                    imgui.text(f"{gc_val['icon']}")
+                    imgui.pop_font()
+                    imgui.end_group()
+                    imgui.same_line(spacing=8)
+                    imgui.begin_group()
+                    gc_val['fn'](imgui)
+                    imgui.end_group()
+                    imgui.spacing(); imgui.spacing(); imgui.spacing()
 
     def gui(self, imgui):
         imgui.text(f"FPS: {self.fps:.1f}")
@@ -253,12 +299,14 @@ def gui_editor(self, imgui):
         """GUI to control scene settings."""
         # Also include the camera GUI in the scene node.
         self.gui_camera(imgui)
-        self.gui_renderables(imgui, [self])
-
         imgui.spacing()
         imgui.separator()
         imgui.spacing()
-
+        self.gui_hierarchy(imgui, [self])
+        imgui.spacing()
+        imgui.separator()
+        imgui.spacing()
+        imgui.spacing()
         self.gui_selected(imgui)
 
     def gui_camera(self, imgui):
@@ -301,7 +349,7 @@ def gui_lights(self, imgui):
             if light_expanded:
                 imgui.tree_pop()
 
-    def gui_renderables(self, imgui, rs):
+    def gui_hierarchy(self, imgui, rs):
         # Nodes GUI
         for r in rs:
             # Skip nodes that shouldn't appear in the hierarchy.
@@ -322,7 +370,7 @@ def gui_renderables(self, imgui, rs):
                 flags |= imgui.TREE_NODE_DEFAULT_OPEN
             if self.is_selected(r):
                 flags |= imgui.TREE_NODE_SELECTED
-            if not any(c.show_in_hierarchy for c in r.nodes) or not r.has_gui:
+            if not any(c.show_in_hierarchy for c in r.nodes):
                 flags |= imgui.TREE_NODE_LEAF
             r.expanded = imgui.tree_node("{} {}##tree_node_{}".format(r.icon, r.name, r.unique_name), flags)
             if imgui.is_item_clicked():
@@ -340,9 +388,8 @@ def gui_renderables(self, imgui, rs):
                     r.enabled = enabled
 
             if r.expanded:
-                if r.has_gui:
-                    # Recursively render children nodes
-                    self.gui_renderables(imgui, r.nodes)
+                # Recursively render children nodes
+                self.gui_hierarchy(imgui, r.nodes)
                 imgui.tree_pop()
 
             if not curr_enabled:
@@ -360,7 +407,10 @@ def n_frames(self):
         n_frames = 1
         ns = self.collect_nodes(req_enabled=False)
         for n in ns:
-            n_frames = max(n_frames, n.n_frames)
+            if n._enabled_frames is None:
+                n_frames = max(n_frames, n.n_frames)
+            else:
+                n_frames = max(n_frames, n._enabled_frames.shape[0])
         return n_frames
 
     def render_outline(self, ctx, camera, prog):
diff --git a/aitviewer/shaders.py b/aitviewer/shaders.py
index f314998..2ce9521 100644
--- a/aitviewer/shaders.py
+++ b/aitviewer/shaders.py
@@ -56,3 +56,18 @@ def get_screen_texture_program():
 @functools.lru_cache()
 def get_chessboard_program():
     return _load('chessboard.glsl')
+
+def clear_shader_cache():
+    """Clear all cached shaders."""
+    funcs =  [
+        get_smooth_lit_with_edges_program,
+        get_flat_lit_with_edges_program,
+        get_smooth_lit_texturized_program,
+        get_simple_unlit_program,
+        get_cylinder_program,
+        get_screen_texture_program,
+        get_chessboard_program,
+    ]
+    for f in funcs:
+        f.cache_clear()
+
diff --git a/aitviewer/shaders/lit_with_edges.glsl b/aitviewer/shaders/lit_with_edges.glsl
index 54c555c..758a1b3 100644
--- a/aitviewer/shaders/lit_with_edges.glsl
+++ b/aitviewer/shaders/lit_with_edges.glsl
@@ -19,9 +19,8 @@
 
 #if TEXTURE
     in vec2 in_uv;
-#else
-    in vec4 in_color;
 #endif
+    in vec4 in_color;
 
     out VS_OUT {
         vec3 vert;
@@ -32,10 +31,8 @@
 
 #if TEXTURE
         vec2 uv;
-#else
-        vec4 color;
 #endif
-
+        vec4 color;
         vec4 vert_light[NR_DIR_LIGHTS];
     } vs_out;
 
@@ -47,9 +44,8 @@
 
 #if TEXTURE
         vs_out.uv = in_uv;
-#else
-        vs_out.color = in_color;
 #endif
+        vs_out.color = in_color;
 
         vec3 world_position = (model_matrix * vec4(in_position, 1.0)).xyz;
         vs_out.vert = world_position;
@@ -80,10 +76,8 @@
 
 #if TEXTURE
         vec2 uv;
-#else
-        vec4 color;
 #endif
-
+        vec4 color;
         vec4 vert_light[NR_DIR_LIGHTS];
     } gs_in[];
 
@@ -91,10 +85,9 @@
 
 #if TEXTURE
     out vec2 g_uv;
-#else
-    out vec4 g_color;
 #endif
 
+    out vec4 g_color;
     out vec3 g_vert;
     out vec4 g_vert_light[NR_DIR_LIGHTS];
 
@@ -139,9 +132,8 @@
 
 #if TEXTURE
             g_uv = gs_in[i].uv;
-#else
-            g_color = gs_in[i].color;
 #endif
+            g_color = gs_in[i].color;
 
             for(int j = 0; j < NR_DIR_LIGHTS; j++) {
                 g_vert_light[j] = gs_in[i].vert_light[j];
@@ -156,7 +148,6 @@
 
 #if TEXTURE
     uniform sampler2D diffuse_texture;
-    uniform float texture_alpha = 1.0;
 #else
     uniform bool norm_coloring;
 #endif
@@ -172,9 +163,8 @@
 
 #if TEXTURE
     in vec2 g_uv;
-#else
-    in vec4 g_color;
 #endif
+    in vec4 g_color;
 
     in vec4 g_vert_light[NR_DIR_LIGHTS];
     noperspective in vec3 dist;
@@ -189,14 +179,14 @@
 
         vec3 normal = normalize(g_norm);
 
-#if TEXTURE
-        // Texture lookup.
-        vec4 base_color = vec4(texture(diffuse_texture, g_uv).rgb, texture_alpha);
-#else
         vec4 base_color = g_color;
         if(use_uniform_color) {
             base_color = uniform_color;
         }
+        
+#if TEXTURE
+        // Texture lookup.
+        base_color.xyz = texture(diffuse_texture, g_uv).rgb;
 #endif
 
         vec3 color = compute_lighting(base_color.rgb, g_vert, normal, g_vert_light);
diff --git a/aitviewer/utils/path.py b/aitviewer/utils/path.py
index 329d41d..d5568b7 100644
--- a/aitviewer/utils/path.py
+++ b/aitviewer/utils/path.py
@@ -16,6 +16,9 @@
 along with this program.  If not, see <https://www.gnu.org/licenses/>.
 """
 import numpy as np
+import scipy.ndimage
+
+from aitviewer.scene.node import Node
 
 
 def line(start, end, num):
@@ -40,3 +43,39 @@ def circle(center, radius, num, start_angle=0.0, end_angle=360.0):
     angles = np.linspace(np.radians(start_angle), np.radians(end_angle), num=num)
     c = np.column_stack((np.cos(angles) * radius, np.zeros(angles.shape), np.sin(angles) * radius))
     return c + center
+
+
+def lock_to_node(node: Node, relative_position, smooth_sigma=None):
+    """
+    Returns a tuple (positions, targets) of two arrays of shape (N, 3) each where N is the
+    number of frames of the node. Each target is computed as the center position of
+    the node at each frame and each position is at a constant offset from the target by the amount
+    specified in the parameter 'relative_position'. The two arrays can be smoothed
+    by passing a value greater than 0 to smooth_sigma.
+
+    :param node: the Node to follow.
+    :param relative_position: A position added to the node position to compute the positions array.
+    :param smooth_sigma: if not None and greater than 0 the position and target arrays are smoothed.
+      with a 1D gaussian kernel of standard deviation equal to this value.
+    """
+    assert isinstance(node, Node), "Node parameter must be a node"
+
+    relative_position = np.array(relative_position)
+    old_current_frame_id = node.current_frame_id
+
+    positions = np.zeros((node.n_frames, 3))
+    targets = np.zeros((node.n_frames, 3))
+    for i in range(node.n_frames):
+        node.current_frame_id = i
+        target = node.current_center
+        position = target + relative_position
+        positions[i] = position
+        targets[i] = target
+
+    node.current_frame_id = old_current_frame_id
+
+    if smooth_sigma is not None and smooth_sigma > 0:
+        positions = scipy.ndimage.gaussian_filter1d(positions, smooth_sigma, axis=0, mode='nearest')
+        targets = scipy.ndimage.gaussian_filter1d(targets, smooth_sigma, axis=0, mode='nearest')
+
+    return positions, targets
diff --git a/aitviewer/utils/utils.py b/aitviewer/utils/utils.py
index e6a0ca6..a300114 100644
--- a/aitviewer/utils/utils.py
+++ b/aitviewer/utils/utils.py
@@ -58,7 +58,6 @@ def get_video_paths(video_path):
         counter += 1
         video_path_candidate = video_path.replace(suffix, f'_{counter}{suffix}')
 
-
     if is_mp4:
         return video_path_candidate, None, is_gif
     else:
@@ -68,10 +67,10 @@ def get_video_paths(video_path):
 
 def video_to_gif(path_mp4, path_gif, remove=False):
     command = ['ffmpeg',
-                '-i', path_mp4,
-                '-y',
-                '-filter_complex', f"[0:v] split [a][b];[a] palettegen=stats_mode=single [p];[b][p] paletteuse=new=1:dither=none",
-                path_gif]
+               '-i', path_mp4,
+               '-y',
+               '-filter_complex', f"[0:v] split [a][b];[a] palettegen=stats_mode=single [p];[b][p] paletteuse=new=1:dither=none",
+               path_gif]
 
     with open(os.devnull, 'w') as FNULL:
         subprocess.Popen(command, stdout=FNULL, stderr=FNULL).wait()
@@ -198,7 +197,7 @@ def compute_vertex_and_face_normals(vertices, faces, vertex_faces, normalize=Fal
 def set_lights_in_program(prog, lights, shadows_enabled):
     """Set program lighting from scene lights"""
     for i, light in enumerate(lights):
-        prog[f'dirLights[{i}].pos'].value = light.position
+        prog[f'dirLights[{i}].pos'].value = tuple(light.position)
         prog[f'dirLights[{i}].color'].value = light.color[:3]
         prog[f'dirLights[{i}].intensity_ambient'].value = light.intensity_ambient
         prog[f'dirLights[{i}].intensity_diffuse'].value = light.intensity_diffuse
diff --git a/aitviewer/viewer.py b/aitviewer/viewer.py
index 501ed3b..71ca1ec 100644
--- a/aitviewer/viewer.py
+++ b/aitviewer/viewer.py
@@ -26,12 +26,15 @@
 
 from array import array
 from aitviewer.configuration import CONFIG as C
+from aitviewer.renderables.billboard import Billboard
 from aitviewer.renderables.meshes import Meshes, VariableTopologyMeshes
-from aitviewer.scene.camera import ViewerCamera
+from aitviewer.renderables.point_clouds import PointClouds
+from aitviewer.scene.camera import PinholeCamera, ViewerCamera
 from aitviewer.scene.scene import Scene
 from aitviewer.scene.node import Node
+from aitviewer.shaders import clear_shader_cache
 from aitviewer.streamables.streamable import Streamable
-from aitviewer.utils import PerfTimer
+from aitviewer.utils import PerfTimer, path
 from aitviewer.utils.utils import get_video_paths, video_to_gif
 from collections import namedtuple
 from moderngl_window import activate_context
@@ -48,13 +51,34 @@
 
 MeshMouseIntersection = namedtuple('MeshMouseIntersection', 'node tri_id vert_id point_world point_local bc_coords')
 
+SHORTCUTS = {
+    "SPACE": "Start/stop playing animation.",
+    ".": "Go to next frame.",
+    ",": "Go to previous frame.",
+    "X": "Center view on the selected object.",
+    "O": "Enable/disable orthographic camera.",
+    "T": "Show the camera target in the scene.",
+    "C": "Save the camera position and orientation to disk.",
+    "L": "Load the camera position and orientation from disk.",
+    "K": "Lock the selection to the currently selected object.",
+    "S": "Show/hide shadows.",
+    "D": "Enabled/disable dark mode.",
+    "P": "Save a screenshot to the the 'export/screenshots' directory.",
+    "I": "Change the viewer mode to 'inspect'",
+    "V": "Change the viewer mode to 'view'",
+    "E": "If a mesh is selected, show the edges of the mesh.",
+    "F": "If a mesh is selected, switch between flat and smooth shading.",
+    "Z": "Show a debug visualization of the object IDs.",
+    "ESC": "Exit the viewer.",
+}
+
 
 class Viewer(moderngl_window.WindowConfig):
     resource_dir = Path(__file__).parent / 'shaders'
-    window_type = 'pyqt5'
     size_mult = 1.0
     samples = 4
     gl_version = (4, 0)
+    window_type = C.window_type
 
     def __init__(self, title="AITViewer", size: Tuple[int, int]=None, config: Union[DictConfig, dict]=None, **kwargs):
         """
@@ -69,6 +93,8 @@ def __init__(self, title="AITViewer", size: Tuple[int, int]=None, config: Union[
             C.update_conf(config)
 
         # Window Setup (Following `moderngl_window.run_window_config`).
+        if self.window_type != 'headless':
+            self.window_type = C.window_type
         base_window_cls = get_local_window_cls(self.window_type)
 
         # If no size is provided use the size from the configuration file
@@ -99,7 +125,7 @@ def __init__(self, title="AITViewer", size: Tuple[int, int]=None, config: Union[
         super().__init__(self.ctx, self.window, self.timer)
 
         # Create GUI context
-        imgui.create_context()
+        self.imgui_ctx = imgui.create_context()
         self.imgui = ModernglWindowRenderer(self.wnd)
         self.imgui_user_interacting = False
 
@@ -139,6 +165,7 @@ def __init__(self, title="AITViewer", size: Tuple[int, int]=None, config: Union[
             'scene': self.gui_scene,
             'playback': self.gui_playback,
             'inspect': self.gui_inspect,
+            'shortcuts': self.gui_shortcuts,
             'exit': self.gui_exit,
         }
 
@@ -157,6 +184,7 @@ def __init__(self, title="AITViewer", size: Tuple[int, int]=None, config: Union[
         self._previous_frame_key = self.wnd.keys.COMMA
         self._shadow_key = self.wnd.keys.S
         self._orthographic_camera_key = self.wnd.keys.O
+        self._center_view_on_selection_key = self.wnd.keys.X
         self._dark_mode_key = self.wnd.keys.D
         self._screenshot_key = self.wnd.keys.P
         self._middle_mouse_button = 3  # middle
@@ -166,8 +194,6 @@ def __init__(self, title="AITViewer", size: Tuple[int, int]=None, config: Union[
         self._load_cam_key = self.wnd.keys.L
         self._show_camera_target_key = self.wnd.keys.T
         self._visualize_key = self.wnd.keys.Z
-        self._flat_shading_key = self.wnd.keys.F
-        self._draw_edges_key = self.wnd.keys.E
         self._lock_selection_key = self.wnd.keys.K
         self._mode_inspect_key = self.wnd.keys.I
         self._mode_view_key = self.wnd.keys.V
@@ -181,12 +207,17 @@ def __init__(self, title="AITViewer", size: Tuple[int, int]=None, config: Union[
                                 self.wnd.keys.P: "P",
                                 self.wnd.keys.S: "S",
                                 self.wnd.keys.T: "T",
+                                self.wnd.keys.X: "X",
                                 self.wnd.keys.Z: "Z"}
 
         # Disable exit on escape key
         self.window.exit_key = None
+
+        # GUI
         self._exit_popup_open = False
+        self._show_shortcuts_window = False
 
+    # noinspection PyAttributeOutsideInit
     def create_framebuffers(self):
         """
         Create all framebuffers which depend on the window size.
@@ -209,6 +240,7 @@ def create_framebuffers(self):
         self.outline_texture = self.ctx.texture(self.wnd.buffer_size, 1, dtype='f4')
         self.outline_framebuffer = self.ctx.framebuffer(color_attachments=[self.outline_texture])
 
+    # noinspection PyAttributeOutsideInit
     def reset(self):
         if self.scene is not None:
             self.scene.release()
@@ -285,29 +317,31 @@ def run(self, *args, log=True):
             self.window.swap_buffers()
         _, duration = self.timer.stop()
         self.on_close()
+        imgui.destroy_context(self.imgui_ctx)
+        # Necessary for pyglet window, otherwise the window is not closed.
+        self.window.close()
         self.window.destroy()
         if duration > 0 and log:
             print("Duration: {0:.2f}s @ {1:.2f} FPS".format(duration, self.window.frames / duration))
 
     def render(self, time, frame_time, export=False):
         """The main drawing function."""
-        # Advance up to 100 frames to avoid looping for too long if the playback speed is too high
-        for _ in range(100):
-            # Check if we need to advance the sequences.
-            if self.run_animations and time - self._last_frame_rendered_at > 1.0 / self.playback_fps:
-                self.scene.next_frame()
-                self._last_frame_rendered_at += 1.0 / self.playback_fps
-            else:
-                break
-
-
-        #Update camera matrices that will be used for rendering
+        if self.run_animations:
+            # Compute number of frames to advance by.
+            frames = (int)((time - self._last_frame_rendered_at) * self.playback_fps)
+            if frames > 0:
+                self.scene.current_frame_id = (self.scene.current_frame_id + frames) % self.scene.n_frames
+                self._last_frame_rendered_at += frames * (1.0 / self.playback_fps)
+
+        # Update camera matrices that will be used for rendering
+        if isinstance(self.scene.camera, ViewerCamera):
+            self.scene.camera.update_animation(frame_time)
         self.scene.camera.update_matrices(self.window.size[0], self.window.size[1])
 
         if not export:
             self.streamable_capture()
-            self.render_fragmap()
 
+        self.render_fragmap()
         self.render_shadowmap()
         self.render_prepare()
         self.render_scene()
@@ -424,6 +458,19 @@ def set_temp_camera(self, camera):
         self.scene.camera = camera
         self._using_temp_camera = True
 
+    def lock_to_node(self, node: Node, relative_position, smooth_sigma=None):
+        """
+        Create and return a PinholeCamera that follows a node, the target of the camera is the center
+        of the node at each frame and the camera is positioned with a constant offset (relative_position)
+        from its target. See aitviewer.utils.path.lock_to_node for more details about parameters.
+        The camera is set as the current viewer camera.
+        """
+        pos, tar = path.lock_to_node(node, relative_position, smooth_sigma=smooth_sigma)
+        cam = PinholeCamera(pos, tar, self.window_size[0], self.window_size[1], viewer=self)
+        self.scene.add(cam)
+        self.set_temp_camera(cam)
+        return cam
+
     def gui(self):
         imgui.new_frame()
 
@@ -459,8 +506,11 @@ def gui(self):
 
     def gui_scene(self):
         # Render scene GUI
-        imgui.begin("Editor", True)
-        self.scene.gui_editor(imgui)
+        imgui.set_next_window_position(50, 50, imgui.FIRST_USE_EVER)
+        imgui.set_next_window_size(self.window_size[0] * 0.25, self.window_size[1] * 0.7, imgui.FIRST_USE_EVER)
+        expanded, _ = imgui.begin("Editor", None)
+        if expanded:
+            self.scene.gui_editor(imgui)
         imgui.end()
 
     def gui_menu(self):
@@ -499,6 +549,11 @@ def gui_menu(self):
                 _, self.show_camera_target = imgui.menu_item("Show Camera Target", self._shortcut_names[self._show_camera_target_key],
                                                     self.show_camera_target, True)
 
+                clicked, _ = imgui.menu_item("Center view on selection", self._shortcut_names[self._center_view_on_selection_key],
+                                             False, isinstance(self.scene.selected_object, Node))
+                if clicked:
+                    self.center_view_on_selection()
+
                 is_ortho = False if self._using_temp_camera else self.scene.camera.is_ortho
                 _, is_ortho = imgui.menu_item("Orthographic Camera",
                                                                 self._shortcut_names[self._orthographic_camera_key],
@@ -531,6 +586,10 @@ def gui_menu(self):
 
                 imgui.end_menu()
 
+            if imgui.begin_menu("Help", True):
+                clicked, self._show_shortcuts_window = imgui.menu_item("Keyboard shortcuts", None, self._show_shortcuts_window)
+                imgui.end_menu()
+
             if imgui.begin_menu("Debug", True):
                 _, self.visualize = imgui.menu_item("Visualize debug texture", self._shortcut_names[self._visualize_key],
                                                     self.visualize, True)
@@ -696,47 +755,62 @@ def gui_menu(self):
 
     def gui_playback(self):
         """GUI to control playback settings."""
-        imgui.begin("Playback", True)
-        u, run_animations = imgui.checkbox("Run animations [{}]".format(self._shortcut_names[self._pause_key]),
-                                                self.run_animations)
-        if u:
-            self.toggle_animation(run_animations)
-
-        # Plot FPS
-        frametime_avg = np.mean(self._past_frametimes[self._past_frametimes > 0.0])
-        fps_avg = 1 / frametime_avg
-        ms_avg = frametime_avg * 1000.0
-        ms_last = self._past_frametimes[-1] * 1000.0
-
-        imgui.plot_lines("Internal {:.1f} fps @ {:.2f} ms/frame [{:.2f}ms]".format(fps_avg, ms_avg, ms_last),
-                         array('f', (1.0 / self._past_frametimes).tolist()),
-                         scale_min=0, scale_max=100.0, graph_size=(100, 20))
-
-        _, self.playback_fps = imgui.drag_float(f'Playback fps', self.playback_fps, 0.1,
-                                                min_value=1.0, max_value=120.0, format='%.1f')
-        imgui.same_line(spacing=10)
-        speedup = self.playback_fps / self.scene.fps
-        imgui.text(f'({speedup:.2f}x speed)')
-
-        # Sequence Control
-        # For simplicity, we allow the global sequence slider to only go as far as the shortest known sequence.
-        n_frames = self.scene.n_frames
-
-        _, self.scene.current_frame_id = imgui.slider_int('Frame##r_global_seq_control', self.scene.current_frame_id,
-                                                          min_value=0, max_value=n_frames - 1)
-        self.prevent_background_interactions()
+        imgui.set_next_window_position(50, 100 + self.window_size[1] * 0.7, imgui.FIRST_USE_EVER)
+        imgui.set_next_window_size(self.window_size[0] * 0.4, self.window_size[1] * 0.15, imgui.FIRST_USE_EVER)
+        expanded, _ = imgui.begin("Playback", None)
+        if expanded:
+            u, run_animations = imgui.checkbox("Run animations [{}]".format(self._shortcut_names[self._pause_key]),
+                                                    self.run_animations)
+            if u:
+                self.toggle_animation(run_animations)
+
+            # Plot FPS
+            frametime_avg = np.mean(self._past_frametimes[self._past_frametimes > 0.0])
+            fps_avg = 1 / frametime_avg
+            ms_avg = frametime_avg * 1000.0
+            ms_last = self._past_frametimes[-1] * 1000.0
+
+            imgui.plot_lines("Internal {:.1f} fps @ {:.2f} ms/frame [{:.2f}ms]".format(fps_avg, ms_avg, ms_last),
+                            array('f', (1.0 / self._past_frametimes).tolist()),
+                            scale_min=0, scale_max=100.0, graph_size=(100, 20))
+
+            _, self.playback_fps = imgui.drag_float(f'Playback fps', self.playback_fps, 0.1,
+                                                    min_value=1.0, max_value=120.0, format='%.1f')
+            imgui.same_line(spacing=10)
+            speedup = self.playback_fps / self.scene.fps
+            imgui.text(f'({speedup:.2f}x speed)')
+
+            # Sequence Control
+            # For simplicity, we allow the global sequence slider to only go as far as the shortest known sequence.
+            n_frames = self.scene.n_frames
+
+            _, self.scene.current_frame_id = imgui.slider_int('Frame##r_global_seq_control', self.scene.current_frame_id,
+                                                            min_value=0, max_value=n_frames - 1)
+            self.prevent_background_interactions()
         imgui.end()
 
+    def gui_shortcuts(self):
+        if self._show_shortcuts_window:
+            imgui.set_next_window_position(self.window_size[0] * 0.6, 200, imgui.FIRST_USE_EVER)
+            imgui.set_next_window_size(self.window_size[0] * 0.35, 350, imgui.FIRST_USE_EVER)
+            expanded, self._show_shortcuts_window = imgui.begin("Keyboard shortcuts", self._show_shortcuts_window)
+            if expanded:
+                for k, v in SHORTCUTS.items():
+                    imgui.bullet_text(f"{k:5} - {v}")
+            imgui.end()
+
     def gui_inspect(self):
         """GUI to control playback settings."""
         if self.selected_mode == 'inspect':
-            imgui.begin("Inspect", True)
-
-            if self.mmi is not None:
-                for k, v in zip(self.mmi._fields, self.mmi):
-                    imgui.text("{}: {}".format(k, v))
-
-            self.prevent_background_interactions()
+            imgui.set_next_window_position(self.window_size[0] * 0.6, 50, imgui.FIRST_USE_EVER)
+            imgui.set_next_window_size(self.window_size[0] * 0.35, 140, imgui.FIRST_USE_EVER)
+            expanded, _ = imgui.begin("Inspect", None)
+            if expanded:
+                if self.mmi is not None:
+                    for k, v in zip(self.mmi._fields, self.mmi):
+                        imgui.text("{}: {}".format(k, v))
+
+                self.prevent_background_interactions()
             imgui.end()
 
     def gui_exit(self):
@@ -788,9 +862,17 @@ def mesh_mouse_intersection(self, x: int, y: int):
             node = self.scene.get_node_by_uid(obj_id)
             # Camera space to world space
             point_world = np.array(np.linalg.inv(self.scene.camera.get_view_matrix()) @ np.array((x, y, z, 1.0)))[:-1]
-            point_local = (np.linalg.inv(node.model_matrix()) @ np.append(point_world, 1.0))[:-1]
-            vert_id = node.closest_vertex_in_triangle(tri_id, point_local)
-            bc_coords = node.get_bc_coords_from_points(tri_id, [point_local])
+            point_local = (np.linalg.inv(node.model_matrix) @ np.append(point_world, 1.0))[:-1]
+            if isinstance(node, Meshes) or isinstance(node, Billboard) or isinstance(node, VariableTopologyMeshes):
+                vert_id = node.closest_vertex_in_triangle(tri_id, point_local)
+                bc_coords = node.get_bc_coords_from_points(tri_id, [point_local])
+            elif isinstance(node, PointClouds):
+                vert_id = tri_id
+                bc_coords = np.array([1, 0, 0])
+            else:
+                vert_id = 0
+                bc_coords = np.array(0, 0, 0)
+
             return MeshMouseIntersection(node, tri_id, vert_id, point_world, point_local, bc_coords)
 
         return None
@@ -814,6 +896,18 @@ def select_object(self, x: int, y: int):
 
         return False
 
+    def center_view_on_selection(self):
+        if isinstance(self.scene.selected_object, Node):
+            if self._using_temp_camera:
+                self.reset_camera()
+            forward = self.scene.camera.forward
+            bounds = self.scene.selected_object.current_bounds
+            diag = np.linalg.norm(bounds[:, 0] - bounds[:, 1])
+            dist = max(0.01, diag * 1.3)
+
+            center = bounds.mean(-1)
+            self.scene.camera.move_with_animation(center - forward * dist, center)
+
     def resize(self, width: int, height: int):
         self.window_size = (width, height)
         self.imgui.resize(width, height)
@@ -867,6 +961,9 @@ def key_event(self, key, action, modifiers):
                     self.reset_camera()
                 self.scene.camera.is_ortho = not self.scene.camera.is_ortho
 
+            elif key == self._center_view_on_selection_key:
+                self.center_view_on_selection()
+
             elif key == self._mode_view_key:
                 self.selected_mode = 'view'
 
@@ -891,19 +988,13 @@ def key_event(self, key, action, modifiers):
             elif key == self._visualize_key:
                 self.visualize = not self.visualize
 
-            elif key == self._flat_shading_key:
-                selected = self.scene.selected_object
-                if isinstance(selected, Meshes) or isinstance(selected, VariableTopologyMeshes):
-                    selected.flat_shading = not selected.flat_shading
-
-            elif key == self._draw_edges_key:
-                selected = self.scene.selected_object
-                if isinstance(selected, Meshes) or isinstance(selected, VariableTopologyMeshes):
-                    selected.draw_edges = not selected.draw_edges
-
             elif key == self._lock_selection_key:
                 self.lock_selection = not self.lock_selection
 
+            # Pass onto selected object
+            if isinstance(self.scene.gui_selected_object, Node):
+                self.scene.gui_selected_object.key_event(key, self.wnd.keys)
+
         if action == self.wnd.keys.ACTION_RELEASE:
             pass
 
@@ -991,8 +1082,65 @@ def get_current_frame_as_image(self):
                                 (self.wnd.fbo.viewport[2] - self.wnd.fbo.viewport[0],
                                  self.wnd.fbo.viewport[3] - self.wnd.fbo.viewport[1]),
                                 self.wnd.fbo.read(viewport=self.wnd.fbo.viewport, alignment=1))
-        image = image.transpose(Image.FLIP_TOP_BOTTOM)
-        return image
+        return image.transpose(Image.FLIP_TOP_BOTTOM)
+
+    def get_current_depth_image(self):
+        """
+        Return the depth buffer as a 'F' PIL image.
+        Depth is stored as the z coordinate in eye (view) space.
+        Therefore values in the depth image represent the distance from the pixel to
+        the plane passing through the camera and orthogonal to the view direction.
+        Values are between the near and far plane distances of the camera used for rendering,
+        everything outside this range is clipped by OpenGL.
+        """
+        # Get depth image from depth buffer.
+        depth = Image.frombytes('F',
+                                (self.wnd.fbo.viewport[2] - self.wnd.fbo.viewport[0],
+                                 self.wnd.fbo.viewport[3] - self.wnd.fbo.viewport[1]),
+                                self.wnd.fbo.read(viewport=self.wnd.fbo.viewport, alignment=1, attachment=-1, dtype='f4'))
+
+        # Convert from [0, 1] range to [-1, 1] range.
+        # This is necessary because our projection matrix computes NDC
+        # from [-1, 1], but depth is then stored normalized from 0 to 1.
+        depth = np.array(depth) * 2.0 - 1.0
+
+        # Extract projection matrix parameters used for mapping Z coordinates.
+        P = self.scene.camera.get_projection_matrix()
+        a, b = P[2, 2], P[2, 3]
+
+        # Linearize depth values. This converts from [-1, 1] range to the
+        # view space Z coordinate value, with positive z in front of the camera.
+        z = b / (a + depth)
+        return Image.fromarray(z, mode='F').transpose(Image.FLIP_TOP_BOTTOM)
+
+    def get_current_mask_image(self):
+        """
+        Render and return a color mask as a 'RGB' PIL image. Each object in the mask
+        has a uniform color computed as an hash of the Node uid.
+        """
+        # Get object ids as floating point numbers from the first channel of
+        # the first attachment of the picking framebuffer.
+        id = Image.frombytes('F',
+                             (self.wnd.fbo.viewport[2] - self.wnd.fbo.viewport[0],
+                              self.wnd.fbo.viewport[3] - self.wnd.fbo.viewport[1]),
+                             self.offscreen_p.read(viewport=self.wnd.fbo.viewport, alignment=1, components=1, attachment=1, dtype='f4'))
+
+        # Convert the id to integer values.
+        id_int = np.asarray(id).astype(dtype=np.int32)
+
+        # Hash the ids.
+        def hash(h):
+            h ^= h >> 16
+            h *= 0x85ebca6b
+            h ^= h >> 13
+            h *= 0xc2b2ae35
+            h ^= h >> 16
+            return h
+        hashed = hash(id_int.copy())
+
+        # Convert the hashed ids to an RGBA image and then throw away the alpha channel.
+        img = Image.frombytes('RGBA', id.size, hashed.tobytes()).convert('RGB')
+        return img.transpose(Image.FLIP_TOP_BOTTOM)
 
     def on_close(self):
         """
@@ -1002,11 +1150,16 @@ def on_close(self):
         for s in self.scene.collect_nodes(obj_type=Streamable):
             s.stop()
 
+        # Clear the lru_cache on all shaders, we do this so that future instances of the viewer
+        # have to recompile shaders with the current moderngl context.
+        # See issue #12 https://github.com/eth-ait/aitviewer/issues/12
+        clear_shader_cache()
+
     def take_screenshot(self):
         """Save the current frame to an image in the screenshots directory inside the export directory"""
         file_path = os.path.join(C.export_dir, 'screenshots', 'frame_{:0>6}.png'.format(self.scene.current_frame_id))
         self.export_frame(file_path)
-        print(f"Saved screenshot to {file_path}")
+        print(f"Screenshot saved to {file_path}")
 
     def export_frame(self, file_path, scale_factor:float=None):
         """Save the current frame to an image.
@@ -1061,7 +1214,6 @@ def export_video(
 
             # The frame dir does not yet exist (we've made sure of it).
             os.makedirs(frame_dir)
-            print("Saving frames to {}".format(frame_dir))
 
         # Store the current camera and create a copy of it if required.
         saved_camera = self.scene.camera
@@ -1156,15 +1308,18 @@ def export_video(
         # Save the video.
         if output_path is not None:
             writer.close()
-
-            # Convert to gif if required.
             if is_gif:
+                # Convert to gif.
                 video_to_gif(path_mp4, path_gif, remove=True)
 
+                print(f"GIF saved to {os.path.abspath(path_gif)}")
+            else:
+                print(f"Video saved to {os.path.abspath(output_path)}")
+        else:
+            print(f"Frames saved to {os.path.abspath(frame_dir)}")
+
         # Reset viewer data.
         self.scene.camera = saved_camera
         self.scene.current_frame_id = saved_curr_frame
         self.run_animations = saved_run_animations
         self._last_frame_rendered_at = self.timer.time
-
-        print("Done.")
diff --git a/examples/animation.py b/examples/animation.py
new file mode 100644
index 0000000..be34fd6
--- /dev/null
+++ b/examples/animation.py
@@ -0,0 +1,91 @@
+"""
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+"""
+import numpy as np
+import trimesh
+
+from aitviewer.renderables.meshes import Meshes
+from aitviewer.viewer import Viewer
+from aitviewer.utils.so3 import aa2rot_numpy
+
+
+if __name__ == '__main__':
+    # Number of frames.
+    N = 200
+
+    # Load a simple untextured cube.
+    cube = trimesh.load('resources/cube.obj')
+
+    # Create sequences of position, scale and rotation values.
+    p1 = np.linspace(np.array([0, 0, 0]), np.array([5, 0, 0]), num=N)
+    p2 = np.linspace(np.array([0, 0, 0]), np.array([0, 0, 5]), num=N)
+    p3 = np.linspace(np.array([0, 0, 0]), np.array([0, 5, 0]), num=N)
+    r2 = aa2rot_numpy(np.linspace([0, 0, 0], np.array([0, np.pi, 0]), num=N))
+    s3 = np.linspace(1.0, 2.0, num=N)
+
+    # Create 3 cubes, specifying sequences for their position, rotation and/or scale.
+    # Each property can be either a single value (e.g. a single position of shape (3)) or an array
+    # of values (e.g. an array of positions with shape (N, 3) where N is the number of frames).
+    # Array properties will be animated when playing the sequence (you can start playing pressing the spacebar).
+    c1 = Meshes(cube.vertices, cube.faces, name='C1', position=p1, color=(0.5, 0, 0, 1), flat_shading=True)
+    c2 = Meshes(cube.vertices, cube.faces, name='C2', position=p2 + [3.0, 0.0, 0.0], rotation=r2, color=(0.3, 0, 0, 1),
+                flat_shading=True)
+    c3 = Meshes(cube.vertices, cube.faces, name='C3', position=p3 + [3.0, 0.0, 0.0], scale=s3, color=(0.1, 0.1, 0.1, 1),
+                flat_shading=True)
+
+    # Some properties of renderable objects can also be animated, such as the vertices of a mesh.
+    # Here we create an array of vertices and vertex colors with shape (N, V, 3) and (N, V, 4) respectively.
+
+    # Load a simple sphere.
+    sphere = trimesh.load('resources/planet/planet.obj')
+
+    # Create initial and final vertex positions.
+    vertices_begin = sphere.vertices
+    vertices_end = sphere.vertices.copy()
+    for i in range(3):
+        # Clamp vertices in each 3D direction preserving the sign to create a cube from the sphere.
+        vertices_begin[:, i] = np.sign(vertices_end[:, i]) * np.minimum(np.abs(vertices_begin[:, i]),
+                                                                        np.full((vertices_begin.shape[0]), 1.7))
+    # Linearly interpolate vertex positions.
+    vertices = np.linspace(vertices_begin, vertices_end, N)
+
+    # Linearly interpolate vertex colors.
+    vertex_colors_begin = np.tile(np.array([0, 0, 0.5, 1]), (vertices.shape[1], 1))
+    vertex_colors_end = np.tile(np.array([0, 0.5, 0.0, 1]), (vertices.shape[1], 1))
+    vertex_colors = np.linspace(vertex_colors_begin, vertex_colors_end, N)
+
+    # Create the node with the vertices and colors we computed.
+    cubesphere = Meshes(vertices, sphere.faces, name='CubeSphere', position=[-5, 2, 0], scale=0.5,
+                        vertex_colors=vertex_colors, flat_shading=True)
+
+    # Create a viewer.
+    v = Viewer()
+
+    # Set the camera position.
+    v.scene.camera.position = (0, 10, 20)
+
+    # Add the parent object to the scene.
+    v.scene.add(c1)
+
+    # Add the other two cubes as children of the first and second cube respectively creating a hierarchy of 3 nodes.
+    # The transform (position, rotation and scale) of each node is also applied to all children.
+    c1.add(c2)
+    c2.add(c3)
+
+    # Add the animated mesh to the scene.
+    v.scene.add(cubesphere)
+
+    v.run()
diff --git a/examples/headless_rendering.py b/examples/headless_rendering.py
index f35c82d..71892b4 100644
--- a/examples/headless_rendering.py
+++ b/examples/headless_rendering.py
@@ -24,11 +24,17 @@
 
 if __name__ == '__main__':
     # Load an AMASS sequence.
-    smpl_seq = SMPLSequence.from_amass(npz_data_path=os.path.join(C.datasets.amass, 'TotalCapture/s2/rom3_poses.npz'),
-                                       start_frame=0, end_frame=500, fps_out=60.0)
+    smpl_seq = SMPLSequence.from_amass(
+        npz_data_path=os.path.join(C.datasets.amass, "ACCAD/Female1Running_c3d/C2 - Run to stand_poses.npz"),
+        fps_out=60.0, name="AMASS Running", show_joint_angles=True)
+    smpl_seq.color = smpl_seq.color[:3] + (0.75,)  # Make the sequence a bit transparent.
 
-    # Render to video.
+    # Create the headless renderer and add the sequence.
     v = HeadlessRenderer()
     v.scene.add(smpl_seq)
-    v.scene.camera.position = np.array([0.0, 0.5, 4.0])
+
+    # Have the camera automatically follow the SMPL sequence. For every frame, the camera points to the center of the
+    # bounding box of the SMPL mesh while keeping a fixed relative distance. The smoothing is optional but ensures that
+    # the view is not too jittery.
+    v.lock_to_node(smpl_seq, (2, 2, 2), smooth_sigma=5.0)
     v.save_video(video_dir=os.path.join(C.export_dir, 'headless/test.mp4'))
diff --git a/examples/load_ROMP.py b/examples/load_ROMP.py
index a277f58..719ffff 100644
--- a/examples/load_ROMP.py
+++ b/examples/load_ROMP.py
@@ -61,8 +61,7 @@
 
     # The OpenCVCamera class expects extrinsics with Y pointing down, so we flip both Y and Z axis to keep a
     # positive determinant.
-    cam_extrinsics[1, 1] = -1.0
-    cam_extrinsics[2, 2] = -1.0
+    cam_extrinsics[1:3, :3] *= -1.0
 
     # Create an OpenCV camera.
     cameras = OpenCVCamera(cam_intrinsics, cam_extrinsics[:3], cols, rows, viewer=viewer)
diff --git a/examples/load_STAR.py b/examples/load_STAR.py
new file mode 100644
index 0000000..84075eb
--- /dev/null
+++ b/examples/load_STAR.py
@@ -0,0 +1,39 @@
+"""
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+"""
+import os
+
+from aitviewer.configuration import CONFIG as C
+from aitviewer.models.star import STARLayer
+from aitviewer.renderables.star import STARSequence
+from aitviewer.viewer import Viewer
+
+
+if __name__ == '__main__':
+    # Instantiate a STAR layer. This requires that the respective repo has been installed via
+    # pip install git+https://github.com/ahmedosman/STAR.git and that the model files are available on the path
+    # specified in `C.star_models`.
+    star_layer = STARLayer(device=C.device)
+
+    # Load a STAR Sequence from AMASS data. BETAs will not be loaded by default and need to be converted.
+    star_seq = STARSequence.from_amass(
+        npz_data_path=os.path.join(C.datasets.amass, "ACCAD/Female1Running_c3d/C2 - Run to stand_poses.npz"),
+        fps_out=60.0, name="AMASS Running", show_joint_angles=False)
+
+    # Add to scene and render
+    v = Viewer()
+    v.scene.add(star_seq)
+    v.run()
diff --git a/examples/load_VIBE.py b/examples/load_VIBE.py
index 81c40e6..3cc7603 100644
--- a/examples/load_VIBE.py
+++ b/examples/load_VIBE.py
@@ -86,7 +86,7 @@ def sort_key(x):
     viewer.auto_set_floor = False
     viewer.playback_fps = 25
     viewer.scene.fps = 25
-    viewer.scene.floor.position[1] = -1.15
+    viewer.scene.floor.position = np.array([0, -1.15, 0])
     viewer.scene.origin.enabled = False
     viewer.shadows_enabled = False
 
diff --git a/examples/load_obj.py b/examples/load_obj.py
index 8e13bc0..e5c17e2 100644
--- a/examples/load_obj.py
+++ b/examples/load_obj.py
@@ -38,7 +38,7 @@
     texture_image = 'resources/drill/drill_uv.png'
     drill_mesh = Meshes(drill.vertices, drill.faces, drill.vertex_normals,
                         uv_coords=drill.visual.uv, path_to_texture=texture_image, scale=50.0,
-                        texture_alpha=0.5)
+                        color=(1, 1, 1, 0.5))
 
     # Display in viewer.
     v = Viewer()
diff --git a/examples/load_template.py b/examples/load_template.py
index 9d83c9a..c5a754e 100644
--- a/examples/load_template.py
+++ b/examples/load_template.py
@@ -21,6 +21,7 @@
 from aitviewer.renderables.smpl import SMPLSequence
 from aitviewer.viewer import Viewer
 
+
 if __name__ == '__main__':
     # Create a neutral SMPL T Pose.
     # This also works with `smplh` or `smplx` model type (but there's no neutral model for SMPL-H).
diff --git a/examples/missing_frames.py b/examples/missing_frames.py
new file mode 100644
index 0000000..3661abb
--- /dev/null
+++ b/examples/missing_frames.py
@@ -0,0 +1,88 @@
+"""
+Copyright (C) 2022  ETH Zurich, Manuel Kaufmann, Velko Vechev, Dario Mylonopoulos
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+"""
+import joblib
+import numpy as np
+
+from aitviewer.viewer import Viewer
+from aitviewer.renderables.smpl import SMPLSequence
+from aitviewer.models.smpl import SMPLLayer
+from aitviewer.configuration import CONFIG as C
+from aitviewer.utils.so3 import aa2rot_numpy
+
+
+if __name__ == '__main__':
+    # In this example we show a way to play sequences of data that has missing frames.
+    # Here we assume that we have data only for the frames that exist but we also know the indices
+    # of missing frames and we want to avoid padding our data with invalid frames.
+    # Instead, we create a mask that has ones for each frame for which we have data and zeros
+    # everywhere else. Thus the number of ones in the mask must match the number of frames in the data.
+
+    # Load SMPL data from the output of the VIBE demo from https://github.com/mkocabas/VIBE
+    data = joblib.load(open("resources/vibe/vibe_output.pkl", 'rb'))
+    poses = data[1]['pose']
+    betas = data[1]['betas']
+
+    # Number of total frames, including frames that are missing.
+    N = poses.shape[0]
+
+    # Number of existing frames of data. We are going to remove 100 frames to create holes in our data.
+    M = N - 100
+
+    # Create a hole of 100 frames in the data, we now have M frames of data.
+    # In this example we suppose that we have 25 frames of data followed by a hole of 100 frames.
+    p = np.concatenate((poses[:25], poses[125:]))
+    b = np.concatenate((betas[:25], betas[125:]))
+
+    # Create a mask with N frames.
+    enabled_frames = np.ones(N, dtype=np.bool8)
+    # Set to zero the values at the indices of the 100 missing frames. There are now only M ones in the mask.
+    enabled_frames[25:125] = 0
+
+    # Instantiate an SMPL sequence using the parameters and the enabled_frames mask.
+    smpl_layer = SMPLLayer(model_type='smpl', gender='neutral', device=C.device)
+    smpl_seq = SMPLSequence(poses_body=p[:, 3:24 * 3],
+                            poses_root=p[:, 0:3],
+                            betas=b,
+                            smpl_layer=smpl_layer,
+                            position=(1, 0, 0),
+                            rotation=aa2rot_numpy(np.array([1, 0, 0]) * np.pi),
+                            enabled_frames=enabled_frames)
+
+    # We do the same for a second sequence but removing a different range of frames.
+    # Notice how the two sequences remain in sync, since the sequence is only advanced
+    # during frames that are enabled (where the mask value is one).
+    p2 = np.concatenate((poses[:175], poses[275:]))
+    b2 = np.concatenate((betas[:175], betas[275:]))
+    enabled_frames2 = np.ones(N, dtype=np.bool8)
+    enabled_frames2[175:275] = 0
+    smpl_layer2 = SMPLLayer(model_type='smpl', gender='neutral', device=C.device)
+    smpl_seq2 = SMPLSequence(poses_body=p2[:, 3:24 * 3],
+                             poses_root=p2[:, 0:3],
+                             betas=b2,
+                             smpl_layer=smpl_layer2,
+                             position=(-1, 0, 0),
+                             rotation=aa2rot_numpy(np.array([1, 0, 0]) * np.pi),
+                             enabled_frames=enabled_frames2)
+
+    # Run the viewer
+    viewer = Viewer(size=(1600, 900))
+    viewer.scene.floor.enabled = False
+    viewer.scene.camera.position = (0, 1, 5)
+    viewer.scene.fps = 25
+    viewer.playback_fps = 25
+    viewer.scene.add(smpl_seq, smpl_seq2)
+    viewer.run()
diff --git a/examples/quickstart.py b/examples/quickstart.py
index 8210c88..572765a 100644
--- a/examples/quickstart.py
+++ b/examples/quickstart.py
@@ -19,9 +19,6 @@
 
 
 if __name__ == '__main__':
-    smpl_template = SMPLSequence.t_pose()
-
-    # Display in viewer.
     v = Viewer()
-    v.scene.add(smpl_template)
+    v.scene.add(SMPLSequence.t_pose())
     v.run()
diff --git a/setup.cfg b/setup.cfg
index d62adf3..b605216 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,4 +6,5 @@ addopts = -rs
 testpaths = tests
 filterwarnings =
     ignore::DeprecationWarning:chumpy.*:
-    ignore::DeprecationWarning:skvideo.*:
\ No newline at end of file
+    ignore::DeprecationWarning:skvideo.*:
+    ignore::DeprecationWarning:smplx.*:
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 37619b9..9ee5046 100644
--- a/setup.py
+++ b/setup.py
@@ -25,11 +25,13 @@
               'imgui>=1.3.0',
               'tqdm>=4.60.0',
               'trimesh>=3.9.15,<4',
-              'rtree>=0.9.7',
               'scipy>=1.5.2,<1.8',
               'omegaconf>=2.1.1',
               'roma>=1.2.3',
               'joblib',
               'scikit-video',
               'Pillow'
-          ])
+          ],
+        extras_require = {
+            "star": ["star @ git+https://github.com/ahmedosman/STAR.git"]
+        })


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..642f239
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,11 @@
+from utils import FAILURE_DIR
+
+import shutil
+import pytest
+import os
+
+@pytest.fixture(scope="session", autouse=True)
+def setup_failures():
+    """At the start of a test session remove the failures directory"""
+    if os.path.exists(FAILURE_DIR):
+        shutil.rmtree(FAILURE_DIR)
diff --git a/tests/reference/amass_0.png b/tests/reference/amass_0.png
index 2a6c1e0..38f740d 100644
Binary files a/tests/reference/amass_0.png and b/tests/reference/amass_0.png differ
diff --git a/tests/reference/amass_1.png b/tests/reference/amass_1.png
index 9fef966..51543f1 100644
Binary files a/tests/reference/amass_1.png and b/tests/reference/amass_1.png differ
diff --git a/tests/reference/amass_2.png b/tests/reference/amass_2.png
index 9963622..fd1ba16 100644
Binary files a/tests/reference/amass_2.png and b/tests/reference/amass_2.png differ
diff --git a/tests/reference/normals.png b/tests/reference/normals.png
index 89f0263..20ec8b2 100644
Binary files a/tests/reference/normals.png and b/tests/reference/normals.png differ
diff --git a/tests/reference/obj.png b/tests/reference/obj.png
index 7844196..9b3311e 100644
Binary files a/tests/reference/obj.png and b/tests/reference/obj.png differ
diff --git a/tests/reference/renderables.png b/tests/reference/renderables.png
index 12d273e..5a6c5c3 100644
Binary files a/tests/reference/renderables.png and b/tests/reference/renderables.png differ
diff --git a/tests/reference/smpl.png b/tests/reference/smpl.png
index d8d1db3..450fb96 100644
Binary files a/tests/reference/smpl.png and b/tests/reference/smpl.png differ
diff --git a/tests/reference/smplh.png b/tests/reference/smplh.png
index 797a7d6..48cfa46 100644
Binary files a/tests/reference/smplh.png and b/tests/reference/smplh.png differ
diff --git a/tests/reference/smplx.png b/tests/reference/smplx.png
index 5833d9d..ca268b6 100644
Binary files a/tests/reference/smplx.png and b/tests/reference/smplx.png differ
diff --git a/tests/reference/vertex_face_colors.png b/tests/reference/vertex_face_colors.png
index 95d2034..6b6211c 100644
Binary files a/tests/reference/vertex_face_colors.png and b/tests/reference/vertex_face_colors.png differ
diff --git a/tests/test_renderables.py b/tests/test_renderables.py
index e4575f3..16e85c2 100644
--- a/tests/test_renderables.py
+++ b/tests/test_renderables.py
@@ -59,7 +59,7 @@ def test_obj(viewer: Viewer):
     texture_image = os.path.join(RESOURCE_DIR, 'drill/drill_uv.png')
     drill_mesh = Meshes(drill.vertices, drill.faces, drill.vertex_normals,
                         uv_coords=drill.visual.uv, path_to_texture=texture_image, scale=50.0,
-                        texture_alpha=0.5)
+                        color=(1, 1, 1, 0.5))
 
     viewer.scene.camera.dolly_zoom(-100.0)
     viewer.scene.add(planet_mesh, drill_mesh, cube_mesh)
@@ -72,11 +72,7 @@ def test_smplh(viewer: Viewer):
                                                position=np.array((-1, 0.0, 0.0)))
     smplh_female = SMPLSequence.t_pose(SMPLLayer(model_type='smplh', gender='female', device=C.device), name='SMPL',
                                                  position=np.array((1, 0.0, 0.0)))
-    mano = SMPLSequence.t_pose(SMPLLayer(model_type='mano', gender='neutral', device=C.device),
-                                         position=np.array((-1.5, 0.0, 0.0)), name='MANO')
-    flame = SMPLSequence.t_pose(SMPLLayer(model_type='flame', gender='neutral', device=C.device),
-                                          position=np.array((1.5, 0.0, 0.0)), name='FLAME')
-    viewer.scene.add(smplh_male, smplh_female, mano, flame)
+    viewer.scene.add(smplh_male, smplh_female)
 
 
 @reference()
diff --git a/tests/utils.py b/tests/utils.py
index 4dfd663..409d441 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -43,7 +43,12 @@ def viewer(refs):
     for ref, img in zip(refs, generate_images(headless, len(refs))):
         # Load and check the refernce image.
         ref_img = Image.open(ref)
-        if np.any(np.asarray(img) != np.asarray(ref_img)):
+
+        diff = ImageChops.difference(img, ref_img)
+        relative_error = np.asarray(diff).sum() / np.full(np.asarray(img).shape, 255).sum()
+        
+        # If the relative error is higher than this threshold report a failure.
+        if relative_error > 1e-5:
             os.makedirs(FAILURE_DIR, exist_ok=True)
 
             # Store the wrong result and diff for debugging.
@@ -53,8 +58,7 @@ def viewer(refs):
             diff = ImageChops.difference(img, ref_img)
             diff.save(base + "_diff" + ext)
 
-            relative_error = np.asarray(diff).sum() / np.full(np.asarray(img).shape, 255).sum()
-            assert False, f"Image does not match reference {ref}, the wrong image has been saved to {wrong} (Relative Error {relative_error:.4f})"
+            assert False, f"Image does not match reference {ref}, the wrong image has been saved to {wrong} (Relative Error {relative_error:.4})"
 
 
 def requires_smpl(func):