forked from GazzolaLab/PyElastica
-
Notifications
You must be signed in to change notification settings - Fork 0
/
_povmacros.py
353 lines (300 loc) · 10.6 KB
/
_povmacros.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
""" POVray macros for pyelastica
This module includes utility methods to support POVray rendering.
"""
import subprocess
from collections import defaultdict
def pyelastica_rod(
x,
r,
color="rgb<0.45,0.39,1>",
transmit=0.0,
interpolation="linear_spline",
deform=None,
tab=" ",
):
"""pyelastica_rod POVray script generator
Generates povray sphere_sweep object in string.
The rod is given with the element radius (r) and joint positions (x)
Parameters
----------
x : numpy array
Position vector
Expected shape: [num_time_step, 3, num_element]
r : numpy array
Radius vector
Expected shape: [num_time_step, num_element]
color : str
Color of the rod (default: Purple <0.45,0.39,1>)
transmit : float
Transparency (0.0 to 1.0).
interpolation : str
Interpolation method for sphere_sweep
Supporting type: 'linear_spline', 'b_spline', 'cubic_spline'
(default: linear_spline)
deform : str
Additional object deformation
Example: "scale<4,4,4> rotate<0,90,90> translate<2,0,4>"
Returns
-------
cmd : string
Povray script
"""
assert interpolation in ["linear_spline", "b_spline", "cubic_spline"]
tab = " "
# Parameters
num_element = r.shape[0]
lines = []
lines.append("sphere_sweep {")
lines.append(tab + f"{interpolation} {num_element}")
for i in range(num_element):
lines.append(tab + f",<{x[0,i]},{x[1,i]},{x[2,i]}>,{r[i]}")
lines.append(tab + "texture{")
lines.append(tab + tab + "pigment{ color %s transmit %f }" % (color, transmit))
lines.append(tab + tab + "finish{ phong 1 }")
lines.append(tab + "}")
if deform is not None:
lines.append(tab + deform)
lines.append(tab + "}\n")
cmd = "\n".join(lines)
return cmd
def render(
filename, width, height, antialias="on", quality=11, display="Off", pov_thread=4
):
"""Rendering frame
Generate the povray script file '.pov' and image file '.png'
The directory must be made before calling this method.
Parameters
----------
filename : str
POV filename (without extension)
width : int
The width of the output image.
height : int
The height of the output image.
antialias : str ['on', 'off']
Turns anti-aliasing on/off [default='on']
quality : int
Image output quality. [default=11]
display : str
Turns display option on/off during POVray rendering. [default='off']
pov_thread : int
Number of thread per povray process. [default=4]
Acceptable range is (4,512).
Refer 'Symmetric Multiprocessing (SMP)' for further details
https://www.povray.org/documentation/3.7.0/r3_2.html#r3_2_8_1
Raises
------
IOError
If the povray run causes unexpected error, such as parsing error,
this method will raise IOerror.
"""
# Define script path and image path
script_file = filename + ".pov"
image_file = filename + ".png"
# Run Povray as subprocess
cmds = [
"povray",
"+I" + script_file,
"+O" + image_file,
f"-H{height}",
f"-W{width}",
f"Work_Threads={pov_thread}",
f"Antialias={antialias}",
f"Quality={quality}",
f"Display={display}",
]
process = subprocess.Popen(
cmds, stderr=subprocess.PIPE, stdin=subprocess.PIPE, stdout=subprocess.PIPE
)
_, stderr = process.communicate()
# Check execution error
if process.returncode:
print(type(stderr), stderr)
raise IOError(
"POVRay rendering failed with the following error: "
+ stderr.decode("ascii")
)
class Stages:
"""Stage definition
Collection of the camera and light sources.
Each camera added to the stage represent distinct viewpoints to render.
Lights can be assigned to multiple cameras.
The povray script can be generated for each viewpoints created using 'generate_scripts.'
(TODO) Implement transform camera for dynamic camera moves
Attributes
----------
pre_scripts : str
Prepending script for all viewpoints
post_scripts : str
Appending script for all viewpoints
cameras : list
List of camera setup
lights : list
List of lightings
_light_assign : dictionary[list]
Dictionary that pairs lighting to camera.
Example) _light_assign[2] is the list of light sources
assigned to the cameras[2]
Methods
-------
add_camera : Add new camera (viewpoint) to the stage.
add_light : Add new light source to the stage for a assigned camera.
generate_scripts : Generate list of povray script for each camera.
Class Objects
-------------
StageObject
Camera
Light
Properties
----------
len : number of camera
The number of viewpoints
"""
def __init__(self, pre_scripts="", post_scripts=""):
self.pre_scripts = pre_scripts
self.post_scripts = post_scripts
self.cameras = []
self.lights = []
self._light_assign = defaultdict(list)
def add_camera(self, name, **kwargs):
"""Add camera (viewpoint)"""
self.cameras.append(self.Camera(name=name, **kwargs))
def add_light(self, camera_id=-1, **kwargs):
"""Add lighting and assign to camera
Parameters
----------
camera_id : int or list
Assigned camera. [default=-1]
If a list of camera_id is given, light is assigned for listed camera.
If camera_id==-1, the lighting is assigned for all camera.
"""
light_id = len(self.lights)
self.lights.append(self.Light(**kwargs))
if isinstance(camera_id, list) or isinstance(camera_id, tuple):
camera_id = list(set(camera_id))
for idx in camera_id:
self._light_assign[idx].append(light_id)
elif isinstance(camera_id, int):
self._light_assign[camera_id].append(light_id)
else:
raise NotImplementedError("camera_id can only be a list or int")
def generate_scripts(self):
"""Generate pov-ray script for all camera setup
Returns
-------
scripts : list
Return list of pov-scripts (string) that includes camera and assigned lightings.
"""
scripts = {}
for idx, camera in enumerate(self.cameras):
light_ids = self._light_assign[idx] + self._light_assign[-1]
cmds = []
cmds.append(self.pre_scripts)
cmds.append(str(camera)) # Script camera
for light_id in light_ids: # Script Lightings
cmds.append(str(self.lights[light_id]))
cmds.append(self.post_scripts)
scripts[camera.name] = "\n".join(cmds)
return scripts
def transform_camera(self, dx, R, camera_id):
# (TODO) translate or rotate the assigned camera
raise NotImplementedError
def __len_(self):
return len(self.cameras)
# Stage Objects: Camera, Light
class StageObject:
"""Template for stage objects
Objects (camera and light) is defined as an object in order to
manipulate (translate or rotate) them during the rendering.
Attributes
----------
str : str
String representation of object.
The placeholder exist to avoid rescripting.
Methods
-------
_color2str : str
Change triplet tuple (or list) of color into rgb string.
_position2str : str
Change triplet tuple (or list) of position vector into string.
"""
def __init__(self):
self.str = ""
self.update_script()
def update_script(self):
raise NotImplementedError
def __str__(self):
return self.str
def _color2str(self, color):
if isinstance(color, str):
return color
elif isinstance(color, list) and len(color) == 3:
# RGB
return "rgb<{},{},{}>".format(*color)
else:
raise NotImplementedError(
"Only string-type color or RGB input is implemented"
)
def _position2str(self, position):
assert len(position) == 3
return "<{},{},{}>".format(*position)
class Camera(StageObject):
"""Camera object
http://www.povray.org/documentation/view/3.7.0/246/
Attributes
----------
location : list or tuple
Position vector of camera location. (length=3)
angle : int
Camera angle
look_at : list or tuple
Position vector of the location where camera points to (length=3)
name : str
Name of the view-point.
sky : list or tuple
Tilt of the camera (length=3) [default=[0,1,0]]
"""
def __init__(self, name, location, angle, look_at, sky=(0, 1, 0)):
self.name = name
self.location = location
self.angle = angle
self.look_at = look_at
self.sky = sky
super().__init__()
def update_script(self):
location = self._position2str(self.location)
look_at = self._position2str(self.look_at)
sky = self._position2str(self.sky)
cmds = []
cmds.append("camera{")
cmds.append(f" location {location}")
cmds.append(f" angle {self.angle}")
cmds.append(f" look_at {look_at}")
cmds.append(f" sky {sky}")
cmds.append(" right x*image_width/image_height")
cmds.append("}")
self.str = "\n".join(cmds)
class Light(StageObject):
"""Light object
Attributes
----------
position : list or tuple
Position vector of light location. (length=3)
color : str or list
Color of the light.
Both string form of color or rgb (normalized) form is supported.
Example) color='White', color=[1,1,1]
"""
def __init__(self, position, color):
self.position = position
self.color = color
super().__init__()
def update_script(self):
position = self._position2str(self.position)
color = self._color2str(self.color)
cmds = []
cmds.append("light_source{")
cmds.append(f" {position}")
cmds.append(f" color {color}")
cmds.append("}")
self.str = "\n".join(cmds)