Spaces:
Runtime error
Runtime error
| """Scenes, conforming to the glTF 2.0 standards as specified in | |
| https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-scene | |
| Author: Matthew Matl | |
| """ | |
| import numpy as np | |
| import networkx as nx | |
| import trimesh | |
| from .mesh import Mesh | |
| from .camera import Camera | |
| from .light import Light, PointLight, DirectionalLight, SpotLight | |
| from .node import Node | |
| from .utils import format_color_vector | |
| class Scene(object): | |
| """A hierarchical scene graph. | |
| Parameters | |
| ---------- | |
| nodes : list of :class:`Node` | |
| The set of all nodes in the scene. | |
| bg_color : (4,) float, optional | |
| Background color of scene. | |
| ambient_light : (3,) float, optional | |
| Color of ambient light. Defaults to no ambient light. | |
| name : str, optional | |
| The user-defined name of this object. | |
| """ | |
| def __init__(self, | |
| nodes=None, | |
| bg_color=None, | |
| ambient_light=None, | |
| name=None): | |
| if bg_color is None: | |
| bg_color = np.ones(4) | |
| else: | |
| bg_color = format_color_vector(bg_color, 4) | |
| if ambient_light is None: | |
| ambient_light = np.zeros(3) | |
| if nodes is None: | |
| nodes = set() | |
| self._nodes = set() # Will be added at the end of this function | |
| self.bg_color = bg_color | |
| self.ambient_light = ambient_light | |
| self.name = name | |
| self._name_to_nodes = {} | |
| self._obj_to_nodes = {} | |
| self._obj_name_to_nodes = {} | |
| self._mesh_nodes = set() | |
| self._point_light_nodes = set() | |
| self._spot_light_nodes = set() | |
| self._directional_light_nodes = set() | |
| self._camera_nodes = set() | |
| self._main_camera_node = None | |
| self._bounds = None | |
| # Transform tree | |
| self._digraph = nx.DiGraph() | |
| self._digraph.add_node('world') | |
| self._path_cache = {} | |
| # Find root nodes and add them | |
| if len(nodes) > 0: | |
| node_parent_map = {n: None for n in nodes} | |
| for node in nodes: | |
| for child in node.children: | |
| if node_parent_map[child] is not None: | |
| raise ValueError('Nodes may not have more than ' | |
| 'one parent') | |
| node_parent_map[child] = node | |
| for node in node_parent_map: | |
| if node_parent_map[node] is None: | |
| self.add_node(node) | |
| def name(self): | |
| """str : The user-defined name of this object. | |
| """ | |
| return self._name | |
| def name(self, value): | |
| if value is not None: | |
| value = str(value) | |
| self._name = value | |
| def nodes(self): | |
| """set of :class:`Node` : Set of nodes in the scene. | |
| """ | |
| return self._nodes | |
| def bg_color(self): | |
| """(3,) float : The scene background color. | |
| """ | |
| return self._bg_color | |
| def bg_color(self, value): | |
| if value is None: | |
| value = np.ones(4) | |
| else: | |
| value = format_color_vector(value, 4) | |
| self._bg_color = value | |
| def ambient_light(self): | |
| """(3,) float : The ambient light in the scene. | |
| """ | |
| return self._ambient_light | |
| def ambient_light(self, value): | |
| if value is None: | |
| value = np.zeros(3) | |
| else: | |
| value = format_color_vector(value, 3) | |
| self._ambient_light = value | |
| def meshes(self): | |
| """set of :class:`Mesh` : The meshes in the scene. | |
| """ | |
| return set([n.mesh for n in self.mesh_nodes]) | |
| def mesh_nodes(self): | |
| """set of :class:`Node` : The nodes containing meshes. | |
| """ | |
| return self._mesh_nodes | |
| def lights(self): | |
| """set of :class:`Light` : The lights in the scene. | |
| """ | |
| return self.point_lights | self.spot_lights | self.directional_lights | |
| def light_nodes(self): | |
| """set of :class:`Node` : The nodes containing lights. | |
| """ | |
| return (self.point_light_nodes | self.spot_light_nodes | | |
| self.directional_light_nodes) | |
| def point_lights(self): | |
| """set of :class:`PointLight` : The point lights in the scene. | |
| """ | |
| return set([n.light for n in self.point_light_nodes]) | |
| def point_light_nodes(self): | |
| """set of :class:`Node` : The nodes containing point lights. | |
| """ | |
| return self._point_light_nodes | |
| def spot_lights(self): | |
| """set of :class:`SpotLight` : The spot lights in the scene. | |
| """ | |
| return set([n.light for n in self.spot_light_nodes]) | |
| def spot_light_nodes(self): | |
| """set of :class:`Node` : The nodes containing spot lights. | |
| """ | |
| return self._spot_light_nodes | |
| def directional_lights(self): | |
| """set of :class:`DirectionalLight` : The directional lights in | |
| the scene. | |
| """ | |
| return set([n.light for n in self.directional_light_nodes]) | |
| def directional_light_nodes(self): | |
| """set of :class:`Node` : The nodes containing directional lights. | |
| """ | |
| return self._directional_light_nodes | |
| def cameras(self): | |
| """set of :class:`Camera` : The cameras in the scene. | |
| """ | |
| return set([n.camera for n in self.camera_nodes]) | |
| def camera_nodes(self): | |
| """set of :class:`Node` : The nodes containing cameras in the scene. | |
| """ | |
| return self._camera_nodes | |
| def main_camera_node(self): | |
| """set of :class:`Node` : The node containing the main camera in the | |
| scene. | |
| """ | |
| return self._main_camera_node | |
| def main_camera_node(self, value): | |
| if value not in self.nodes: | |
| raise ValueError('New main camera node must already be in scene') | |
| self._main_camera_node = value | |
| def bounds(self): | |
| """(2,3) float : The axis-aligned bounds of the scene. | |
| """ | |
| if self._bounds is None: | |
| # Compute corners | |
| corners = [] | |
| for mesh_node in self.mesh_nodes: | |
| mesh = mesh_node.mesh | |
| pose = self.get_pose(mesh_node) | |
| corners_local = trimesh.bounds.corners(mesh.bounds) | |
| corners_world = pose[:3,:3].dot(corners_local.T).T + pose[:3,3] | |
| corners.append(corners_world) | |
| if len(corners) == 0: | |
| self._bounds = np.zeros((2,3)) | |
| else: | |
| corners = np.vstack(corners) | |
| self._bounds = np.array([np.min(corners, axis=0), | |
| np.max(corners, axis=0)]) | |
| return self._bounds | |
| def centroid(self): | |
| """(3,) float : The centroid of the scene's axis-aligned bounding box | |
| (AABB). | |
| """ | |
| return np.mean(self.bounds, axis=0) | |
| def extents(self): | |
| """(3,) float : The lengths of the axes of the scene's AABB. | |
| """ | |
| return np.diff(self.bounds, axis=0).reshape(-1) | |
| def scale(self): | |
| """(3,) float : The length of the diagonal of the scene's AABB. | |
| """ | |
| return np.linalg.norm(self.extents) | |
| def add(self, obj, name=None, pose=None, | |
| parent_node=None, parent_name=None): | |
| """Add an object (mesh, light, or camera) to the scene. | |
| Parameters | |
| ---------- | |
| obj : :class:`Mesh`, :class:`Light`, or :class:`Camera` | |
| The object to add to the scene. | |
| name : str | |
| A name for the new node to be created. | |
| pose : (4,4) float | |
| The local pose of this node relative to its parent node. | |
| parent_node : :class:`Node` | |
| The parent of this Node. If None, the new node is a root node. | |
| parent_name : str | |
| The name of the parent node, can be specified instead of | |
| `parent_node`. | |
| Returns | |
| ------- | |
| node : :class:`Node` | |
| The newly-created and inserted node. | |
| """ | |
| if isinstance(obj, Mesh): | |
| node = Node(name=name, matrix=pose, mesh=obj) | |
| elif isinstance(obj, Light): | |
| node = Node(name=name, matrix=pose, light=obj) | |
| elif isinstance(obj, Camera): | |
| node = Node(name=name, matrix=pose, camera=obj) | |
| else: | |
| raise TypeError('Unrecognized object type') | |
| if parent_node is None and parent_name is not None: | |
| parent_nodes = self.get_nodes(name=parent_name) | |
| if len(parent_nodes) == 0: | |
| raise ValueError('No parent node with name {} found' | |
| .format(parent_name)) | |
| elif len(parent_nodes) > 1: | |
| raise ValueError('More than one parent node with name {} found' | |
| .format(parent_name)) | |
| parent_node = list(parent_nodes)[0] | |
| self.add_node(node, parent_node=parent_node) | |
| return node | |
| def get_nodes(self, node=None, name=None, obj=None, obj_name=None): | |
| """Search for existing nodes. Only nodes matching all specified | |
| parameters is returned, or None if no such node exists. | |
| Parameters | |
| ---------- | |
| node : :class:`Node`, optional | |
| If present, returns this node if it is in the scene. | |
| name : str | |
| A name for the Node. | |
| obj : :class:`Mesh`, :class:`Light`, or :class:`Camera` | |
| An object that is attached to the node. | |
| obj_name : str | |
| The name of an object that is attached to the node. | |
| Returns | |
| ------- | |
| nodes : set of :class:`.Node` | |
| The nodes that match all query terms. | |
| """ | |
| if node is not None: | |
| if node in self.nodes: | |
| return set([node]) | |
| else: | |
| return set() | |
| nodes = set(self.nodes) | |
| if name is not None: | |
| matches = set() | |
| if name in self._name_to_nodes: | |
| matches = self._name_to_nodes[name] | |
| nodes = nodes & matches | |
| if obj is not None: | |
| matches = set() | |
| if obj in self._obj_to_nodes: | |
| matches = self._obj_to_nodes[obj] | |
| nodes = nodes & matches | |
| if obj_name is not None: | |
| matches = set() | |
| if obj_name in self._obj_name_to_nodes: | |
| matches = self._obj_name_to_nodes[obj_name] | |
| nodes = nodes & matches | |
| return nodes | |
| def add_node(self, node, parent_node=None): | |
| """Add a Node to the scene. | |
| Parameters | |
| ---------- | |
| node : :class:`Node` | |
| The node to be added. | |
| parent_node : :class:`Node` | |
| The parent of this Node. If None, the new node is a root node. | |
| """ | |
| if node in self.nodes: | |
| raise ValueError('Node already in scene') | |
| self.nodes.add(node) | |
| # Add node to sets | |
| if node.name is not None: | |
| if node.name not in self._name_to_nodes: | |
| self._name_to_nodes[node.name] = set() | |
| self._name_to_nodes[node.name].add(node) | |
| for obj in [node.mesh, node.camera, node.light]: | |
| if obj is not None: | |
| if obj not in self._obj_to_nodes: | |
| self._obj_to_nodes[obj] = set() | |
| self._obj_to_nodes[obj].add(node) | |
| if obj.name is not None: | |
| if obj.name not in self._obj_name_to_nodes: | |
| self._obj_name_to_nodes[obj.name] = set() | |
| self._obj_name_to_nodes[obj.name].add(node) | |
| if node.mesh is not None: | |
| self._mesh_nodes.add(node) | |
| if node.light is not None: | |
| if isinstance(node.light, PointLight): | |
| self._point_light_nodes.add(node) | |
| if isinstance(node.light, SpotLight): | |
| self._spot_light_nodes.add(node) | |
| if isinstance(node.light, DirectionalLight): | |
| self._directional_light_nodes.add(node) | |
| if node.camera is not None: | |
| self._camera_nodes.add(node) | |
| if self._main_camera_node is None: | |
| self._main_camera_node = node | |
| if parent_node is None: | |
| parent_node = 'world' | |
| elif parent_node not in self.nodes: | |
| raise ValueError('Parent node must already be in scene') | |
| elif node not in parent_node.children: | |
| parent_node.children.append(node) | |
| # Create node in graph | |
| self._digraph.add_node(node) | |
| self._digraph.add_edge(node, parent_node) | |
| # Iterate over children | |
| for child in node.children: | |
| self.add_node(child, node) | |
| self._path_cache = {} | |
| self._bounds = None | |
| def has_node(self, node): | |
| """Check if a node is already in the scene. | |
| Parameters | |
| ---------- | |
| node : :class:`Node` | |
| The node to be checked. | |
| Returns | |
| ------- | |
| has_node : bool | |
| True if the node is already in the scene and false otherwise. | |
| """ | |
| return node in self.nodes | |
| def remove_node(self, node): | |
| """Remove a node and all its children from the scene. | |
| Parameters | |
| ---------- | |
| node : :class:`Node` | |
| The node to be removed. | |
| """ | |
| # Disconnect self from parent who is staying in the graph | |
| parent = list(self._digraph.neighbors(node))[0] | |
| self._remove_node(node) | |
| if isinstance(parent, Node): | |
| parent.children.remove(node) | |
| self._path_cache = {} | |
| self._bounds = None | |
| def get_pose(self, node): | |
| """Get the world-frame pose of a node in the scene. | |
| Parameters | |
| ---------- | |
| node : :class:`Node` | |
| The node to find the pose of. | |
| Returns | |
| ------- | |
| pose : (4,4) float | |
| The transform matrix for this node. | |
| """ | |
| if node not in self.nodes: | |
| raise ValueError('Node must already be in scene') | |
| if node in self._path_cache: | |
| path = self._path_cache[node] | |
| else: | |
| # Get path from from_frame to to_frame | |
| path = nx.shortest_path(self._digraph, node, 'world') | |
| self._path_cache[node] = path | |
| # Traverse from from_node to to_node | |
| pose = np.eye(4) | |
| for n in path[:-1]: | |
| pose = np.dot(n.matrix, pose) | |
| return pose | |
| def set_pose(self, node, pose): | |
| """Set the local-frame pose of a node in the scene. | |
| Parameters | |
| ---------- | |
| node : :class:`Node` | |
| The node to set the pose of. | |
| pose : (4,4) float | |
| The pose to set the node to. | |
| """ | |
| if node not in self.nodes: | |
| raise ValueError('Node must already be in scene') | |
| node._matrix = pose | |
| if node.mesh is not None: | |
| self._bounds = None | |
| def clear(self): | |
| """Clear out all nodes to form an empty scene. | |
| """ | |
| self._nodes = set() | |
| self._name_to_nodes = {} | |
| self._obj_to_nodes = {} | |
| self._obj_name_to_nodes = {} | |
| self._mesh_nodes = set() | |
| self._point_light_nodes = set() | |
| self._spot_light_nodes = set() | |
| self._directional_light_nodes = set() | |
| self._camera_nodes = set() | |
| self._main_camera_node = None | |
| self._bounds = None | |
| # Transform tree | |
| self._digraph = nx.DiGraph() | |
| self._digraph.add_node('world') | |
| self._path_cache = {} | |
| def _remove_node(self, node): | |
| """Remove a node and all its children from the scene. | |
| Parameters | |
| ---------- | |
| node : :class:`Node` | |
| The node to be removed. | |
| """ | |
| # Remove self from nodes | |
| self.nodes.remove(node) | |
| # Remove children | |
| for child in node.children: | |
| self._remove_node(child) | |
| # Remove self from the graph | |
| self._digraph.remove_node(node) | |
| # Remove from maps | |
| if node.name in self._name_to_nodes: | |
| self._name_to_nodes[node.name].remove(node) | |
| if len(self._name_to_nodes[node.name]) == 0: | |
| self._name_to_nodes.pop(node.name) | |
| for obj in [node.mesh, node.camera, node.light]: | |
| if obj is None: | |
| continue | |
| self._obj_to_nodes[obj].remove(node) | |
| if len(self._obj_to_nodes[obj]) == 0: | |
| self._obj_to_nodes.pop(obj) | |
| if obj.name is not None: | |
| self._obj_name_to_nodes[obj.name].remove(node) | |
| if len(self._obj_name_to_nodes[obj.name]) == 0: | |
| self._obj_name_to_nodes.pop(obj.name) | |
| if node.mesh is not None: | |
| self._mesh_nodes.remove(node) | |
| if node.light is not None: | |
| if isinstance(node.light, PointLight): | |
| self._point_light_nodes.remove(node) | |
| if isinstance(node.light, SpotLight): | |
| self._spot_light_nodes.remove(node) | |
| if isinstance(node.light, DirectionalLight): | |
| self._directional_light_nodes.remove(node) | |
| if node.camera is not None: | |
| self._camera_nodes.remove(node) | |
| if self._main_camera_node == node: | |
| if len(self._camera_nodes) > 0: | |
| self._main_camera_node = next(iter(self._camera_nodes)) | |
| else: | |
| self._main_camera_node = None | |
| def from_trimesh_scene(trimesh_scene, | |
| bg_color=None, ambient_light=None): | |
| """Create a :class:`.Scene` from a :class:`trimesh.scene.scene.Scene`. | |
| Parameters | |
| ---------- | |
| trimesh_scene : :class:`trimesh.scene.scene.Scene` | |
| Scene with :class:~`trimesh.base.Trimesh` objects. | |
| bg_color : (4,) float | |
| Background color for the created scene. | |
| ambient_light : (3,) float or None | |
| Ambient light in the scene. | |
| Returns | |
| ------- | |
| scene_pr : :class:`Scene` | |
| A scene containing the same geometry as the trimesh scene. | |
| """ | |
| # convert trimesh geometries to pyrender geometries | |
| geometries = {name: Mesh.from_trimesh(geom) | |
| for name, geom in trimesh_scene.geometry.items()} | |
| # create the pyrender scene object | |
| scene_pr = Scene(bg_color=bg_color, ambient_light=ambient_light) | |
| # add every node with geometry to the pyrender scene | |
| for node in trimesh_scene.graph.nodes_geometry: | |
| pose, geom_name = trimesh_scene.graph[node] | |
| scene_pr.add(geometries[geom_name], pose=pose) | |
| return scene_pr | |