pyrender-maintained 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyrender/__init__.py +24 -0
- pyrender/camera.py +435 -0
- pyrender/constants.py +149 -0
- pyrender/font.py +272 -0
- pyrender/light.py +382 -0
- pyrender/material.py +705 -0
- pyrender/mesh.py +328 -0
- pyrender/node.py +263 -0
- pyrender/offscreen.py +160 -0
- pyrender/platforms/__init__.py +6 -0
- pyrender/platforms/base.py +73 -0
- pyrender/platforms/egl.py +219 -0
- pyrender/platforms/osmesa.py +59 -0
- pyrender/platforms/pyglet_platform.py +90 -0
- pyrender/primitive.py +489 -0
- pyrender/renderer.py +1328 -0
- pyrender/sampler.py +102 -0
- pyrender/scene.py +585 -0
- pyrender/shader_program.py +283 -0
- pyrender/texture.py +259 -0
- pyrender/trackball.py +216 -0
- pyrender/utils.py +115 -0
- pyrender/version.py +1 -0
- pyrender/viewer.py +1157 -0
- pyrender_maintained-1.0.0.dist-info/METADATA +55 -0
- pyrender_maintained-1.0.0.dist-info/RECORD +29 -0
- pyrender_maintained-1.0.0.dist-info/WHEEL +5 -0
- pyrender_maintained-1.0.0.dist-info/licenses/LICENSE +21 -0
- pyrender_maintained-1.0.0.dist-info/top_level.txt +1 -0
pyrender/sampler.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Samplers, conforming to the glTF 2.0 standards as specified in
|
|
2
|
+
https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-sampler
|
|
3
|
+
|
|
4
|
+
Author: Matthew Matl
|
|
5
|
+
"""
|
|
6
|
+
from .constants import GLTF
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Sampler(object):
|
|
10
|
+
"""Texture sampler properties for filtering and wrapping modes.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
name : str, optional
|
|
15
|
+
The user-defined name of this object.
|
|
16
|
+
magFilter : int, optional
|
|
17
|
+
Magnification filter. Valid values:
|
|
18
|
+
- :attr:`.GLTF.NEAREST`
|
|
19
|
+
- :attr:`.GLTF.LINEAR`
|
|
20
|
+
minFilter : int, optional
|
|
21
|
+
Minification filter. Valid values:
|
|
22
|
+
- :attr:`.GLTF.NEAREST`
|
|
23
|
+
- :attr:`.GLTF.LINEAR`
|
|
24
|
+
- :attr:`.GLTF.NEAREST_MIPMAP_NEAREST`
|
|
25
|
+
- :attr:`.GLTF.LINEAR_MIPMAP_NEAREST`
|
|
26
|
+
- :attr:`.GLTF.NEAREST_MIPMAP_LINEAR`
|
|
27
|
+
- :attr:`.GLTF.LINEAR_MIPMAP_LINEAR`
|
|
28
|
+
wrapS : int, optional
|
|
29
|
+
S (U) wrapping mode. Valid values:
|
|
30
|
+
- :attr:`.GLTF.CLAMP_TO_EDGE`
|
|
31
|
+
- :attr:`.GLTF.MIRRORED_REPEAT`
|
|
32
|
+
- :attr:`.GLTF.REPEAT`
|
|
33
|
+
wrapT : int, optional
|
|
34
|
+
T (V) wrapping mode. Valid values:
|
|
35
|
+
- :attr:`.GLTF.CLAMP_TO_EDGE`
|
|
36
|
+
- :attr:`.GLTF.MIRRORED_REPEAT`
|
|
37
|
+
- :attr:`.GLTF.REPEAT`
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self,
|
|
41
|
+
name=None,
|
|
42
|
+
magFilter=None,
|
|
43
|
+
minFilter=None,
|
|
44
|
+
wrapS=GLTF.REPEAT,
|
|
45
|
+
wrapT=GLTF.REPEAT):
|
|
46
|
+
self.name = name
|
|
47
|
+
self.magFilter = magFilter
|
|
48
|
+
self.minFilter = minFilter
|
|
49
|
+
self.wrapS = wrapS
|
|
50
|
+
self.wrapT = wrapT
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def name(self):
|
|
54
|
+
"""str : The user-defined name of this object.
|
|
55
|
+
"""
|
|
56
|
+
return self._name
|
|
57
|
+
|
|
58
|
+
@name.setter
|
|
59
|
+
def name(self, value):
|
|
60
|
+
if value is not None:
|
|
61
|
+
value = str(value)
|
|
62
|
+
self._name = value
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def magFilter(self):
|
|
66
|
+
"""int : Magnification filter type.
|
|
67
|
+
"""
|
|
68
|
+
return self._magFilter
|
|
69
|
+
|
|
70
|
+
@magFilter.setter
|
|
71
|
+
def magFilter(self, value):
|
|
72
|
+
self._magFilter = value
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def minFilter(self):
|
|
76
|
+
"""int : Minification filter type.
|
|
77
|
+
"""
|
|
78
|
+
return self._minFilter
|
|
79
|
+
|
|
80
|
+
@minFilter.setter
|
|
81
|
+
def minFilter(self, value):
|
|
82
|
+
self._minFilter = value
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def wrapS(self):
|
|
86
|
+
"""int : S (U) wrapping mode.
|
|
87
|
+
"""
|
|
88
|
+
return self._wrapS
|
|
89
|
+
|
|
90
|
+
@wrapS.setter
|
|
91
|
+
def wrapS(self, value):
|
|
92
|
+
self._wrapS = value
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def wrapT(self):
|
|
96
|
+
"""int : T (V) wrapping mode.
|
|
97
|
+
"""
|
|
98
|
+
return self._wrapT
|
|
99
|
+
|
|
100
|
+
@wrapT.setter
|
|
101
|
+
def wrapT(self, value):
|
|
102
|
+
self._wrapT = value
|
pyrender/scene.py
ADDED
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
"""Scenes, conforming to the glTF 2.0 standards as specified in
|
|
2
|
+
https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-scene
|
|
3
|
+
|
|
4
|
+
Author: Matthew Matl
|
|
5
|
+
"""
|
|
6
|
+
import numpy as np
|
|
7
|
+
import networkx as nx
|
|
8
|
+
import trimesh
|
|
9
|
+
|
|
10
|
+
from .mesh import Mesh
|
|
11
|
+
from .camera import Camera
|
|
12
|
+
from .light import Light, PointLight, DirectionalLight, SpotLight
|
|
13
|
+
from .node import Node
|
|
14
|
+
from .utils import format_color_vector
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Scene(object):
|
|
18
|
+
"""A hierarchical scene graph.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
nodes : list of :class:`Node`
|
|
23
|
+
The set of all nodes in the scene.
|
|
24
|
+
bg_color : (4,) float, optional
|
|
25
|
+
Background color of scene.
|
|
26
|
+
ambient_light : (3,) float, optional
|
|
27
|
+
Color of ambient light. Defaults to no ambient light.
|
|
28
|
+
name : str, optional
|
|
29
|
+
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 function
|
|
49
|
+
|
|
50
|
+
self.bg_color = bg_color
|
|
51
|
+
self.ambient_light = ambient_light
|
|
52
|
+
self.name = name
|
|
53
|
+
|
|
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 = None
|
|
63
|
+
self._bounds = None
|
|
64
|
+
|
|
65
|
+
# Transform tree
|
|
66
|
+
self._digraph = nx.DiGraph()
|
|
67
|
+
self._digraph.add_node('world')
|
|
68
|
+
self._path_cache = {}
|
|
69
|
+
|
|
70
|
+
# Find root nodes and add them
|
|
71
|
+
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] = node
|
|
79
|
+
for node in node_parent_map:
|
|
80
|
+
if node_parent_map[node] is None:
|
|
81
|
+
self.add_node(node)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def name(self):
|
|
85
|
+
"""str : The user-defined name of this object.
|
|
86
|
+
"""
|
|
87
|
+
return self._name
|
|
88
|
+
|
|
89
|
+
@name.setter
|
|
90
|
+
def name(self, value):
|
|
91
|
+
if value is not None:
|
|
92
|
+
value = str(value)
|
|
93
|
+
self._name = value
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def nodes(self):
|
|
97
|
+
"""set of :class:`Node` : Set of nodes in the scene.
|
|
98
|
+
"""
|
|
99
|
+
return self._nodes
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def bg_color(self):
|
|
103
|
+
"""(3,) float : The scene background color.
|
|
104
|
+
"""
|
|
105
|
+
return self._bg_color
|
|
106
|
+
|
|
107
|
+
@bg_color.setter
|
|
108
|
+
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 = value
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def ambient_light(self):
|
|
117
|
+
"""(3,) float : The ambient light in the scene.
|
|
118
|
+
"""
|
|
119
|
+
return self._ambient_light
|
|
120
|
+
|
|
121
|
+
@ambient_light.setter
|
|
122
|
+
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 = value
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
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
|
+
@property
|
|
136
|
+
def mesh_nodes(self):
|
|
137
|
+
"""set of :class:`Node` : The nodes containing meshes.
|
|
138
|
+
"""
|
|
139
|
+
return self._mesh_nodes
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def lights(self):
|
|
143
|
+
"""set of :class:`Light` : The lights in the scene.
|
|
144
|
+
"""
|
|
145
|
+
return self.point_lights | self.spot_lights | self.directional_lights
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
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
|
+
@property
|
|
155
|
+
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
|
+
@property
|
|
161
|
+
def point_light_nodes(self):
|
|
162
|
+
"""set of :class:`Node` : The nodes containing point lights.
|
|
163
|
+
"""
|
|
164
|
+
return self._point_light_nodes
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
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
|
+
@property
|
|
173
|
+
def spot_light_nodes(self):
|
|
174
|
+
"""set of :class:`Node` : The nodes containing spot lights.
|
|
175
|
+
"""
|
|
176
|
+
return self._spot_light_nodes
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def directional_lights(self):
|
|
180
|
+
"""set of :class:`DirectionalLight` : The directional lights in
|
|
181
|
+
the scene.
|
|
182
|
+
"""
|
|
183
|
+
return set([n.light for n in self.directional_light_nodes])
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def directional_light_nodes(self):
|
|
187
|
+
"""set of :class:`Node` : The nodes containing directional lights.
|
|
188
|
+
"""
|
|
189
|
+
return self._directional_light_nodes
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
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
|
+
@property
|
|
198
|
+
def camera_nodes(self):
|
|
199
|
+
"""set of :class:`Node` : The nodes containing cameras in the scene.
|
|
200
|
+
"""
|
|
201
|
+
return self._camera_nodes
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def main_camera_node(self):
|
|
205
|
+
"""set of :class:`Node` : The node containing the main camera in the
|
|
206
|
+
scene.
|
|
207
|
+
"""
|
|
208
|
+
return self._main_camera_node
|
|
209
|
+
|
|
210
|
+
@main_camera_node.setter
|
|
211
|
+
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 = value
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def bounds(self):
|
|
218
|
+
"""(2,3) float : The axis-aligned bounds of the scene.
|
|
219
|
+
"""
|
|
220
|
+
if self._bounds is None:
|
|
221
|
+
# Compute corners
|
|
222
|
+
corners = []
|
|
223
|
+
for mesh_node in self.mesh_nodes:
|
|
224
|
+
mesh = mesh_node.mesh
|
|
225
|
+
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._bounds
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def centroid(self):
|
|
239
|
+
"""(3,) float : The centroid of the scene's axis-aligned bounding box
|
|
240
|
+
(AABB).
|
|
241
|
+
"""
|
|
242
|
+
return np.mean(self.bounds, axis=0)
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
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
|
+
@property
|
|
251
|
+
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
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
obj : :class:`Mesh`, :class:`Light`, or :class:`Camera`
|
|
263
|
+
The object to add to the scene.
|
|
264
|
+
name : str
|
|
265
|
+
A name for the new node to be created.
|
|
266
|
+
pose : (4,4) float
|
|
267
|
+
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 : str
|
|
271
|
+
The name of the parent node, can be specified instead of
|
|
272
|
+
`parent_node`.
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
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 node
|
|
301
|
+
|
|
302
|
+
def get_nodes(self, node=None, name=None, obj=None, obj_name=None):
|
|
303
|
+
"""Search for existing nodes. Only nodes matching all specified
|
|
304
|
+
parameters is returned, or None if no such node exists.
|
|
305
|
+
|
|
306
|
+
Parameters
|
|
307
|
+
----------
|
|
308
|
+
node : :class:`Node`, optional
|
|
309
|
+
If present, returns this node if it is in the scene.
|
|
310
|
+
name : str
|
|
311
|
+
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 : str
|
|
315
|
+
The name of an object that is attached to the node.
|
|
316
|
+
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
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 & matches
|
|
333
|
+
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 & matches
|
|
338
|
+
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 & matches
|
|
343
|
+
|
|
344
|
+
return nodes
|
|
345
|
+
|
|
346
|
+
def add_node(self, node, parent_node=None):
|
|
347
|
+
"""Add a Node to the scene.
|
|
348
|
+
|
|
349
|
+
Parameters
|
|
350
|
+
----------
|
|
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 sets
|
|
361
|
+
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 = node
|
|
387
|
+
|
|
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 graph
|
|
396
|
+
self._digraph.add_node(node)
|
|
397
|
+
self._digraph.add_edge(node, parent_node)
|
|
398
|
+
|
|
399
|
+
# Iterate over children
|
|
400
|
+
for child in node.children:
|
|
401
|
+
self.add_node(child, node)
|
|
402
|
+
|
|
403
|
+
self._path_cache = {}
|
|
404
|
+
self._bounds = None
|
|
405
|
+
|
|
406
|
+
def has_node(self, node):
|
|
407
|
+
"""Check if a node is already in the scene.
|
|
408
|
+
|
|
409
|
+
Parameters
|
|
410
|
+
----------
|
|
411
|
+
node : :class:`Node`
|
|
412
|
+
The node to be checked.
|
|
413
|
+
|
|
414
|
+
Returns
|
|
415
|
+
-------
|
|
416
|
+
has_node : bool
|
|
417
|
+
True if the node is already in the scene and false otherwise.
|
|
418
|
+
"""
|
|
419
|
+
return node in self.nodes
|
|
420
|
+
|
|
421
|
+
def remove_node(self, node):
|
|
422
|
+
"""Remove a node and all its children from the scene.
|
|
423
|
+
|
|
424
|
+
Parameters
|
|
425
|
+
----------
|
|
426
|
+
node : :class:`Node`
|
|
427
|
+
The node to be removed.
|
|
428
|
+
"""
|
|
429
|
+
# Disconnect self from parent who is staying in the graph
|
|
430
|
+
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 = None
|
|
436
|
+
|
|
437
|
+
def get_pose(self, node):
|
|
438
|
+
"""Get the world-frame pose of a node in the scene.
|
|
439
|
+
|
|
440
|
+
Parameters
|
|
441
|
+
----------
|
|
442
|
+
node : :class:`Node`
|
|
443
|
+
The node to find the pose of.
|
|
444
|
+
|
|
445
|
+
Returns
|
|
446
|
+
-------
|
|
447
|
+
pose : (4,4) float
|
|
448
|
+
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_frame
|
|
456
|
+
path = nx.shortest_path(self._digraph, node, 'world')
|
|
457
|
+
self._path_cache[node] = path
|
|
458
|
+
|
|
459
|
+
# Traverse from from_node to to_node
|
|
460
|
+
pose = np.eye(4)
|
|
461
|
+
for n in path[:-1]:
|
|
462
|
+
pose = np.dot(n.matrix, pose)
|
|
463
|
+
|
|
464
|
+
return pose
|
|
465
|
+
|
|
466
|
+
def set_pose(self, node, pose):
|
|
467
|
+
"""Set the local-frame pose of a node in the scene.
|
|
468
|
+
|
|
469
|
+
Parameters
|
|
470
|
+
----------
|
|
471
|
+
node : :class:`Node`
|
|
472
|
+
The node to set the pose of.
|
|
473
|
+
pose : (4,4) float
|
|
474
|
+
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 = pose
|
|
479
|
+
if node.mesh is not None:
|
|
480
|
+
self._bounds = None
|
|
481
|
+
|
|
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 = None
|
|
496
|
+
self._bounds = None
|
|
497
|
+
|
|
498
|
+
# Transform tree
|
|
499
|
+
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
|
+
Parameters
|
|
507
|
+
----------
|
|
508
|
+
node : :class:`Node`
|
|
509
|
+
The node to be removed.
|
|
510
|
+
"""
|
|
511
|
+
|
|
512
|
+
# Remove self from nodes
|
|
513
|
+
self.nodes.remove(node)
|
|
514
|
+
|
|
515
|
+
# Remove children
|
|
516
|
+
for child in node.children:
|
|
517
|
+
self._remove_node(child)
|
|
518
|
+
|
|
519
|
+
# Remove self from the graph
|
|
520
|
+
self._digraph.remove_node(node)
|
|
521
|
+
|
|
522
|
+
# Remove from maps
|
|
523
|
+
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
|
+
continue
|
|
530
|
+
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 = None
|
|
553
|
+
|
|
554
|
+
@staticmethod
|
|
555
|
+
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
|
+
Parameters
|
|
560
|
+
----------
|
|
561
|
+
trimesh_scene : :class:`trimesh.scene.scene.Scene`
|
|
562
|
+
Scene with :class:~`trimesh.base.Trimesh` objects.
|
|
563
|
+
bg_color : (4,) float
|
|
564
|
+
Background color for the created scene.
|
|
565
|
+
ambient_light : (3,) float or None
|
|
566
|
+
Ambient light in the scene.
|
|
567
|
+
|
|
568
|
+
Returns
|
|
569
|
+
-------
|
|
570
|
+
scene_pr : :class:`Scene`
|
|
571
|
+
A scene containing the same geometry as the trimesh scene.
|
|
572
|
+
"""
|
|
573
|
+
# convert trimesh geometries to pyrender geometries
|
|
574
|
+
geometries = {name: Mesh.from_trimesh(geom)
|
|
575
|
+
for name, geom in trimesh_scene.geometry.items()}
|
|
576
|
+
|
|
577
|
+
# create the pyrender scene object
|
|
578
|
+
scene_pr = Scene(bg_color=bg_color, ambient_light=ambient_light)
|
|
579
|
+
|
|
580
|
+
# add every node with geometry to the pyrender scene
|
|
581
|
+
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_pr
|