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/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