Team Ai
Apppublic

OpenMotionLab/MotionGPT

sourceHugging Facemitupdated 1y agoView on Hugging Face
118likes
scene.py586 linesDownload Raw Back to pyrender
1"""Scenes, conforming to the glTF 2.0 standards as specified in2https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-scene3 4Author: Matthew Matl5"""6import numpy as np7import networkx as nx8import trimesh9 10from .mesh import Mesh11from .camera import Camera12from .light import Light, PointLight, DirectionalLight, SpotLight13from .node import Node14from .utils import format_color_vector15 16 17class Scene(object):18    """A hierarchical scene graph.19 20    Parameters21    ----------22    nodes : list of :class:`Node`23        The set of all nodes in the scene.24    bg_color : (4,) float, optional25        Background color of scene.26    ambient_light : (3,) float, optional27        Color of ambient light. Defaults to no ambient light.28    name : str, optional29        The user-defined name of this object.30    """31 32    def __init__(self,33                 nodes=None,34                 bg_color=None,35                 ambient_light=None,36                 name=None):37 38        if bg_color is None:39            bg_color = np.ones(4)40        else:41            bg_color = format_color_vector(bg_color, 4)42 43        if ambient_light is None:44            ambient_light = np.zeros(3)45 46        if nodes is None:47            nodes = set()48        self._nodes = set()  # Will be added at the end of this function49 50        self.bg_color = bg_color51        self.ambient_light = ambient_light52        self.name = name53 54        self._name_to_nodes = {}55        self._obj_to_nodes = {}56        self._obj_name_to_nodes = {}57        self._mesh_nodes = set()58        self._point_light_nodes = set()59        self._spot_light_nodes = set()60        self._directional_light_nodes = set()61        self._camera_nodes = set()62        self._main_camera_node = None63        self._bounds = None64 65        # Transform tree66        self._digraph = nx.DiGraph()67        self._digraph.add_node('world')68        self._path_cache = {}69 70        # Find root nodes and add them71        if len(nodes) > 0:72            node_parent_map = {n: None for n in nodes}73            for node in nodes:74                for child in node.children:75                    if node_parent_map[child] is not None:76                        raise ValueError('Nodes may not have more than '77                                         'one parent')78                    node_parent_map[child] = node79            for node in node_parent_map:80                if node_parent_map[node] is None:81                    self.add_node(node)82 83    @property84    def name(self):85        """str : The user-defined name of this object.86        """87        return self._name88 89    @name.setter90    def name(self, value):91        if value is not None:92            value = str(value)93        self._name = value94 95    @property96    def nodes(self):97        """set of :class:`Node` : Set of nodes in the scene.98        """99        return self._nodes100 101    @property102    def bg_color(self):103        """(3,) float : The scene background color.104        """105        return self._bg_color106 107    @bg_color.setter108    def bg_color(self, value):109        if value is None:110            value = np.ones(4)111        else:112            value = format_color_vector(value, 4)113        self._bg_color = value114 115    @property116    def ambient_light(self):117        """(3,) float : The ambient light in the scene.118        """119        return self._ambient_light120 121    @ambient_light.setter122    def ambient_light(self, value):123        if value is None:124            value = np.zeros(3)125        else:126            value = format_color_vector(value, 3)127        self._ambient_light = value128 129    @property130    def meshes(self):131        """set of :class:`Mesh` : The meshes in the scene.132        """133        return set([n.mesh for n in self.mesh_nodes])134 135    @property136    def mesh_nodes(self):137        """set of :class:`Node` : The nodes containing meshes.138        """139        return self._mesh_nodes140 141    @property142    def lights(self):143        """set of :class:`Light` : The lights in the scene.144        """145        return self.point_lights | self.spot_lights | self.directional_lights146 147    @property148    def light_nodes(self):149        """set of :class:`Node` : The nodes containing lights.150        """151        return (self.point_light_nodes | self.spot_light_nodes |152                self.directional_light_nodes)153 154    @property155    def point_lights(self):156        """set of :class:`PointLight` : The point lights in the scene.157        """158        return set([n.light for n in self.point_light_nodes])159 160    @property161    def point_light_nodes(self):162        """set of :class:`Node` : The nodes containing point lights.163        """164        return self._point_light_nodes165 166    @property167    def spot_lights(self):168        """set of :class:`SpotLight` : The spot lights in the scene.169        """170        return set([n.light for n in self.spot_light_nodes])171 172    @property173    def spot_light_nodes(self):174        """set of :class:`Node` : The nodes containing spot lights.175        """176        return self._spot_light_nodes177 178    @property179    def directional_lights(self):180        """set of :class:`DirectionalLight` : The directional lights in181        the scene.182        """183        return set([n.light for n in self.directional_light_nodes])184 185    @property186    def directional_light_nodes(self):187        """set of :class:`Node` : The nodes containing directional lights.188        """189        return self._directional_light_nodes190 191    @property192    def cameras(self):193        """set of :class:`Camera` : The cameras in the scene.194        """195        return set([n.camera for n in self.camera_nodes])196 197    @property198    def camera_nodes(self):199        """set of :class:`Node` : The nodes containing cameras in the scene.200        """201        return self._camera_nodes202 203    @property204    def main_camera_node(self):205        """set of :class:`Node` : The node containing the main camera in the206        scene.207        """208        return self._main_camera_node209 210    @main_camera_node.setter211    def main_camera_node(self, value):212        if value not in self.nodes:213            raise ValueError('New main camera node must already be in scene')214        self._main_camera_node = value215 216    @property217    def bounds(self):218        """(2,3) float : The axis-aligned bounds of the scene.219        """220        if self._bounds is None:221            # Compute corners222            corners = []223            for mesh_node in self.mesh_nodes:224                mesh = mesh_node.mesh225                pose = self.get_pose(mesh_node)226                corners_local = trimesh.bounds.corners(mesh.bounds)227                corners_world = pose[:3,:3].dot(corners_local.T).T + pose[:3,3]228                corners.append(corners_world)229            if len(corners) == 0:230                self._bounds = np.zeros((2,3))231            else:232                corners = np.vstack(corners)233                self._bounds = np.array([np.min(corners, axis=0),234                                         np.max(corners, axis=0)])235        return self._bounds236 237    @property238    def centroid(self):239        """(3,) float : The centroid of the scene's axis-aligned bounding box240        (AABB).241        """242        return np.mean(self.bounds, axis=0)243 244    @property245    def extents(self):246        """(3,) float : The lengths of the axes of the scene's AABB.247        """248        return np.diff(self.bounds, axis=0).reshape(-1)249 250    @property251    def scale(self):252        """(3,) float : The length of the diagonal of the scene's AABB.253        """254        return np.linalg.norm(self.extents)255 256    def add(self, obj, name=None, pose=None,257            parent_node=None, parent_name=None):258        """Add an object (mesh, light, or camera) to the scene.259 260        Parameters261        ----------262        obj : :class:`Mesh`, :class:`Light`, or :class:`Camera`263            The object to add to the scene.264        name : str265            A name for the new node to be created.266        pose : (4,4) float267            The local pose of this node relative to its parent node.268        parent_node : :class:`Node`269            The parent of this Node. If None, the new node is a root node.270        parent_name : str271            The name of the parent node, can be specified instead of272            `parent_node`.273 274        Returns275        -------276        node : :class:`Node`277            The newly-created and inserted node.278        """279        if isinstance(obj, Mesh):280            node = Node(name=name, matrix=pose, mesh=obj)281        elif isinstance(obj, Light):282            node = Node(name=name, matrix=pose, light=obj)283        elif isinstance(obj, Camera):284            node = Node(name=name, matrix=pose, camera=obj)285        else:286            raise TypeError('Unrecognized object type')287 288        if parent_node is None and parent_name is not None:289            parent_nodes = self.get_nodes(name=parent_name)290            if len(parent_nodes) == 0:291                raise ValueError('No parent node with name {} found'292                                 .format(parent_name))293            elif len(parent_nodes) > 1:294                raise ValueError('More than one parent node with name {} found'295                                 .format(parent_name))296            parent_node = list(parent_nodes)[0]297 298        self.add_node(node, parent_node=parent_node)299 300        return node301 302    def get_nodes(self, node=None, name=None, obj=None, obj_name=None):303        """Search for existing nodes. Only nodes matching all specified304        parameters is returned, or None if no such node exists.305 306        Parameters307        ----------308        node : :class:`Node`, optional309            If present, returns this node if it is in the scene.310        name : str311            A name for the Node.312        obj : :class:`Mesh`, :class:`Light`, or :class:`Camera`313            An object that is attached to the node.314        obj_name : str315            The name of an object that is attached to the node.316 317        Returns318        -------319        nodes : set of :class:`.Node`320            The nodes that match all query terms.321        """322        if node is not None:323            if node in self.nodes:324                return set([node])325            else:326                return set()327        nodes = set(self.nodes)328        if name is not None:329            matches = set()330            if name in self._name_to_nodes:331                matches = self._name_to_nodes[name]332            nodes = nodes & matches333        if obj is not None:334            matches = set()335            if obj in self._obj_to_nodes:336                matches = self._obj_to_nodes[obj]337            nodes = nodes & matches338        if obj_name is not None:339            matches = set()340            if obj_name in self._obj_name_to_nodes:341                matches = self._obj_name_to_nodes[obj_name]342            nodes = nodes & matches343 344        return nodes345 346    def add_node(self, node, parent_node=None):347        """Add a Node to the scene.348 349        Parameters350        ----------351        node : :class:`Node`352            The node to be added.353        parent_node : :class:`Node`354            The parent of this Node. If None, the new node is a root node.355        """356        if node in self.nodes:357            raise ValueError('Node already in scene')358        self.nodes.add(node)359 360        # Add node to sets361        if node.name is not None:362            if node.name not in self._name_to_nodes:363                self._name_to_nodes[node.name] = set()364            self._name_to_nodes[node.name].add(node)365        for obj in [node.mesh, node.camera, node.light]:366            if obj is not None:367                if obj not in self._obj_to_nodes:368                    self._obj_to_nodes[obj] = set()369                self._obj_to_nodes[obj].add(node)370                if obj.name is not None:371                    if obj.name not in self._obj_name_to_nodes:372                        self._obj_name_to_nodes[obj.name] = set()373                    self._obj_name_to_nodes[obj.name].add(node)374        if node.mesh is not None:375            self._mesh_nodes.add(node)376        if node.light is not None:377            if isinstance(node.light, PointLight):378                self._point_light_nodes.add(node)379            if isinstance(node.light, SpotLight):380                self._spot_light_nodes.add(node)381            if isinstance(node.light, DirectionalLight):382                self._directional_light_nodes.add(node)383        if node.camera is not None:384            self._camera_nodes.add(node)385            if self._main_camera_node is None:386                self._main_camera_node = node387 388        if parent_node is None:389            parent_node = 'world'390        elif parent_node not in self.nodes:391            raise ValueError('Parent node must already be in scene')392        elif node not in parent_node.children:393            parent_node.children.append(node)394 395        # Create node in graph396        self._digraph.add_node(node)397        self._digraph.add_edge(node, parent_node)398 399        # Iterate over children400        for child in node.children:401            self.add_node(child, node)402 403        self._path_cache = {}404        self._bounds = None405 406    def has_node(self, node):407        """Check if a node is already in the scene.408 409        Parameters410        ----------411        node : :class:`Node`412            The node to be checked.413 414        Returns415        -------416        has_node : bool417            True if the node is already in the scene and false otherwise.418        """419        return node in self.nodes420 421    def remove_node(self, node):422        """Remove a node and all its children from the scene.423 424        Parameters425        ----------426        node : :class:`Node`427            The node to be removed.428        """429        # Disconnect self from parent who is staying in the graph430        parent = list(self._digraph.neighbors(node))[0]431        self._remove_node(node)432        if isinstance(parent, Node):433            parent.children.remove(node)434        self._path_cache = {}435        self._bounds = None436 437    def get_pose(self, node):438        """Get the world-frame pose of a node in the scene.439 440        Parameters441        ----------442        node : :class:`Node`443            The node to find the pose of.444 445        Returns446        -------447        pose : (4,4) float448            The transform matrix for this node.449        """450        if node not in self.nodes:451            raise ValueError('Node must already be in scene')452        if node in self._path_cache:453            path = self._path_cache[node]454        else:455            # Get path from from_frame to to_frame456            path = nx.shortest_path(self._digraph, node, 'world')457            self._path_cache[node] = path458 459        # Traverse from from_node to to_node460        pose = np.eye(4)461        for n in path[:-1]:462            pose = np.dot(n.matrix, pose)463 464        return pose465 466    def set_pose(self, node, pose):467        """Set the local-frame pose of a node in the scene.468 469        Parameters470        ----------471        node : :class:`Node`472            The node to set the pose of.473        pose : (4,4) float474            The pose to set the node to.475        """476        if node not in self.nodes:477            raise ValueError('Node must already be in scene')478        node._matrix = pose479        if node.mesh is not None:480            self._bounds = None481 482    def clear(self):483        """Clear out all nodes to form an empty scene.484        """485        self._nodes = set()486 487        self._name_to_nodes = {}488        self._obj_to_nodes = {}489        self._obj_name_to_nodes = {}490        self._mesh_nodes = set()491        self._point_light_nodes = set()492        self._spot_light_nodes = set()493        self._directional_light_nodes = set()494        self._camera_nodes = set()495        self._main_camera_node = None496        self._bounds = None497 498        # Transform tree499        self._digraph = nx.DiGraph()500        self._digraph.add_node('world')501        self._path_cache = {}502 503    def _remove_node(self, node):504        """Remove a node and all its children from the scene.505 506        Parameters507        ----------508        node : :class:`Node`509            The node to be removed.510        """511 512        # Remove self from nodes513        self.nodes.remove(node)514 515        # Remove children516        for child in node.children:517            self._remove_node(child)518 519        # Remove self from the graph520        self._digraph.remove_node(node)521 522        # Remove from maps523        if node.name in self._name_to_nodes:524            self._name_to_nodes[node.name].remove(node)525            if len(self._name_to_nodes[node.name]) == 0:526                self._name_to_nodes.pop(node.name)527        for obj in [node.mesh, node.camera, node.light]:528            if obj is None:529                continue530            self._obj_to_nodes[obj].remove(node)531            if len(self._obj_to_nodes[obj]) == 0:532                self._obj_to_nodes.pop(obj)533            if obj.name is not None:534                self._obj_name_to_nodes[obj.name].remove(node)535                if len(self._obj_name_to_nodes[obj.name]) == 0:536                    self._obj_name_to_nodes.pop(obj.name)537        if node.mesh is not None:538            self._mesh_nodes.remove(node)539        if node.light is not None:540            if isinstance(node.light, PointLight):541                self._point_light_nodes.remove(node)542            if isinstance(node.light, SpotLight):543                self._spot_light_nodes.remove(node)544            if isinstance(node.light, DirectionalLight):545                self._directional_light_nodes.remove(node)546        if node.camera is not None:547            self._camera_nodes.remove(node)548            if self._main_camera_node == node:549                if len(self._camera_nodes) > 0:550                    self._main_camera_node = next(iter(self._camera_nodes))551                else:552                    self._main_camera_node = None553 554    @staticmethod555    def from_trimesh_scene(trimesh_scene,556                           bg_color=None, ambient_light=None):557        """Create a :class:`.Scene` from a :class:`trimesh.scene.scene.Scene`.558 559        Parameters560        ----------561        trimesh_scene : :class:`trimesh.scene.scene.Scene`562            Scene with :class:~`trimesh.base.Trimesh` objects.563        bg_color : (4,) float564            Background color for the created scene.565        ambient_light : (3,) float or None566            Ambient light in the scene.567 568        Returns569        -------570        scene_pr : :class:`Scene`571            A scene containing the same geometry as the trimesh scene.572        """573        # convert trimesh geometries to pyrender geometries574        geometries = {name: Mesh.from_trimesh(geom)575                      for name, geom in trimesh_scene.geometry.items()}576 577        # create the pyrender scene object578        scene_pr = Scene(bg_color=bg_color, ambient_light=ambient_light)579 580        # add every node with geometry to the pyrender scene581        for node in trimesh_scene.graph.nodes_geometry:582            pose, geom_name = trimesh_scene.graph[node]583            scene_pr.add(geometries[geom_name], pose=pose)584 585        return scene_pr586