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/trackball.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Trackball class for 3D manipulation of viewpoints.
|
|
2
|
+
"""
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
import trimesh.transformations as transformations
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Trackball(object):
|
|
9
|
+
"""A trackball class for creating camera transforms from mouse movements.
|
|
10
|
+
"""
|
|
11
|
+
STATE_ROTATE = 0
|
|
12
|
+
STATE_PAN = 1
|
|
13
|
+
STATE_ROLL = 2
|
|
14
|
+
STATE_ZOOM = 3
|
|
15
|
+
|
|
16
|
+
def __init__(self, pose, size, scale,
|
|
17
|
+
target=np.array([0.0, 0.0, 0.0])):
|
|
18
|
+
"""Initialize a trackball with an initial camera-to-world pose
|
|
19
|
+
and the given parameters.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
pose : [4,4]
|
|
24
|
+
An initial camera-to-world pose for the trackball.
|
|
25
|
+
|
|
26
|
+
size : (float, float)
|
|
27
|
+
The width and height of the camera image in pixels.
|
|
28
|
+
|
|
29
|
+
scale : float
|
|
30
|
+
The diagonal of the scene's bounding box --
|
|
31
|
+
used for ensuring translation motions are sufficiently
|
|
32
|
+
fast for differently-sized scenes.
|
|
33
|
+
|
|
34
|
+
target : (3,) float
|
|
35
|
+
The center of the scene in world coordinates.
|
|
36
|
+
The trackball will revolve around this point.
|
|
37
|
+
"""
|
|
38
|
+
self._size = np.array(size)
|
|
39
|
+
self._scale = float(scale)
|
|
40
|
+
|
|
41
|
+
self._pose = pose
|
|
42
|
+
self._n_pose = pose
|
|
43
|
+
|
|
44
|
+
self._target = target
|
|
45
|
+
self._n_target = target
|
|
46
|
+
|
|
47
|
+
self._state = Trackball.STATE_ROTATE
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def pose(self):
|
|
51
|
+
"""autolab_core.RigidTransform : The current camera-to-world pose.
|
|
52
|
+
"""
|
|
53
|
+
return self._n_pose
|
|
54
|
+
|
|
55
|
+
def set_state(self, state):
|
|
56
|
+
"""Set the state of the trackball in order to change the effect of
|
|
57
|
+
dragging motions.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
state : int
|
|
62
|
+
One of Trackball.STATE_ROTATE, Trackball.STATE_PAN,
|
|
63
|
+
Trackball.STATE_ROLL, and Trackball.STATE_ZOOM.
|
|
64
|
+
"""
|
|
65
|
+
self._state = state
|
|
66
|
+
|
|
67
|
+
def resize(self, size):
|
|
68
|
+
"""Resize the window.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
size : (float, float)
|
|
73
|
+
The new width and height of the camera image in pixels.
|
|
74
|
+
"""
|
|
75
|
+
self._size = np.array(size)
|
|
76
|
+
|
|
77
|
+
def down(self, point):
|
|
78
|
+
"""Record an initial mouse press at a given point.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
point : (2,) int
|
|
83
|
+
The x and y pixel coordinates of the mouse press.
|
|
84
|
+
"""
|
|
85
|
+
self._pdown = np.array(point, dtype=np.float32)
|
|
86
|
+
self._pose = self._n_pose
|
|
87
|
+
self._target = self._n_target
|
|
88
|
+
|
|
89
|
+
def drag(self, point):
|
|
90
|
+
"""Update the tracball during a drag.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
point : (2,) int
|
|
95
|
+
The current x and y pixel coordinates of the mouse during a drag.
|
|
96
|
+
This will compute a movement for the trackball with the relative
|
|
97
|
+
motion between this point and the one marked by down().
|
|
98
|
+
"""
|
|
99
|
+
point = np.array(point, dtype=np.float32)
|
|
100
|
+
dx, dy = point - self._pdown
|
|
101
|
+
mindim = 0.3 * np.min(self._size)
|
|
102
|
+
|
|
103
|
+
target = self._target
|
|
104
|
+
x_axis = self._pose[:3,0].flatten()
|
|
105
|
+
y_axis = self._pose[:3,1].flatten()
|
|
106
|
+
z_axis = self._pose[:3,2].flatten()
|
|
107
|
+
eye = self._pose[:3,3].flatten()
|
|
108
|
+
|
|
109
|
+
# Interpret drag as a rotation
|
|
110
|
+
if self._state == Trackball.STATE_ROTATE:
|
|
111
|
+
x_angle = -dx / mindim
|
|
112
|
+
x_rot_mat = transformations.rotation_matrix(
|
|
113
|
+
x_angle, y_axis, target
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
y_angle = dy / mindim
|
|
117
|
+
y_rot_mat = transformations.rotation_matrix(
|
|
118
|
+
y_angle, x_axis, target
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
self._n_pose = y_rot_mat.dot(x_rot_mat.dot(self._pose))
|
|
122
|
+
|
|
123
|
+
# Interpret drag as a roll about the camera axis
|
|
124
|
+
elif self._state == Trackball.STATE_ROLL:
|
|
125
|
+
center = self._size / 2.0
|
|
126
|
+
v_init = self._pdown - center
|
|
127
|
+
v_curr = point - center
|
|
128
|
+
v_init = v_init / np.linalg.norm(v_init)
|
|
129
|
+
v_curr = v_curr / np.linalg.norm(v_curr)
|
|
130
|
+
|
|
131
|
+
theta = (-np.arctan2(v_curr[1], v_curr[0]) +
|
|
132
|
+
np.arctan2(v_init[1], v_init[0]))
|
|
133
|
+
|
|
134
|
+
rot_mat = transformations.rotation_matrix(theta, z_axis, target)
|
|
135
|
+
|
|
136
|
+
self._n_pose = rot_mat.dot(self._pose)
|
|
137
|
+
|
|
138
|
+
# Interpret drag as a camera pan in view plane
|
|
139
|
+
elif self._state == Trackball.STATE_PAN:
|
|
140
|
+
dx = -dx / (5.0 * mindim) * self._scale
|
|
141
|
+
dy = -dy / (5.0 * mindim) * self._scale
|
|
142
|
+
|
|
143
|
+
translation = dx * x_axis + dy * y_axis
|
|
144
|
+
self._n_target = self._target + translation
|
|
145
|
+
t_tf = np.eye(4)
|
|
146
|
+
t_tf[:3,3] = translation
|
|
147
|
+
self._n_pose = t_tf.dot(self._pose)
|
|
148
|
+
|
|
149
|
+
# Interpret drag as a zoom motion
|
|
150
|
+
elif self._state == Trackball.STATE_ZOOM:
|
|
151
|
+
radius = np.linalg.norm(eye - target)
|
|
152
|
+
ratio = 0.0
|
|
153
|
+
if dy > 0:
|
|
154
|
+
ratio = np.exp(abs(dy) / (0.5 * self._size[1])) - 1.0
|
|
155
|
+
elif dy < 0:
|
|
156
|
+
ratio = 1.0 - np.exp(dy / (0.5 * (self._size[1])))
|
|
157
|
+
translation = -np.sign(dy) * ratio * radius * z_axis
|
|
158
|
+
t_tf = np.eye(4)
|
|
159
|
+
t_tf[:3,3] = translation
|
|
160
|
+
self._n_pose = t_tf.dot(self._pose)
|
|
161
|
+
|
|
162
|
+
def scroll(self, clicks):
|
|
163
|
+
"""Zoom using a mouse scroll wheel motion.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
clicks : int
|
|
168
|
+
The number of clicks. Positive numbers indicate forward wheel
|
|
169
|
+
movement.
|
|
170
|
+
"""
|
|
171
|
+
target = self._target
|
|
172
|
+
ratio = 0.90
|
|
173
|
+
|
|
174
|
+
mult = 1.0
|
|
175
|
+
if clicks > 0:
|
|
176
|
+
mult = ratio**clicks
|
|
177
|
+
elif clicks < 0:
|
|
178
|
+
mult = (1.0 / ratio)**abs(clicks)
|
|
179
|
+
|
|
180
|
+
z_axis = self._n_pose[:3,2].flatten()
|
|
181
|
+
eye = self._n_pose[:3,3].flatten()
|
|
182
|
+
radius = np.linalg.norm(eye - target)
|
|
183
|
+
translation = (mult * radius - radius) * z_axis
|
|
184
|
+
t_tf = np.eye(4)
|
|
185
|
+
t_tf[:3,3] = translation
|
|
186
|
+
self._n_pose = t_tf.dot(self._n_pose)
|
|
187
|
+
|
|
188
|
+
z_axis = self._pose[:3,2].flatten()
|
|
189
|
+
eye = self._pose[:3,3].flatten()
|
|
190
|
+
radius = np.linalg.norm(eye - target)
|
|
191
|
+
translation = (mult * radius - radius) * z_axis
|
|
192
|
+
t_tf = np.eye(4)
|
|
193
|
+
t_tf[:3,3] = translation
|
|
194
|
+
self._pose = t_tf.dot(self._pose)
|
|
195
|
+
|
|
196
|
+
def rotate(self, azimuth, axis=None):
|
|
197
|
+
"""Rotate the trackball about the "Up" axis by azimuth radians.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
azimuth : float
|
|
202
|
+
The number of radians to rotate.
|
|
203
|
+
"""
|
|
204
|
+
target = self._target
|
|
205
|
+
|
|
206
|
+
y_axis = self._n_pose[:3,1].flatten()
|
|
207
|
+
if axis is not None:
|
|
208
|
+
y_axis = axis
|
|
209
|
+
x_rot_mat = transformations.rotation_matrix(azimuth, y_axis, target)
|
|
210
|
+
self._n_pose = x_rot_mat.dot(self._n_pose)
|
|
211
|
+
|
|
212
|
+
y_axis = self._pose[:3,1].flatten()
|
|
213
|
+
if axis is not None:
|
|
214
|
+
y_axis = axis
|
|
215
|
+
x_rot_mat = transformations.rotation_matrix(azimuth, y_axis, target)
|
|
216
|
+
self._pose = x_rot_mat.dot(self._pose)
|
pyrender/utils.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from PIL import Image
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def format_color_vector(value, length):
|
|
6
|
+
"""Format a color vector.
|
|
7
|
+
"""
|
|
8
|
+
if isinstance(value, int):
|
|
9
|
+
value = value / 255.0
|
|
10
|
+
if isinstance(value, float):
|
|
11
|
+
value = np.repeat(value, length)
|
|
12
|
+
if isinstance(value, list) or isinstance(value, tuple):
|
|
13
|
+
value = np.array(value)
|
|
14
|
+
if isinstance(value, np.ndarray):
|
|
15
|
+
value = value.squeeze()
|
|
16
|
+
if np.issubdtype(value.dtype, np.integer):
|
|
17
|
+
value = (value / 255.0).astype(np.float32)
|
|
18
|
+
if value.ndim != 1:
|
|
19
|
+
raise ValueError('Format vector takes only 1-D vectors')
|
|
20
|
+
if length > value.shape[0]:
|
|
21
|
+
value = np.hstack((value, np.ones(length - value.shape[0])))
|
|
22
|
+
elif length < value.shape[0]:
|
|
23
|
+
value = value[:length]
|
|
24
|
+
else:
|
|
25
|
+
raise ValueError('Invalid vector data type')
|
|
26
|
+
|
|
27
|
+
return value.squeeze().astype(np.float32)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def format_color_array(value, shape):
|
|
31
|
+
"""Format an array of colors.
|
|
32
|
+
"""
|
|
33
|
+
# Convert uint8 to floating
|
|
34
|
+
value = np.asanyarray(value)
|
|
35
|
+
if np.issubdtype(value.dtype, np.integer):
|
|
36
|
+
value = (value / 255.0).astype(np.float32)
|
|
37
|
+
|
|
38
|
+
# Match up shapes
|
|
39
|
+
if value.ndim == 1:
|
|
40
|
+
value = np.tile(value, (shape[0],1))
|
|
41
|
+
if value.shape[1] < shape[1]:
|
|
42
|
+
nc = shape[1] - value.shape[1]
|
|
43
|
+
value = np.column_stack((value, np.ones((value.shape[0], nc))))
|
|
44
|
+
elif value.shape[1] > shape[1]:
|
|
45
|
+
value = value[:,:shape[1]]
|
|
46
|
+
return value.astype(np.float32)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def format_texture_source(texture, target_channels='RGB'):
|
|
50
|
+
"""Format a texture as a float32 np array.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# Pass through None
|
|
54
|
+
if texture is None:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
# Convert PIL images into numpy arrays
|
|
58
|
+
if isinstance(texture, Image.Image):
|
|
59
|
+
if texture.mode == 'P' and target_channels in ('RGB', 'RGBA'):
|
|
60
|
+
texture = np.array(texture.convert(target_channels))
|
|
61
|
+
else:
|
|
62
|
+
texture = np.array(texture)
|
|
63
|
+
|
|
64
|
+
# Format numpy arrays
|
|
65
|
+
if isinstance(texture, np.ndarray):
|
|
66
|
+
if np.issubdtype(texture.dtype, np.floating):
|
|
67
|
+
texture = np.array(texture * 255.0, dtype=np.uint8)
|
|
68
|
+
elif np.issubdtype(texture.dtype, np.integer):
|
|
69
|
+
texture = texture.astype(np.uint8)
|
|
70
|
+
else:
|
|
71
|
+
raise TypeError('Invalid type {} for texture'.format(
|
|
72
|
+
type(texture)
|
|
73
|
+
))
|
|
74
|
+
|
|
75
|
+
# Format array by picking out correct texture channels or padding
|
|
76
|
+
if texture.ndim == 2:
|
|
77
|
+
texture = texture[:,:,np.newaxis]
|
|
78
|
+
if target_channels == 'R':
|
|
79
|
+
texture = texture[:,:,0]
|
|
80
|
+
texture = texture.squeeze()
|
|
81
|
+
elif target_channels == 'RG':
|
|
82
|
+
if texture.shape[2] == 1:
|
|
83
|
+
texture = np.repeat(texture, 2, axis=2)
|
|
84
|
+
else:
|
|
85
|
+
texture = texture[:,:,(0,1)]
|
|
86
|
+
elif target_channels == 'GB':
|
|
87
|
+
if texture.shape[2] == 1:
|
|
88
|
+
texture = np.repeat(texture, 2, axis=2)
|
|
89
|
+
elif texture.shape[2] > 2:
|
|
90
|
+
texture = texture[:,:,(1,2)]
|
|
91
|
+
elif target_channels == 'RGB':
|
|
92
|
+
if texture.shape[2] == 1:
|
|
93
|
+
texture = np.repeat(texture, 3, axis=2)
|
|
94
|
+
elif texture.shape[2] == 2:
|
|
95
|
+
raise ValueError('Cannot reformat 2-channel texture into RGB')
|
|
96
|
+
else:
|
|
97
|
+
texture = texture[:,:,(0,1,2)]
|
|
98
|
+
elif target_channels == 'RGBA':
|
|
99
|
+
if texture.shape[2] == 1:
|
|
100
|
+
texture = np.repeat(texture, 4, axis=2)
|
|
101
|
+
texture[:,:,3] = 255
|
|
102
|
+
elif texture.shape[2] == 2:
|
|
103
|
+
raise ValueError('Cannot reformat 2-channel texture into RGBA')
|
|
104
|
+
elif texture.shape[2] == 3:
|
|
105
|
+
tx = np.empty((texture.shape[0], texture.shape[1], 4), dtype=np.uint8)
|
|
106
|
+
tx[:,:,:3] = texture
|
|
107
|
+
tx[:,:,3] = 255
|
|
108
|
+
texture = tx
|
|
109
|
+
else:
|
|
110
|
+
raise ValueError('Invalid texture channel specification: {}'
|
|
111
|
+
.format(target_channels))
|
|
112
|
+
else:
|
|
113
|
+
raise TypeError('Invalid type {} for texture'.format(type(texture)))
|
|
114
|
+
|
|
115
|
+
return texture
|
pyrender/version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.0.0'
|