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/mesh.py ADDED
@@ -0,0 +1,328 @@
1
+ """Meshes, conforming to the glTF 2.0 standards as specified in
2
+ https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-mesh
3
+
4
+ Author: Matthew Matl
5
+ """
6
+ import copy
7
+
8
+ import numpy as np
9
+ import trimesh
10
+
11
+ from .primitive import Primitive
12
+ from .constants import GLTF
13
+ from .material import MetallicRoughnessMaterial
14
+
15
+
16
+ class Mesh(object):
17
+ """A set of primitives to be rendered.
18
+
19
+ Parameters
20
+ ----------
21
+ name : str
22
+ The user-defined name of this object.
23
+ primitives : list of :class:`Primitive`
24
+ The primitives associated with this mesh.
25
+ weights : (k,) float
26
+ Array of weights to be applied to the Morph Targets.
27
+ is_visible : bool
28
+ If False, the mesh will not be rendered.
29
+ """
30
+
31
+ def __init__(self, primitives, name=None, weights=None, is_visible=True):
32
+ self.primitives = primitives
33
+ self.name = name
34
+ self.weights = weights
35
+ self.is_visible = is_visible
36
+
37
+ self._bounds = None
38
+
39
+ @property
40
+ def name(self):
41
+ """str : The user-defined name of this object.
42
+ """
43
+ return self._name
44
+
45
+ @name.setter
46
+ def name(self, value):
47
+ if value is not None:
48
+ value = str(value)
49
+ self._name = value
50
+
51
+ @property
52
+ def primitives(self):
53
+ """list of :class:`Primitive` : The primitives associated
54
+ with this mesh.
55
+ """
56
+ return self._primitives
57
+
58
+ @primitives.setter
59
+ def primitives(self, value):
60
+ self._primitives = value
61
+
62
+ @property
63
+ def weights(self):
64
+ """(k,) float : Weights to be applied to morph targets.
65
+ """
66
+ return self._weights
67
+
68
+ @weights.setter
69
+ def weights(self, value):
70
+ self._weights = value
71
+
72
+ @property
73
+ def is_visible(self):
74
+ """bool : Whether the mesh is visible.
75
+ """
76
+ return self._is_visible
77
+
78
+ @is_visible.setter
79
+ def is_visible(self, value):
80
+ self._is_visible = value
81
+
82
+ @property
83
+ def bounds(self):
84
+ """(2,3) float : The axis-aligned bounds of the mesh.
85
+ """
86
+ if self._bounds is None:
87
+ bounds = np.array([[np.inf, np.inf, np.inf],
88
+ [-np.inf, -np.inf, -np.inf]])
89
+ for p in self.primitives:
90
+ bounds[0] = np.minimum(bounds[0], p.bounds[0])
91
+ bounds[1] = np.maximum(bounds[1], p.bounds[1])
92
+ self._bounds = bounds
93
+ return self._bounds
94
+
95
+ @property
96
+ def centroid(self):
97
+ """(3,) float : The centroid of the mesh's axis-aligned bounding box
98
+ (AABB).
99
+ """
100
+ return np.mean(self.bounds, axis=0)
101
+
102
+ @property
103
+ def extents(self):
104
+ """(3,) float : The lengths of the axes of the mesh's AABB.
105
+ """
106
+ return np.diff(self.bounds, axis=0).reshape(-1)
107
+
108
+ @property
109
+ def scale(self):
110
+ """(3,) float : The length of the diagonal of the mesh's AABB.
111
+ """
112
+ return np.linalg.norm(self.extents)
113
+
114
+ @property
115
+ def is_transparent(self):
116
+ """bool : If True, the mesh is partially-transparent.
117
+ """
118
+ for p in self.primitives:
119
+ if p.is_transparent:
120
+ return True
121
+ return False
122
+
123
+ @staticmethod
124
+ def from_points(points, colors=None, normals=None,
125
+ is_visible=True, poses=None):
126
+ """Create a Mesh from a set of points.
127
+
128
+ Parameters
129
+ ----------
130
+ points : (n,3) float
131
+ The point positions.
132
+ colors : (n,3) or (n,4) float, optional
133
+ RGB or RGBA colors for each point.
134
+ normals : (n,3) float, optionals
135
+ The normal vectors for each point.
136
+ is_visible : bool
137
+ If False, the points will not be rendered.
138
+ poses : (x,4,4)
139
+ Array of 4x4 transformation matrices for instancing this object.
140
+
141
+ Returns
142
+ -------
143
+ mesh : :class:`Mesh`
144
+ The created mesh.
145
+ """
146
+ primitive = Primitive(
147
+ positions=points,
148
+ normals=normals,
149
+ color_0=colors,
150
+ mode=GLTF.POINTS,
151
+ poses=poses
152
+ )
153
+ mesh = Mesh(primitives=[primitive], is_visible=is_visible)
154
+ return mesh
155
+
156
+ @staticmethod
157
+ def from_trimesh(mesh, material=None, is_visible=True,
158
+ poses=None, wireframe=False, smooth=True):
159
+ """Create a Mesh from a :class:`~trimesh.base.Trimesh`.
160
+
161
+ Parameters
162
+ ----------
163
+ mesh : :class:`~trimesh.base.Trimesh` or list of them
164
+ A triangular mesh or a list of meshes.
165
+ material : :class:`Material`
166
+ The material of the object. Overrides any mesh material.
167
+ If not specified and the mesh has no material, a default material
168
+ will be used.
169
+ is_visible : bool
170
+ If False, the mesh will not be rendered.
171
+ poses : (n,4,4) float
172
+ Array of 4x4 transformation matrices for instancing this object.
173
+ wireframe : bool
174
+ If `True`, the mesh will be rendered as a wireframe object
175
+ smooth : bool
176
+ If `True`, the mesh will be rendered with interpolated vertex
177
+ normals. Otherwise, the mesh edges will stay sharp.
178
+
179
+ Returns
180
+ -------
181
+ mesh : :class:`Mesh`
182
+ The created mesh.
183
+ """
184
+
185
+ if isinstance(mesh, (list, tuple, set, np.ndarray)):
186
+ meshes = list(mesh)
187
+ elif isinstance(mesh, trimesh.Trimesh):
188
+ meshes = [mesh]
189
+ else:
190
+ raise TypeError('Expected a Trimesh or a list, got a {}'
191
+ .format(type(mesh)))
192
+
193
+ primitives = []
194
+ for m in meshes:
195
+ positions = None
196
+ normals = None
197
+ indices = None
198
+
199
+ # Compute positions, normals, and indices
200
+ if smooth:
201
+ positions = m.vertices.copy()
202
+ normals = m.vertex_normals.copy()
203
+ indices = m.faces.copy()
204
+ else:
205
+ positions = m.vertices[m.faces].reshape((3 * len(m.faces), 3))
206
+ normals = np.repeat(m.face_normals, 3, axis=0)
207
+
208
+ # Compute colors, texture coords, and material properties
209
+ color_0, texcoord_0, primitive_material = Mesh._get_trimesh_props(m, smooth=smooth, material=material)
210
+
211
+ # Override if material is given.
212
+ if material is not None:
213
+ #primitive_material = copy.copy(material)
214
+ primitive_material = copy.deepcopy(material) # TODO
215
+
216
+ if primitive_material is None:
217
+ # Replace material with default if needed
218
+ primitive_material = MetallicRoughnessMaterial(
219
+ alphaMode='BLEND',
220
+ baseColorFactor=[0.3, 0.3, 0.3, 1.0],
221
+ metallicFactor=0.2,
222
+ roughnessFactor=0.8
223
+ )
224
+
225
+ primitive_material.wireframe = wireframe
226
+
227
+ # Create the primitive
228
+ primitives.append(Primitive(
229
+ positions=positions,
230
+ normals=normals,
231
+ texcoord_0=texcoord_0,
232
+ color_0=color_0,
233
+ indices=indices,
234
+ material=primitive_material,
235
+ mode=GLTF.TRIANGLES,
236
+ poses=poses
237
+ ))
238
+
239
+ return Mesh(primitives=primitives, is_visible=is_visible)
240
+
241
+ @staticmethod
242
+ def _get_trimesh_props(mesh, smooth=False, material=None):
243
+ """Gets the vertex colors, texture coordinates, and material properties
244
+ from a :class:`~trimesh.base.Trimesh`.
245
+ """
246
+ colors = None
247
+ texcoords = None
248
+
249
+ # If the trimesh visual is undefined, return none for both
250
+ if not mesh.visual.defined:
251
+ return colors, texcoords, material
252
+
253
+ # Process vertex colors
254
+ if material is None:
255
+ if mesh.visual.kind == 'vertex':
256
+ vc = mesh.visual.vertex_colors.copy()
257
+ if smooth:
258
+ colors = vc
259
+ else:
260
+ colors = vc[mesh.faces].reshape(
261
+ (3 * len(mesh.faces), vc.shape[1])
262
+ )
263
+ material = MetallicRoughnessMaterial(
264
+ alphaMode='BLEND',
265
+ baseColorFactor=[1.0, 1.0, 1.0, 1.0],
266
+ metallicFactor=0.2,
267
+ roughnessFactor=0.8
268
+ )
269
+ # Process face colors
270
+ elif mesh.visual.kind == 'face':
271
+ if smooth:
272
+ raise ValueError('Cannot use face colors with a smooth mesh')
273
+ else:
274
+ colors = np.repeat(mesh.visual.face_colors, 3, axis=0)
275
+
276
+ material = MetallicRoughnessMaterial(
277
+ alphaMode='BLEND',
278
+ baseColorFactor=[1.0, 1.0, 1.0, 1.0],
279
+ metallicFactor=0.2,
280
+ roughnessFactor=0.8
281
+ )
282
+
283
+ # Process texture colors
284
+ if mesh.visual.kind == 'texture':
285
+ # Configure UV coordinates
286
+ if mesh.visual.uv is not None and len(mesh.visual.uv) != 0:
287
+ uv = mesh.visual.uv.copy()
288
+ if smooth:
289
+ texcoords = uv
290
+ else:
291
+ texcoords = uv[mesh.faces].reshape(
292
+ (3 * len(mesh.faces), uv.shape[1])
293
+ )
294
+
295
+ if material is None:
296
+ # Configure mesh material
297
+ mat = mesh.visual.material
298
+
299
+ if isinstance(mat, trimesh.visual.texture.PBRMaterial):
300
+ material = MetallicRoughnessMaterial(
301
+ normalTexture=mat.normalTexture,
302
+ occlusionTexture=mat.occlusionTexture,
303
+ emissiveTexture=mat.emissiveTexture,
304
+ emissiveFactor=mat.emissiveFactor,
305
+ alphaMode='BLEND',
306
+ baseColorFactor=mat.baseColorFactor,
307
+ baseColorTexture=mat.baseColorTexture,
308
+ metallicFactor=mat.metallicFactor,
309
+ roughnessFactor=mat.roughnessFactor,
310
+ metallicRoughnessTexture=mat.metallicRoughnessTexture,
311
+ doubleSided=mat.doubleSided,
312
+ alphaCutoff=mat.alphaCutoff
313
+ )
314
+ elif isinstance(mat, trimesh.visual.texture.SimpleMaterial):
315
+ glossiness = mat.kwargs.get('Ns', 1.0)
316
+ if isinstance(glossiness, list):
317
+ glossiness = float(glossiness[0])
318
+ roughness = (2 / (glossiness + 2)) ** (1.0 / 4.0)
319
+ material = MetallicRoughnessMaterial(
320
+ alphaMode='BLEND',
321
+ roughnessFactor=roughness,
322
+ baseColorFactor=mat.diffuse,
323
+ baseColorTexture=mat.image,
324
+ )
325
+ elif isinstance(mat, MetallicRoughnessMaterial):
326
+ material = mat
327
+
328
+ return colors, texcoords, material
pyrender/node.py ADDED
@@ -0,0 +1,263 @@
1
+ """Nodes, conforming to the glTF 2.0 standards as specified in
2
+ https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-node
3
+
4
+ Author: Matthew Matl
5
+ """
6
+ import numpy as np
7
+
8
+ import trimesh.transformations as transformations
9
+
10
+ from .camera import Camera
11
+ from .mesh import Mesh
12
+ from .light import Light
13
+
14
+
15
+ class Node(object):
16
+ """A node in the node hierarchy.
17
+
18
+ Parameters
19
+ ----------
20
+ name : str, optional
21
+ The user-defined name of this object.
22
+ camera : :class:`Camera`, optional
23
+ The camera in this node.
24
+ children : list of :class:`Node`
25
+ The children of this node.
26
+ skin : int, optional
27
+ The index of the skin referenced by this node.
28
+ matrix : (4,4) float, optional
29
+ A floating-point 4x4 transformation matrix.
30
+ mesh : :class:`Mesh`, optional
31
+ The mesh in this node.
32
+ rotation : (4,) float, optional
33
+ The node's unit quaternion in the order (x, y, z, w), where
34
+ w is the scalar.
35
+ scale : (3,) float, optional
36
+ The node's non-uniform scale, given as the scaling factors along the x,
37
+ y, and z axes.
38
+ translation : (3,) float, optional
39
+ The node's translation along the x, y, and z axes.
40
+ weights : (n,) float
41
+ The weights of the instantiated Morph Target. Number of elements must
42
+ match number of Morph Targets of used mesh.
43
+ light : :class:`Light`, optional
44
+ The light in this node.
45
+ """
46
+
47
+ def __init__(self,
48
+ name=None,
49
+ camera=None,
50
+ children=None,
51
+ skin=None,
52
+ matrix=None,
53
+ mesh=None,
54
+ rotation=None,
55
+ scale=None,
56
+ translation=None,
57
+ weights=None,
58
+ light=None):
59
+ # Set defaults
60
+ if children is None:
61
+ children = []
62
+
63
+ self._matrix = None
64
+ self._scale = None
65
+ self._rotation = None
66
+ self._translation = None
67
+ if matrix is None:
68
+ if rotation is None:
69
+ rotation = np.array([0.0, 0.0, 0.0, 1.0])
70
+ if translation is None:
71
+ translation = np.zeros(3)
72
+ if scale is None:
73
+ scale = np.ones(3)
74
+ self.rotation = rotation
75
+ self.translation = translation
76
+ self.scale = scale
77
+ else:
78
+ self.matrix = matrix
79
+
80
+ self.name = name
81
+ self.camera = camera
82
+ self.children = children
83
+ self.skin = skin
84
+ self.mesh = mesh
85
+ self.weights = weights
86
+ self.light = light
87
+
88
+ @property
89
+ def name(self):
90
+ """str : The user-defined name of this object.
91
+ """
92
+ return self._name
93
+
94
+ @name.setter
95
+ def name(self, value):
96
+ if value is not None:
97
+ value = str(value)
98
+ self._name = value
99
+
100
+ @property
101
+ def camera(self):
102
+ """:class:`Camera` : The camera in this node.
103
+ """
104
+ return self._camera
105
+
106
+ @camera.setter
107
+ def camera(self, value):
108
+ if value is not None and not isinstance(value, Camera):
109
+ raise TypeError('Value must be a camera')
110
+ self._camera = value
111
+
112
+ @property
113
+ def children(self):
114
+ """list of :class:`Node` : The children of this node.
115
+ """
116
+ return self._children
117
+
118
+ @children.setter
119
+ def children(self, value):
120
+ self._children = value
121
+
122
+ @property
123
+ def skin(self):
124
+ """int : The skin index for this node.
125
+ """
126
+ return self._skin
127
+
128
+ @skin.setter
129
+ def skin(self, value):
130
+ self._skin = value
131
+
132
+ @property
133
+ def mesh(self):
134
+ """:class:`Mesh` : The mesh in this node.
135
+ """
136
+ return self._mesh
137
+
138
+ @mesh.setter
139
+ def mesh(self, value):
140
+ if value is not None and not isinstance(value, Mesh):
141
+ raise TypeError('Value must be a mesh')
142
+ self._mesh = value
143
+
144
+ @property
145
+ def light(self):
146
+ """:class:`Light` : The light in this node.
147
+ """
148
+ return self._light
149
+
150
+ @light.setter
151
+ def light(self, value):
152
+ if value is not None and not isinstance(value, Light):
153
+ raise TypeError('Value must be a light')
154
+ self._light = value
155
+
156
+ @property
157
+ def rotation(self):
158
+ """(4,) float : The xyzw quaternion for this node.
159
+ """
160
+ return self._rotation
161
+
162
+ @rotation.setter
163
+ def rotation(self, value):
164
+ value = np.asanyarray(value)
165
+ if value.shape != (4,):
166
+ raise ValueError('Quaternion must be a (4,) vector')
167
+ if np.abs(np.linalg.norm(value) - 1.0) > 1e-3:
168
+ raise ValueError('Quaternion must have norm == 1.0')
169
+ self._rotation = value
170
+ self._matrix = None
171
+
172
+ @property
173
+ def translation(self):
174
+ """(3,) float : The translation for this node.
175
+ """
176
+ return self._translation
177
+
178
+ @translation.setter
179
+ def translation(self, value):
180
+ value = np.asanyarray(value)
181
+ if value.shape != (3,):
182
+ raise ValueError('Translation must be a (3,) vector')
183
+ self._translation = value
184
+ self._matrix = None
185
+
186
+ @property
187
+ def scale(self):
188
+ """(3,) float : The scale for this node.
189
+ """
190
+ return self._scale
191
+
192
+ @scale.setter
193
+ def scale(self, value):
194
+ value = np.asanyarray(value)
195
+ if value.shape != (3,):
196
+ raise ValueError('Scale must be a (3,) vector')
197
+ self._scale = value
198
+ self._matrix = None
199
+
200
+ @property
201
+ def matrix(self):
202
+ """(4,4) float : The homogenous transform matrix for this node.
203
+
204
+ Note that this matrix's elements are not settable,
205
+ it's just a copy of the internal matrix. You can set the whole
206
+ matrix, but not an individual element.
207
+ """
208
+ if self._matrix is None:
209
+ self._matrix = self._m_from_tqs(
210
+ self.translation, self.rotation, self.scale
211
+ )
212
+ return self._matrix.copy()
213
+
214
+ @matrix.setter
215
+ def matrix(self, value):
216
+ value = np.asanyarray(value)
217
+ if value.shape != (4,4):
218
+ raise ValueError('Matrix must be a 4x4 numpy ndarray')
219
+ if not np.allclose(value[3,:], np.array([0.0, 0.0, 0.0, 1.0])):
220
+ raise ValueError('Bottom row of matrix must be [0,0,0,1]')
221
+ self.rotation = Node._q_from_m(value)
222
+ self.scale = Node._s_from_m(value)
223
+ self.translation = Node._t_from_m(value)
224
+ self._matrix = value
225
+
226
+ @staticmethod
227
+ def _t_from_m(m):
228
+ return m[:3,3]
229
+
230
+ @staticmethod
231
+ def _r_from_m(m):
232
+ U = m[:3,:3]
233
+ norms = np.linalg.norm(U.T, axis=1)
234
+ return U / norms
235
+
236
+ @staticmethod
237
+ def _q_from_m(m):
238
+ M = np.eye(4)
239
+ M[:3,:3] = Node._r_from_m(m)
240
+ q_wxyz = transformations.quaternion_from_matrix(M)
241
+ return np.roll(q_wxyz, -1)
242
+
243
+ @staticmethod
244
+ def _s_from_m(m):
245
+ return np.linalg.norm(m[:3,:3].T, axis=1)
246
+
247
+ @staticmethod
248
+ def _r_from_q(q):
249
+ q_wxyz = np.roll(q, 1)
250
+ return transformations.quaternion_matrix(q_wxyz)[:3,:3]
251
+
252
+ @staticmethod
253
+ def _m_from_tqs(t, q, s):
254
+ S = np.eye(4)
255
+ S[:3,:3] = np.diag(s)
256
+
257
+ R = np.eye(4)
258
+ R[:3,:3] = Node._r_from_q(q)
259
+
260
+ T = np.eye(4)
261
+ T[:3,3] = t
262
+
263
+ return T.dot(R.dot(S))