FBX IO: Vertex position access with attributes
[blender-addons.git] / render_povray / scenography.py
bloba5164c051de651a14cdcd2b94797380b2c65b8e1
1 # SPDX-FileCopyrightText: 2021-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 """With respect to camera frame and optics distortions, also export environment
7 with world, sky, atmospheric effects such as rainbows or smoke """
9 import bpy
11 import os
12 from imghdr import what # imghdr is a python lib to identify image file types
13 from math import atan, pi, sqrt, degrees
14 from . import voxel_lib # for smoke rendering
15 from .model_primitives import write_object_modifiers
18 # -------- find image texture # used for export_world -------- #
21 def image_format(img_f):
22 """Identify input image filetypes to transmit to POV."""
23 # First use the below explicit extensions to identify image file prospects
24 ext = {
25 "JPG": "jpeg",
26 "JPEG": "jpeg",
27 "GIF": "gif",
28 "TGA": "tga",
29 "IFF": "iff",
30 "PPM": "ppm",
31 "PNG": "png",
32 "SYS": "sys",
33 "TIFF": "tiff",
34 "TIF": "tiff",
35 "EXR": "exr",
36 "HDR": "hdr",
37 }.get(os.path.splitext(img_f)[-1].upper(), "")
38 # Then, use imghdr to really identify the filetype as it can be different
39 if not ext:
40 # maybe add a check for if path exists here?
41 print(" WARNING: texture image has no extension") # too verbose
43 ext = what(img_f) # imghdr is a python lib to identify image file types
44 return ext
47 def img_map(ts):
48 """Translate mapping type from Blender UI to POV syntax and return that string."""
49 image_map = ""
50 texdata = bpy.data.textures[ts.texture]
51 if ts.mapping == "FLAT":
52 image_map = "map_type 0 "
53 elif ts.mapping == "SPHERE":
54 image_map = "map_type 1 "
55 elif ts.mapping == "TUBE":
56 image_map = "map_type 2 "
58 # map_type 3 and 4 in development (?) (ENV in pov 3.8)
59 # for POV-Ray, currently they just seem to default back to Flat (type 0)
60 # elif ts.mapping=="?":
61 # image_map = " map_type 3 "
62 # elif ts.mapping=="?":
63 # image_map = " map_type 4 "
64 if ts.use_interpolation: # Available if image sampling class reactivated?
65 image_map += " interpolate 2 "
66 if texdata.extension == "CLIP":
67 image_map += " once "
68 # image_map += "}"
69 # if ts.mapping=='CUBE':
70 # image_map+= "warp { cubic } rotate <-90,0,180>"
71 # no direct cube type mapping. Though this should work in POV 3.7
72 # it doesn't give that good results(best suited to environment maps?)
73 # if image_map == "":
74 # print(" No texture image found ")
75 return image_map
78 def img_map_transforms(ts):
79 """Translate mapping transformations from Blender UI to POV syntax and return that string."""
80 # XXX TODO: unchecked textures give error of variable referenced before assignment XXX
81 # POV-Ray "scale" is not a number of repetitions factor, but ,its
82 # inverse, a standard scale factor.
83 # 0.5 Offset is needed relatively to scale because center of the
84 # scale is 0.5,0.5 in blender and 0,0 in POV
85 # Strange that the translation factor for scale is not the same as for
86 # translate.
87 # TODO: verify both matches with other blender renderers / internal in previous versions.
88 image_map_transforms = ""
89 image_map_transforms = "scale <%.4g,%.4g,%.4g> translate <%.4g,%.4g,%.4g>" % (
90 ts.scale[0],
91 ts.scale[1],
92 ts.scale[2],
93 ts.offset[0],
94 ts.offset[1],
95 ts.offset[2],
97 # image_map_transforms = (" translate <-0.5,-0.5,0.0> scale <%.4g,%.4g,%.4g> translate <%.4g,%.4g,%.4g>" % \
98 # ( 1.0 / ts.scale.x,
99 # 1.0 / ts.scale.y,
100 # 1.0 / ts.scale.z,
101 # (0.5 / ts.scale.x) + ts.offset.x,
102 # (0.5 / ts.scale.y) + ts.offset.y,
103 # ts.offset.z))
104 # image_map_transforms = (
105 # "translate <-0.5,-0.5,0> "
106 # "scale <-1,-1,1> * <%.4g,%.4g,%.4g> "
107 # "translate <0.5,0.5,0> + <%.4g,%.4g,%.4g>" % \
108 # (1.0 / ts.scale.x,
109 # 1.0 / ts.scale.y,
110 # 1.0 / ts.scale.z,
111 # ts.offset.x,
112 # ts.offset.y,
113 # ts.offset.z)
115 return image_map_transforms
118 def img_map_bg(wts):
119 """Translate world mapping from Blender UI to POV syntax and return that string."""
120 tex = bpy.data.textures[wts.texture]
121 image_mapBG = ""
122 # texture_coords refers to the mapping of world textures:
123 if wts.texture_coords in ["VIEW", "GLOBAL"]:
124 image_mapBG = " map_type 0 "
125 elif wts.texture_coords == "ANGMAP":
126 image_mapBG = " map_type 1 "
127 elif wts.texture_coords == "TUBE":
128 image_mapBG = " map_type 2 "
130 if tex.use_interpolation:
131 image_mapBG += " interpolate 2 "
132 if tex.extension == "CLIP":
133 image_mapBG += " once "
134 # image_mapBG += "}"
135 # if wts.mapping == 'CUBE':
136 # image_mapBG += "warp { cubic } rotate <-90,0,180>"
137 # no direct cube type mapping. Though this should work in POV 3.7
138 # it doesn't give that good results(best suited to environment maps?)
139 # if image_mapBG == "":
140 # print(" No background texture image found ")
141 return image_mapBG
144 def path_image(image):
145 """Conform a path string to POV syntax to avoid POV errors."""
146 return bpy.path.abspath(image.filepath, library=image.library).replace("\\", "/")
147 # .replace("\\","/") to get only forward slashes as it's what POV prefers,
148 # even on windows
151 # end find image texture
152 # -----------------------------------------------------------------------------
155 def export_camera(file, scene, global_matrix, render, tab_write):
156 """Translate camera from Blender UI to POV syntax and write to exported file."""
157 camera = scene.camera
159 # DH disabled for now, this isn't the correct context
160 active_object = None # bpy.context.active_object # does not always work MR
161 matrix = global_matrix @ camera.matrix_world
162 focal_point = camera.data.dof.focus_distance
164 # compute resolution
165 q_size = render.resolution_x / render.resolution_y
166 tab_write(file, "#declare camLocation = <%.6f, %.6f, %.6f>;\n" % matrix.translation[:])
167 tab_write(
168 file,
170 "#declare camLookAt = <%.6f, %.6f, %.6f>;\n"
171 % tuple(degrees(e) for e in matrix.to_3x3().to_euler())
175 tab_write(file, "camera {\n")
176 if scene.pov.baking_enable and active_object and active_object.type == "MESH":
177 tab_write(file, "mesh_camera{ 1 3\n") # distribution 3 is what we want here
178 tab_write(file, "mesh{%s}\n" % active_object.name)
179 tab_write(file, "}\n")
180 tab_write(file, "location <0,0,.01>")
181 tab_write(file, "direction <0,0,-1>")
183 else:
184 if camera.data.type == "ORTHO":
185 # XXX todo: track when SensorHeightRatio was added to see if needed (not used)
186 sensor_height_ratio = (
187 render.resolution_x * camera.data.ortho_scale / render.resolution_y
189 tab_write(file, "orthographic\n")
190 # Blender angle is radian so should be converted to degrees:
191 # % (camera.data.angle * (180.0 / pi) )
192 # but actually argument is not compulsory after angle in pov ortho mode
193 tab_write(file, "angle\n")
194 tab_write(file, "right <%6f, 0, 0>\n" % -camera.data.ortho_scale)
195 tab_write(file, "location <0, 0, 0>\n")
196 tab_write(file, "look_at <0, 0, -1>\n")
197 tab_write(file, "up <0, %6f, 0>\n" % (camera.data.ortho_scale / q_size))
199 elif camera.data.type == "PANO":
200 tab_write(file, "panoramic\n")
201 tab_write(file, "location <0, 0, 0>\n")
202 tab_write(file, "look_at <0, 0, -1>\n")
203 tab_write(file, "right <%s, 0, 0>\n" % -q_size)
204 tab_write(file, "up <0, 1, 0>\n")
205 tab_write(file, "angle %f\n" % (360.0 * atan(16.0 / camera.data.lens) / pi))
206 elif camera.data.type == "PERSP":
207 # Standard camera otherwise would be default in pov
208 tab_write(file, "location <0, 0, 0>\n")
209 tab_write(file, "look_at <0, 0, -1>\n")
210 tab_write(file, "right <%s, 0, 0>\n" % -q_size)
211 tab_write(file, "up <0, 1, 0>\n")
212 tab_write(
213 file,
214 "angle %f\n"
215 % (2 * atan(camera.data.sensor_width / 2 / camera.data.lens) * 180.0 / pi),
218 tab_write(
219 file,
220 "rotate <%.6f, %.6f, %.6f>\n" % tuple(degrees(e) for e in matrix.to_3x3().to_euler()),
223 tab_write(file, "translate <%.6f, %.6f, %.6f>\n" % matrix.translation[:])
224 if camera.data.dof.use_dof and (focal_point != 0 or camera.data.dof.focus_object):
225 tab_write(
226 file, "aperture %.3g\n" % (1 / (camera.data.dof.aperture_fstop * 10000) * 1000)
228 tab_write(
229 file,
230 "blur_samples %d %d\n"
231 % (camera.data.pov.dof_samples_min, camera.data.pov.dof_samples_max),
233 tab_write(file, "variance 1/%d\n" % camera.data.pov.dof_variance)
234 tab_write(file, "confidence %.3g\n" % camera.data.pov.dof_confidence)
235 if camera.data.dof.focus_object:
236 focal_ob = scene.objects[camera.data.dof.focus_object.name]
237 matrix_blur = global_matrix @ focal_ob.matrix_world
238 tab_write(file, "focal_point <%.4f,%.4f,%.4f>\n" % matrix_blur.translation[:])
239 else:
240 tab_write(file, "focal_point <0, 0, %f>\n" % focal_point)
241 if camera.data.pov.normal_enable:
242 tab_write(
243 file,
244 "normal {%s %.4f turbulence %.4f scale %.4f}\n"
246 camera.data.pov.normal_patterns,
247 camera.data.pov.cam_normal,
248 camera.data.pov.turbulence,
249 camera.data.pov.scale,
252 tab_write(file, "}\n")
255 exported_lights_count = 0
258 def export_lights(lamps, file, scene, global_matrix, tab_write):
259 """Translate lights from Blender UI to POV syntax and write to exported file."""
261 from .render import write_matrix, tab_write
263 # Incremented after each lamp export to declare its target
264 # currently used for Fresnel diffuse shader as their slope vector:
265 global exported_lights_count
266 # Get all lamps and keep their count in a global variable
267 for exported_lights_count, ob in enumerate(lamps, start=1):
268 lamp = ob.data
270 matrix = global_matrix @ ob.matrix_world
272 # Color is no longer modified by energy
273 # any way to directly get bpy_prop_array as tuple?
274 color = tuple(lamp.color)
276 tab_write(file, "light_source {\n")
277 tab_write(file, "< 0,0,0 >\n")
278 tab_write(file, "color srgb<%.3g, %.3g, %.3g>\n" % color)
280 if lamp.type == "POINT":
281 pass
282 elif lamp.type == "SPOT":
283 tab_write(file, "spotlight\n")
285 # Falloff is the main radius from the centre line
286 tab_write(file, "falloff %.2f\n" % (degrees(lamp.spot_size) / 2.0)) # 1 TO 179 FOR BOTH
287 tab_write(
288 file, "radius %.6f\n" % ((degrees(lamp.spot_size) / 2.0) * (1.0 - lamp.spot_blend))
291 # Blender does not have a tightness equivalent, 0 is most like blender default.
292 tab_write(file, "tightness 0\n") # 0:10f
294 tab_write(file, "point_at <0, 0, -1>\n")
295 if lamp.pov.use_halo:
296 tab_write(file, "looks_like{\n")
297 tab_write(file, "sphere{<0,0,0>,%.6f\n" % lamp.distance)
298 tab_write(file, "hollow\n")
299 tab_write(file, "material{\n")
300 tab_write(file, "texture{\n")
301 tab_write(file, "pigment{rgbf<1,1,1,%.4f>}\n" % (lamp.pov.halo_intensity * 5.0))
302 tab_write(file, "}\n")
303 tab_write(file, "interior{\n")
304 tab_write(file, "media{\n")
305 tab_write(file, "emission 1\n")
306 tab_write(file, "scattering {1, 0.5}\n")
307 tab_write(file, "density{\n")
308 tab_write(file, "spherical\n")
309 tab_write(file, "color_map{\n")
310 tab_write(file, "[0.0 rgb <0,0,0>]\n")
311 tab_write(file, "[0.5 rgb <1,1,1>]\n")
312 tab_write(file, "[1.0 rgb <1,1,1>]\n")
313 tab_write(file, "}\n")
314 tab_write(file, "}\n")
315 tab_write(file, "}\n")
316 tab_write(file, "}\n")
317 tab_write(file, "}\n")
318 tab_write(file, "}\n")
319 tab_write(file, "}\n")
320 elif lamp.type == "SUN":
321 tab_write(file, "parallel\n")
322 tab_write(file, "point_at <0, 0, -1>\n") # *must* be after 'parallel'
324 elif lamp.type == "AREA":
325 tab_write(file, "fade_distance %.6f\n" % (lamp.distance / 2.0))
326 # Area lights have no falloff type, so always use blenders lamp quad equivalent
327 # for those?
328 tab_write(file, "fade_power %d\n" % 2)
329 size_x = lamp.size
330 samples_x = lamp.pov.shadow_ray_samples_x
331 if lamp.shape == "SQUARE":
332 size_y = size_x
333 samples_y = samples_x
334 else:
335 size_y = lamp.size_y
336 samples_y = lamp.pov.shadow_ray_samples_y
338 tab_write(
339 file,
340 "area_light <%.6f,0,0>,<0,%.6f,0> %d, %d\n"
341 % (size_x, size_y, samples_x, samples_y),
343 tab_write(file, "area_illumination\n")
344 if lamp.pov.shadow_ray_sample_method == "CONSTANT_JITTERED":
345 if lamp.pov.use_jitter:
346 tab_write(file, "jitter\n")
347 else:
348 tab_write(file, "adaptive 1\n")
349 tab_write(file, "jitter\n")
351 # No shadow checked either at global or light level:
352 if not scene.pov.use_shadows or (lamp.pov.shadow_method == "NOSHADOW"):
353 tab_write(file, "shadowless\n")
355 # Sun shouldn't be attenuated. Area lights have no falloff attribute so they
356 # are put to type 2 attenuation a little higher above.
357 if lamp.type not in {"SUN", "AREA"}:
358 if lamp.falloff_type == "INVERSE_SQUARE":
359 tab_write(file, "fade_distance %.6f\n" % (sqrt(lamp.distance / 2.0)))
360 tab_write(file, "fade_power %d\n" % 2) # Use blenders lamp quad equivalent
361 elif lamp.falloff_type == "INVERSE_LINEAR":
362 tab_write(file, "fade_distance %.6f\n" % (lamp.distance / 2.0))
363 tab_write(file, "fade_power %d\n" % 1) # Use blenders lamp linear
364 elif lamp.falloff_type == "CONSTANT":
365 tab_write(file, "fade_distance %.6f\n" % (lamp.distance / 2.0))
366 tab_write(file, "fade_power %d\n" % 3)
367 # Use blenders lamp constant equivalent no attenuation.
368 # Using Custom curve for fade power 3 for now.
369 elif lamp.falloff_type == "CUSTOM_CURVE":
370 tab_write(file, "fade_power %d\n" % 4)
372 write_matrix(file, matrix)
374 tab_write(file, "}\n")
376 # v(A,B) rotates vector A about origin by vector B.
377 file.write(
378 "#declare lampTarget%s= vrotate(<%.4g,%.4g,%.4g>,<%.4g,%.4g,%.4g>);\n"
380 exported_lights_count,
381 -ob.location.x,
382 -ob.location.y,
383 -ob.location.z,
384 ob.rotation_euler.x,
385 ob.rotation_euler.y,
386 ob.rotation_euler.z,
391 def export_world(file, world, scene, global_matrix, tab_write):
392 """write world as POV background and sky_sphere to exported file"""
393 render = scene.pov
394 agnosticrender = scene.render
395 camera = scene.camera
396 # matrix = global_matrix @ camera.matrix_world # view dependant for later use NOT USED
397 if not world:
398 return
400 # These lines added to get sky gradient (visible with PNG output)
402 # For simple flat background:
403 if not world.pov.use_sky_blend:
404 # No alpha with Sky option:
405 if render.alpha_mode == "SKY" and not agnosticrender.film_transparent:
406 tab_write(
407 file, "background {rgbt<%.3g, %.3g, %.3g, 0>}\n" % (world.pov.horizon_color[:])
410 elif render.alpha_mode == "STRAIGHT" or agnosticrender.film_transparent:
411 tab_write(
412 file, "background {rgbt<%.3g, %.3g, %.3g, 1>}\n" % (world.pov.horizon_color[:])
414 else:
415 # Non fully transparent background could premultiply alpha and avoid
416 # anti-aliasing display issue
417 tab_write(
418 file,
419 "background {rgbft<%.3g, %.3g, %.3g, %.3g, 0>}\n"
421 world.pov.horizon_color[0],
422 world.pov.horizon_color[1],
423 world.pov.horizon_color[2],
424 render.alpha_filter,
428 world_tex_count = 0
429 # For Background image textures
430 for t in world.pov_texture_slots: # risk to write several sky_spheres but maybe ok.
431 if t:
432 tex = bpy.data.textures[t.texture]
433 if tex.type is not None:
434 world_tex_count += 1
435 # XXX No enable checkbox for world textures yet (report it?)
436 # if t and tex.type == 'IMAGE' and t.use:
437 if tex.type == "IMAGE":
438 image_filename = path_image(tex.image)
439 if tex.image.filepath != image_filename:
440 tex.image.filepath = image_filename
441 if image_filename != "" and t.use_map_blend:
442 textures_blend = image_filename
443 # colvalue = t.default_value
444 t_blend = t
446 # Commented below was an idea to make the Background image oriented as camera
447 # taken here:
448 # http://news.pov.org/pov.newusers/thread/%3Cweb.4a5cddf4e9c9822ba2f93e20@news.pov.org%3E/
449 # Replace 4/3 by the ratio of each image found by some custom or existing
450 # function
451 # mapping_blend = (" translate <%.4g,%.4g,%.4g> rotate z*degrees" \
452 # "(atan((camLocation - camLookAt).x/(camLocation - " \
453 # "camLookAt).y)) rotate x*degrees(atan((camLocation - " \
454 # "camLookAt).y/(camLocation - camLookAt).z)) rotate y*" \
455 # "degrees(atan((camLocation - camLookAt).z/(camLocation - " \
456 # "camLookAt).x)) scale <%.4g,%.4g,%.4g>b" % \
457 # (t_blend.offset.x / 10 , t_blend.offset.y / 10 ,
458 # t_blend.offset.z / 10, t_blend.scale.x ,
459 # t_blend.scale.y , t_blend.scale.z))
460 # using camera rotation valuesdirectly from blender seems much easier
461 if t_blend.texture_coords == "ANGMAP":
462 mapping_blend = ""
463 else:
464 # POV-Ray "scale" is not a number of repetitions factor, but its
465 # inverse, a standard scale factor.
466 # 0.5 Offset is needed relatively to scale because center of the
467 # UV scale is 0.5,0.5 in blender and 0,0 in POV
468 # Further Scale by 2 and translate by -1 are
469 # required for the sky_sphere not to repeat
471 mapping_blend = (
472 "scale 2 scale <%.4g,%.4g,%.4g> translate -1 "
473 "translate <%.4g,%.4g,%.4g> rotate<0,0,0> "
475 (1.0 / t_blend.scale.x),
476 (1.0 / t_blend.scale.y),
477 (1.0 / t_blend.scale.z),
478 0.5 - (0.5 / t_blend.scale.x) - t_blend.offset.x,
479 0.5 - (0.5 / t_blend.scale.y) - t_blend.offset.y,
480 t_blend.offset.z,
484 # The initial position and rotation of the pov camera is probably creating
485 # the rotation offset should look into it someday but at least background
486 # won't rotate with the camera now.
487 # Putting the map on a plane would not introduce the skysphere distortion and
488 # allow for better image scale matching but also some waay to chose depth and
489 # size of the plane relative to camera.
490 tab_write(file, "sky_sphere {\n")
491 tab_write(file, "pigment {\n")
492 tab_write(
493 file,
494 'image_map{%s "%s" %s}\n'
495 % (image_format(textures_blend), textures_blend, img_map_bg(t_blend)),
497 tab_write(file, "}\n")
498 tab_write(file, "%s\n" % mapping_blend)
499 # The following layered pigment opacifies to black over the texture for
500 # transmit below 1 or otherwise adds to itself
501 tab_write(file, "pigment {rgb 0 transmit %s}\n" % tex.intensity)
502 tab_write(file, "}\n")
503 # tab_write(file, "scale 2\n")
504 # tab_write(file, "translate -1\n")
506 # For only Background gradient
508 if world_tex_count == 0 and world.pov.use_sky_blend:
509 tab_write(file, "sky_sphere {\n")
510 tab_write(file, "pigment {\n")
511 # maybe Should follow the advice of POV doc about replacing gradient
512 # for skysphere..5.5
513 tab_write(file, "gradient y\n")
514 tab_write(file, "color_map {\n")
516 if render.alpha_mode == "TRANSPARENT":
517 tab_write(
518 file,
519 "[0.0 rgbft<%.3g, %.3g, %.3g, %.3g, 0>]\n"
521 world.pov.horizon_color[0],
522 world.pov.horizon_color[1],
523 world.pov.horizon_color[2],
524 render.alpha_filter,
527 tab_write(
528 file,
529 "[1.0 rgbft<%.3g, %.3g, %.3g, %.3g, 0>]\n"
531 world.pov.zenith_color[0],
532 world.pov.zenith_color[1],
533 world.pov.zenith_color[2],
534 render.alpha_filter,
537 if agnosticrender.film_transparent or render.alpha_mode == "STRAIGHT":
538 tab_write(file, "[0.0 rgbt<%.3g, %.3g, %.3g, 0.99>]\n" % (world.pov.horizon_color[:]))
539 # aa premult not solved with transmit 1
540 tab_write(file, "[1.0 rgbt<%.3g, %.3g, %.3g, 0.99>]\n" % (world.pov.zenith_color[:]))
541 else:
542 tab_write(file, "[0.0 rgbt<%.3g, %.3g, %.3g, 0>]\n" % (world.pov.horizon_color[:]))
543 tab_write(file, "[1.0 rgbt<%.3g, %.3g, %.3g, 0>]\n" % (world.pov.zenith_color[:]))
544 tab_write(file, "}\n")
545 tab_write(file, "}\n")
546 tab_write(file, "}\n")
547 # Sky_sphere alpha (transmit) is not translating into image alpha the same
548 # way as 'background'
550 # if world.pov.light_settings.use_indirect_light:
551 # scene.pov.radio_enable=1
553 # Maybe change the above to a function copyInternalRenderer settings when
554 # user pushes a button, then:
555 # scene.pov.radio_enable = world.pov.light_settings.use_indirect_light
556 # and other such translations but maybe this would not be allowed either?
558 # -----------------------------------------------------------------------------
560 mist = world.mist_settings
562 if mist.use_mist:
563 tab_write(file, "fog {\n")
564 if mist.falloff == "LINEAR":
565 tab_write(file, "distance %.6f\n" % ((mist.start + mist.depth) * 0.368))
566 elif mist.falloff in ["QUADRATIC", "INVERSE_QUADRATIC"]: # n**2 or squrt(n)?
567 tab_write(file, "distance %.6f\n" % ((mist.start + mist.depth) ** 2 * 0.368))
568 tab_write(
569 file,
570 "color rgbt<%.3g, %.3g, %.3g, %.3g>\n"
571 % (*world.pov.horizon_color, (1.0 - mist.intensity)),
573 # tab_write(file, "fog_offset %.6f\n" % mist.start) #create a pov property to prepend
574 # tab_write(file, "fog_alt %.6f\n" % mist.height) #XXX right?
575 # tab_write(file, "turbulence 0.2\n")
576 # tab_write(file, "turb_depth 0.3\n")
577 tab_write(file, "fog_type 1\n") # type2 for height
578 tab_write(file, "}\n")
579 if scene.pov.media_enable:
580 tab_write(file, "media {\n")
581 tab_write(
582 file,
583 "scattering { %d, rgb %.12f*<%.4g, %.4g, %.4g>\n"
585 int(scene.pov.media_scattering_type),
586 scene.pov.media_diffusion_scale,
587 *(scene.pov.media_diffusion_color[:]),
590 if scene.pov.media_scattering_type == "5":
591 tab_write(file, "eccentricity %.3g\n" % scene.pov.media_eccentricity)
592 tab_write(file, "}\n")
593 tab_write(
594 file,
595 "absorption %.12f*<%.4g, %.4g, %.4g>\n"
596 % (scene.pov.media_absorption_scale, *(scene.pov.media_absorption_color[:])),
598 tab_write(file, "\n")
599 tab_write(file, "samples %.d\n" % scene.pov.media_samples)
600 tab_write(file, "}\n")
603 # -----------------------------------------------------------------------------
604 def export_rainbows(rainbows, file, scene, global_matrix, tab_write):
605 """write all POV rainbows primitives to exported file"""
607 from .render import write_matrix, tab_write
609 pov_mat_name = "Default_texture"
610 for ob in rainbows:
611 povdataname = ob.data.name # enough? XXX not used nor matrix fn?
612 angle = degrees(ob.data.spot_size / 2.5) # radians in blender (2
613 width = ob.data.spot_blend * 10
614 distance = ob.data.shadow_buffer_clip_start
615 # eps=0.0000001
616 # angle = br/(cr+eps) * 10 #eps is small epsilon variable to avoid dividing by zero
617 # width = ob.dimensions[2] #now let's say width of rainbow is the actual proxy height
618 # formerly:
619 # cz-bz # let's say width of the rainbow is height of the cone (interfacing choice
621 # v(A,B) rotates vector A about origin by vector B.
622 # and avoid a 0 length vector by adding 1
624 # file.write("#declare %s_Target= vrotate(<%.6g,%.6g,%.6g>,<%.4g,%.4g,%.4g>);\n" % \
625 # (povdataname, -(ob.location.x+0.1), -(ob.location.y+0.1), -(ob.location.z+0.1),
626 # ob.rotation_euler.x, ob.rotation_euler.y, ob.rotation_euler.z))
628 direction = ( # XXX currently not used (replaced by track to?)
629 ob.location.x,
630 ob.location.y,
631 ob.location.z,
632 ) # not taking matrix into account
633 rmatrix = global_matrix @ ob.matrix_world
635 # ob.rotation_euler.to_matrix().to_4x4() * mathutils.Vector((0,0,1))
636 # XXX Is result of the below offset by 90 degrees?
637 up = ob.matrix_world.to_3x3()[1].xyz # * global_matrix
639 # XXX TO CHANGE:
640 # formerly:
641 # tab_write(file, "#declare %s = rainbow {\n"%povdataname)
643 # clumsy for now but remove the rainbow from instancing
644 # system because not an object. use lamps later instead of meshes
646 # del data_ref[dataname]
647 tab_write(file, "rainbow {\n")
649 tab_write(file, "angle %.4f\n" % angle)
650 tab_write(file, "width %.4f\n" % width)
651 tab_write(file, "distance %.4f\n" % distance)
652 tab_write(file, "arc_angle %.4f\n" % ob.pov.arc_angle)
653 tab_write(file, "falloff_angle %.4f\n" % ob.pov.falloff_angle)
654 tab_write(file, "direction <%.4f,%.4f,%.4f>\n" % rmatrix.translation[:])
655 tab_write(file, "up <%.4f,%.4f,%.4f>\n" % (up[0], up[1], up[2]))
656 tab_write(file, "color_map {\n")
657 tab_write(file, "[0.000 color srgbt<1.0, 0.5, 1.0, 1.0>]\n")
658 tab_write(file, "[0.130 color srgbt<0.5, 0.5, 1.0, 0.9>]\n")
659 tab_write(file, "[0.298 color srgbt<0.2, 0.2, 1.0, 0.7>]\n")
660 tab_write(file, "[0.412 color srgbt<0.2, 1.0, 1.0, 0.4>]\n")
661 tab_write(file, "[0.526 color srgbt<0.2, 1.0, 0.2, 0.4>]\n")
662 tab_write(file, "[0.640 color srgbt<1.0, 1.0, 0.2, 0.4>]\n")
663 tab_write(file, "[0.754 color srgbt<1.0, 0.5, 0.2, 0.6>]\n")
664 tab_write(file, "[0.900 color srgbt<1.0, 0.2, 0.2, 0.7>]\n")
665 tab_write(file, "[1.000 color srgbt<1.0, 0.2, 0.2, 1.0>]\n")
666 tab_write(file, "}\n")
668 # tab_write(file, "texture {%s}\n"%pov_mat_name)
669 write_object_modifiers(ob, file)
670 # tab_write(file, "rotate x*90\n")
671 # matrix = global_matrix @ ob.matrix_world
672 # write_matrix(file, matrix)
673 tab_write(file, "}\n")
674 # continue #Don't render proxy mesh, skip to next object
677 def export_smoke(file, smoke_obj_name, smoke_path, comments, global_matrix):
678 """export Blender smoke type fluids to pov media using df3 library"""
680 from .render import write_matrix, tab_write
682 flowtype = -1 # XXX todo: not used yet? should trigger emissive for fire type
683 depsgraph = bpy.context.evaluated_depsgraph_get()
684 smoke_obj = bpy.data.objects[smoke_obj_name].evaluated_get(depsgraph)
685 domain = None
686 smoke_modifier = None
687 # Search smoke domain target for smoke modifiers
688 for mod in smoke_obj.modifiers:
689 if mod.type == "FLUID":
690 if mod.fluid_type == "DOMAIN":
691 domain = smoke_obj
692 smoke_modifier = mod
694 elif mod.fluid_type == "FLOW":
695 if mod.flow_settings.flow_type == "BOTH":
696 flowtype = 2
697 elif mod.flow_settings.flow_type == "FIRE":
698 flowtype = 1
699 elif mod.flow_settings.flow_type == "SMOKE":
700 flowtype = 0
701 eps = 0.000001 # XXX not used currently. restore from corner case ... zero div?
702 if domain is not None:
703 mod_set = smoke_modifier.domain_settings
704 channeldata = []
705 for v in mod_set.density_grid:
706 channeldata.append(v.real)
707 print(v.real)
708 # -------- Usage in voxel texture:
709 # channeldata = []
710 # if channel == 'density':
711 # for v in mod_set.density_grid:
712 # channeldata.append(v.real)
714 # if channel == 'fire':
715 # for v in mod_set.flame_grid:
716 # channeldata.append(v.real)
718 resolution = mod_set.resolution_max
719 big_res = [
720 mod_set.domain_resolution[0],
721 mod_set.domain_resolution[1],
722 mod_set.domain_resolution[2],
725 if mod_set.use_noise:
726 big_res[0] = big_res[0] * (mod_set.noise_scale + 1)
727 big_res[1] = big_res[1] * (mod_set.noise_scale + 1)
728 big_res[2] = big_res[2] * (mod_set.noise_scale + 1)
729 # else:
730 # p = []
731 # -------- gather smoke domain settings
732 # BBox = domain.bound_box
733 # p.append([BBox[0][0], BBox[0][1], BBox[0][2]])
734 # p.append([BBox[6][0], BBox[6][1], BBox[6][2]])
735 # mod_set = smoke_modifier.domain_settings
736 # resolution = mod_set.resolution_max
737 # smokecache = mod_set.point_cache
738 # ret = read_cache(smokecache, mod_set.use_noise, mod_set.noise_scale + 1, flowtype)
739 # res_x = ret[0]
740 # res_y = ret[1]
741 # res_z = ret[2]
742 # density = ret[3]
743 # fire = ret[4]
745 # if res_x * res_y * res_z > 0:
746 # -------- new cache format
747 # big_res = []
748 # big_res.append(res_x)
749 # big_res.append(res_y)
750 # big_res.append(res_z)
751 # else:
752 # max = domain.dimensions[0]
753 # if (max - domain.dimensions[1]) < -eps:
754 # max = domain.dimensions[1]
756 # if (max - domain.dimensions[2]) < -eps:
757 # max = domain.dimensions[2]
759 # big_res = [int(round(resolution * domain.dimensions[0] / max, 0)),
760 # int(round(resolution * domain.dimensions[1] / max, 0)),
761 # int(round(resolution * domain.dimensions[2] / max, 0))]
763 # if mod_set.use_noise:
764 # big_res = [big_res[0] * (mod_set.noise_scale + 1),
765 # big_res[1] * (mod_set.noise_scale + 1),
766 # big_res[2] * (mod_set.noise_scale + 1)]
768 # if channel == 'density':
769 # channeldata = density
771 # if channel == 'fire':
772 # channeldata = fire
774 # sc_fr = '%s/%s/%s/%05d' % (
775 # efutil.export_path,
776 # efutil.scene_filename(),
777 # bpy.context.scene.name,
778 # bpy.context.scene.frame_current
780 # if not os.path.exists( sc_fr ):
781 # os.makedirs(sc_fr)
783 # smoke_filename = '%s.smoke' % bpy.path.clean_name(domain.name)
784 # smoke_path = '/'.join([sc_fr, smoke_filename])
786 # with open(smoke_path, 'wb') as smoke_file:
787 # # Binary densitygrid file format
789 # # File header
790 # smoke_file.write(b'SMOKE') #magic number
791 # smoke_file.write(struct.pack('<I', big_res[0]))
792 # smoke_file.write(struct.pack('<I', big_res[1]))
793 # smoke_file.write(struct.pack('<I', big_res[2]))
794 # Density data
795 # smoke_file.write(struct.pack('<%df'%len(channeldata), *channeldata))
797 # LuxLog('Binary SMOKE file written: %s' % (smoke_path))
799 # return big_res[0], big_res[1], big_res[2], channeldata
801 mydf3 = voxel_lib.df3(big_res[0], big_res[1], big_res[2])
802 sim_sizeX, sim_sizeY, sim_sizeZ = mydf3.size()
803 for x in range(sim_sizeX):
804 for y in range(sim_sizeY):
805 for z in range(sim_sizeZ):
806 mydf3.set(x, y, z, channeldata[((z * sim_sizeY + y) * sim_sizeX + x)])
807 try:
808 mydf3.exportDF3(smoke_path)
809 except ZeroDivisionError:
810 print("Show smoke simulation in 3D view before export")
811 print("Binary smoke.df3 file written in preview directory")
812 if comments:
813 file.write("\n//--Smoke--\n\n")
815 # Note: We start with a default unit cube.
816 # This is mandatory to read correctly df3 data - otherwise we could just directly use
817 # bbox coordinates from the start, and avoid scale/translate operations at the end...
818 file.write("box{<0,0,0>, <1,1,1>\n")
819 file.write(" pigment{ rgbt 1 }\n")
820 file.write(" hollow\n")
821 file.write(" interior{ //---------------------\n")
822 file.write(" media{ method 3\n")
823 file.write(" emission <1,1,1>*1\n") # 0>1 for dark smoke to white vapour
824 file.write(" scattering{ 1, // Type\n")
825 file.write(" <1,1,1>*0.1\n")
826 file.write(" } // end scattering\n")
827 file.write(' density{density_file df3 "%s"\n' % smoke_path)
828 file.write(" color_map {\n")
829 file.write(" [0.00 rgb 0]\n")
830 file.write(" [0.05 rgb 0]\n")
831 file.write(" [0.20 rgb 0.2]\n")
832 file.write(" [0.30 rgb 0.6]\n")
833 file.write(" [0.40 rgb 1]\n")
834 file.write(" [1.00 rgb 1]\n")
835 file.write(" } // end color_map\n")
836 file.write(" } // end of density\n")
837 file.write(" samples %i // higher = more precise\n" % resolution)
838 file.write(" } // end of media --------------------------\n")
839 file.write(" } // end of interior\n")
841 # START OF TRANSFORMATIONS
843 # Size to consider here are bbox dimensions (i.e. still in object space, *before* applying
844 # loc/rot/scale and other transformations (like parent stuff), aka matrix_world).
845 bbox = smoke_obj.bound_box
846 dim = [
847 abs(bbox[6][0] - bbox[0][0]),
848 abs(bbox[6][1] - bbox[0][1]),
849 abs(bbox[6][2] - bbox[0][2]),
852 # We scale our cube to get its final size and shapes but still in *object* space (same as Blender's bbox).
853 file.write("scale<%.6g,%.6g,%.6g>\n" % (dim[0], dim[1], dim[2]))
855 # We offset our cube such that (0,0,0) coordinate matches Blender's object center.
856 file.write("translate<%.6g,%.6g,%.6g>\n" % (bbox[0][0], bbox[0][1], bbox[0][2]))
858 # We apply object's transformations to get final loc/rot/size in world space!
859 # Note: we could combine the two previous transformations with this matrix directly...
860 write_matrix(file, global_matrix @ smoke_obj.matrix_world)
862 # END OF TRANSFORMATIONS
864 file.write("}\n")
866 # file.write(" interpolate 1\n")
867 # file.write(" frequency 0\n")
868 # file.write(" }\n")
869 # file.write("}\n")