Import_3ds: Improved distance cue node setup
[blender-addons.git] / render_povray / model_all.py
bloba8d8bdbdba73695c019e70d28e1cf91147d957a9
1 # SPDX-FileCopyrightText: 2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 """Translate to POV the control point compound geometries.
7 Such as polygon meshes or curve based shapes.
8 """
10 # --------
11 # -- Faster mesh export ...one day
12 # import numpy as np
13 # --------
14 import bpy
15 from . import texturing # for how textures influence shaders
16 from . import model_poly_topology
17 # from .texturing import local_material_names
18 from .scenography import export_smoke
21 def matrix_as_pov_string(matrix):
22 """Translate some transform matrix from Blender UI
23 to POV syntax and return that string"""
24 return (
25 "matrix <"
26 "%.6f, %.6f, %.6f, "
27 "%.6f, %.6f, %.6f, "
28 "%.6f, %.6f, %.6f, "
29 "%.6f, %.6f, %.6f"
30 ">\n"
31 % (
32 matrix[0][0],
33 matrix[1][0],
34 matrix[2][0],
35 matrix[0][1],
36 matrix[1][1],
37 matrix[2][1],
38 matrix[0][2],
39 matrix[1][2],
40 matrix[2][2],
41 matrix[0][3],
42 matrix[1][3],
43 matrix[2][3],
47 # objectNames = {}
48 DEF_OBJ_NAME = "Default"
51 def objects_loop(
52 file,
53 scene,
54 sel,
55 csg,
56 material_names_dictionary,
57 unpacked_images,
58 tab_level,
59 tab_write,
60 info_callback,
62 # global preview_dir
63 # global smoke_path
64 # global global_matrix
65 # global comments
67 # global tab
68 """write all meshes as POV mesh2{} syntax to exported file"""
69 # # some numpy functions to speed up mesh export NOT IN USE YET
70 # # Current 2.93 beta numpy linking has troubles so definitions commented off for now
72 # # TODO: also write a numpy function to read matrices at object level?
73 # # feed below with mesh object.data, but only after doing data.calc_loop_triangles()
74 # def read_verts_co(self, mesh):
75 # #'float64' would be a slower 64-bit floating-point number numpy datatype
76 # # using 'float32' vert coordinates for now until any issue is reported
77 # mverts_co = np.zeros((len(mesh.vertices) * 3), dtype=np.float32)
78 # mesh.vertices.foreach_get("co", mverts_co)
79 # return np.reshape(mverts_co, (len(mesh.vertices), 3))
81 # def read_verts_idx(self, mesh):
82 # mverts_idx = np.zeros((len(mesh.vertices)), dtype=np.int64)
83 # mesh.vertices.foreach_get("index", mverts_idx)
84 # return np.reshape(mverts_idx, (len(mesh.vertices), 1))
86 # def read_verts_norms(self, mesh):
87 # #'float64' would be a slower 64-bit floating-point number numpy datatype
88 # # using less accurate 'float16' normals for now until any issue is reported
89 # mverts_no = np.zeros((len(mesh.vertices) * 3), dtype=np.float16)
90 # mesh.vertices.foreach_get("normal", mverts_no)
91 # return np.reshape(mverts_no, (len(mesh.vertices), 3))
93 # def read_faces_idx(self, mesh):
94 # mfaces_idx = np.zeros((len(mesh.loop_triangles)), dtype=np.int64)
95 # mesh.loop_triangles.foreach_get("index", mfaces_idx)
96 # return np.reshape(mfaces_idx, (len(mesh.loop_triangles), 1))
98 # def read_faces_verts_indices(self, mesh):
99 # mfaces_verts_idx = np.zeros((len(mesh.loop_triangles) * 3), dtype=np.int64)
100 # mesh.loop_triangles.foreach_get("vertices", mfaces_verts_idx)
101 # return np.reshape(mfaces_verts_idx, (len(mesh.loop_triangles), 3))
103 # # Why is below different from vertex indices?
104 # def read_faces_verts_loops(self, mesh):
105 # mfaces_verts_loops = np.zeros((len(mesh.loop_triangles) * 3), dtype=np.int64)
106 # mesh.loop_triangles.foreach_get("loops", mfaces_verts_loops)
107 # return np.reshape(mfaces_verts_loops, (len(mesh.loop_triangles), 3))
109 # def read_faces_norms(self, mesh):
110 # #'float64' would be a slower 64-bit floating-point number numpy datatype
111 # # using less accurate 'float16' normals for now until any issue is reported
112 # mfaces_no = np.zeros((len(mesh.loop_triangles) * 3), dtype=np.float16)
113 # mesh.loop_triangles.foreach_get("normal", mfaces_no)
114 # return np.reshape(mfaces_no, (len(mesh.loop_triangles), 3))
116 # def read_faces_smooth(self, mesh):
117 # mfaces_smth = np.zeros((len(mesh.loop_triangles) * 1), dtype=np.bool)
118 # mesh.loop_triangles.foreach_get("use_smooth", mfaces_smth)
119 # return np.reshape(mfaces_smth, (len(mesh.loop_triangles), 1))
121 # def read_faces_material_indices(self, mesh):
122 # mfaces_mats_idx = np.zeros((len(mesh.loop_triangles)), dtype=np.int16)
123 # mesh.loop_triangles.foreach_get("material_index", mfaces_mats_idx)
124 # return np.reshape(mfaces_mats_idx, (len(mesh.loop_triangles), 1))
126 # obmatslist = []
127 # def hasUniqueMaterial():
128 # # Grab materials attached to object instances ...
129 # if hasattr(obj, 'material_slots'):
130 # for ms in obj.material_slots:
131 # if ms.material is not None and ms.link == 'OBJECT':
132 # if ms.material in obmatslist:
133 # return False
134 # else:
135 # obmatslist.append(ms.material)
136 # return True
137 # def hasObjectMaterial(obj):
138 # # Grab materials attached to object instances ...
139 # if hasattr(obj, 'material_slots'):
140 # for ms in obj.material_slots:
141 # if ms.material is not None and ms.link == 'OBJECT':
142 # # If there is at least one material slot linked to the object
143 # # and not the data (mesh), always create a new, "private" data instance.
144 # return True
145 # return False
146 # For objects using local material(s) only!
147 # This is a mapping between a tuple (dataname, material_names_dictionary, ...),
148 # and the POV dataname.
149 # As only objects using:
150 # * The same data.
151 # * EXACTLY the same materials, in EXACTLY the same sockets.
152 # ... can share a same instance in POV export.
153 from .render import (
154 string_strip_hyphen,
155 global_matrix,
156 tab,
157 comments,
159 from .render_core import (
160 preview_dir,
161 smoke_path,
163 from .model_primitives import write_object_modifiers
164 from .shading import write_object_material_interior
165 from .scenography import image_format, img_map, img_map_transforms
167 linebreaksinlists = scene.pov.list_lf_enable and not scene.pov.tempfiles_enable
168 obmats2data = {}
171 def check_object_materials(obj, obj_name, dataname):
172 """Compare other objects exported material slots to avoid rewriting duplicates"""
173 if hasattr(obj, "material_slots"):
174 has_local_mats = False
175 key = [dataname]
176 for ms in obj.material_slots:
177 if ms.material is not None:
178 key.append(ms.material.name)
179 if ms.link == "OBJECT" and not has_local_mats:
180 has_local_mats = True
181 else:
182 # Even if the slot is empty, it is important to grab it...
183 key.append("")
184 if has_local_mats:
185 # If this object uses local material(s), lets find if another object
186 # using the same data and exactly the same list of materials
187 # (in the same slots) has already been processed...
188 # Note that here also, we use object name as new, unique dataname for Pov.
189 key = tuple(key) # Lists are not hashable...
190 if key not in obmats2data:
191 obmats2data[key] = obj_name
192 return obmats2data[key]
193 return None
195 data_ref = {}
197 def store(scene, ob, name, dataname, matrix):
198 # The Object needs to be written at least once but if its data is
199 # already in data_ref this has already been done.
200 # This func returns the "povray" name of the data, or None
201 # if no writing is needed.
202 if ob.is_modified(scene, "RENDER"):
203 # Data modified.
204 # Create unique entry in data_ref by using object name
205 # (always unique in Blender) as data name.
206 data_ref[name] = [(name, matrix_as_pov_string(matrix))]
207 return name
208 # Here, we replace dataname by the value returned by check_object_materials, only if
209 # it is not evaluated to False (i.e. only if the object uses some local material(s)).
210 dataname = check_object_materials(ob, name, dataname) or dataname
211 if dataname in data_ref:
212 # Data already known, just add the object instance.
213 data_ref[dataname].append((name, matrix_as_pov_string(matrix)))
214 # No need to write data
215 return None
216 # Else (no return yet): Data not yet processed, create a new entry in data_ref.
217 data_ref[dataname] = [(name, matrix_as_pov_string(matrix))]
218 return dataname
220 ob_num = 0
221 depsgraph = bpy.context.evaluated_depsgraph_get()
222 for ob in sel:
223 # Using depsgraph
224 ob = bpy.data.objects[ob.name].evaluated_get(depsgraph)
226 # subtract original from the count of their instances as were not counted before 2.8
227 if (ob.is_instancer and ob.original != ob):
228 continue
230 ob_num += 1
232 # XXX I moved all those checks here, as there is no need to compute names
233 # for object we won't export here!
234 if ob.type in {
235 "LIGHT",
236 "CAMERA", # 'EMPTY', #empties can bear dupligroups
237 "META",
238 "ARMATURE",
239 "LATTICE",
241 continue
242 fluid_found = False
243 for mod in ob.modifiers:
244 if mod and hasattr(mod, "fluid_type"):
245 fluid_found = True
246 if mod.fluid_type == "DOMAIN":
247 if mod.domain_settings.domain_type == "GAS":
248 export_smoke(file, ob.name, smoke_path, comments, global_matrix)
249 break # don't render domain mesh, skip to next object.
250 if mod.fluid_type == "FLOW": # The domain contains all the smoke. so that's it.
251 if mod.flow_settings.flow_type == "SMOKE": # Check how liquids behave
252 break # don't render smoke flow emitter mesh either, skip to next object.
253 if fluid_found:
254 return
255 # No fluid found
256 if hasattr(ob, "particle_systems"):
257 # Importing function Export Hair
258 # here rather than at the top recommended for addons startup footprint
259 from .particles import export_hair
261 for p_sys in ob.particle_systems:
262 for particle_mod in [
264 for m in ob.modifiers
265 if (m is not None) and (m.type == "PARTICLE_SYSTEM")
267 if (
268 (p_sys.settings.render_type == "PATH")
269 and particle_mod.show_render
270 and (p_sys.name == particle_mod.particle_system.name)
272 export_hair(file, ob, particle_mod, p_sys, global_matrix)
273 if not ob.show_instancer_for_render:
274 continue # don't render emitter mesh, skip to next object.
276 # ------------------------------------------------
277 # Generating a name for object just like materials to be able to use it
278 # (baking for now or anything else).
279 # XXX I don't understand that if we are here, sel if a non-empty iterable,
280 # so this condition is always True, IMO -- mont29
281 # EMPTY type objects treated a little further below -- MR
283 # modified elif to if below as non EMPTY objects can also be instancers
284 if ob.is_instancer:
285 if ob.instance_type == "COLLECTION":
286 name_orig = "OB" + ob.name
287 dataname_orig = "DATA" + ob.instance_collection.name
288 else:
289 # hoping only dupligroups have several source datablocks
290 # ob_dupli_list_create(scene) #deprecated in 2.8
291 for eachduplicate in depsgraph.object_instances:
292 # Real dupli instance filtered because
293 # original included in list since 2.8
294 if eachduplicate.is_instance:
295 dataname_orig = "DATA" + eachduplicate.object.name
296 # obj.dupli_list_clear() #just don't store any reference to instance since 2.8
297 elif ob.data: # not an EMPTY type object
298 name_orig = "OB" + ob.name
299 dataname_orig = "DATA" + ob.data.name
300 elif ob.type == "EMPTY":
301 name_orig = "OB" + ob.name
302 dataname_orig = "DATA" + ob.name
303 else:
304 name_orig = DEF_OBJ_NAME
305 dataname_orig = DEF_OBJ_NAME
306 name = string_strip_hyphen(bpy.path.clean_name(name_orig))
307 dataname = string_strip_hyphen(bpy.path.clean_name(dataname_orig))
308 # for slot in obj.material_slots:
309 # if slot.material is not None and slot.link == 'OBJECT':
310 # obmaterial = slot.material
312 # ------------------------------------------------
314 if info_callback:
315 info_callback("Object %2.d of %2.d (%s)" % (ob_num, len(sel), ob.name))
317 me = ob.data
319 matrix = global_matrix @ ob.matrix_world
320 povdataname = store(scene, ob, name, dataname, matrix)
321 if povdataname is None:
322 print("This is an instance of " + name)
323 continue
325 print("Writing Down First Occurrence of " + name)
327 # ------------ Mesh Primitives ------------ #
328 # special export_curves() function takes care of writing
329 # lathe, sphere_sweep, birail, and loft except with modifiers
330 # converted to mesh
331 if not ob.is_modified(scene, "RENDER"):
332 if ob.type == "CURVE" and (
333 ob.pov.curveshape in {"lathe", "sphere_sweep", "loft"}
335 continue # Don't render proxy mesh, skip to next object
336 # pov_mat_name = "Default_texture" # Not used...remove?
338 # Implicit else-if (as not skipped by previous "continue")
339 # which itself has no "continue" (to combine custom pov code)?, so Keep this last.
340 # For originals, but not their instances, attempt to export mesh:
341 if not ob.is_instancer:
342 # except duplis which should be instances groups for now but all duplis later
343 if ob.type == "EMPTY":
344 # XXX Should we only write this once and instantiate the same for every
345 # empty in the final matrix writing, or even no matrix and just a comment
346 # with empty object transforms ?
347 tab_write(file, "\n//dummy sphere to represent Empty location\n")
348 tab_write(
349 file,
350 "#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n"
351 % povdataname,
353 continue # Don't render empty object but this is later addition, watch it.
354 if ob.pov.object_as:
355 pass
356 else:
357 model_poly_topology.export_mesh(file, ob, povdataname,
358 material_names_dictionary,
359 unpacked_images,
360 tab_level, tab_write, linebreaksinlists)
362 # ------------ Povray Primitives ------------ #
363 # Also implicit elif (continue) clauses and sorted after mesh
364 # as less often used.
365 if ob.pov.object_as == "PLANE":
366 tab_write(file, "#declare %s = plane{ <0,0,1>,0\n" % povdataname)
367 if ob.active_material:
368 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
369 try:
370 material = ob.active_material
371 write_object_material_interior(file, material, ob, tab_write)
372 except IndexError:
373 print(me)
374 # tab_write(file, "texture {%s}\n"%pov_mat_name)
375 write_object_modifiers(ob, file)
376 # tab_write(file, "rotate x*90\n")
377 tab_write(file, "}\n")
378 continue # Don't render proxy mesh, skip to next object
380 if ob.pov.object_as == "SPHERE":
382 tab_write(
383 file,
384 "#declare %s = sphere { 0,%6f\n" % (povdataname, ob.pov.sphere_radius),
386 if ob.active_material:
387 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
388 try:
389 material = ob.active_material
390 write_object_material_interior(file, material, ob, tab_write)
391 except IndexError:
392 print(me)
393 # tab_write(file, "texture {%s}\n"%pov_mat_name)
394 write_object_modifiers(ob, file)
395 # tab_write(file, "rotate x*90\n")
396 tab_write(file, "}\n")
397 continue # Don't render proxy mesh, skip to next object
399 if ob.pov.object_as == "BOX":
400 tab_write(file, "#declare %s = box { -1,1\n" % povdataname)
401 if ob.active_material:
402 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
403 try:
404 material = ob.active_material
405 write_object_material_interior(file, material, ob, tab_write)
406 except IndexError:
407 print(me)
408 # tab_write(file, "texture {%s}\n"%pov_mat_name)
409 write_object_modifiers(ob, file)
410 # tab_write(file, "rotate x*90\n")
411 tab_write(file, "}\n")
412 continue # Don't render proxy mesh, skip to next object
414 if ob.pov.object_as == "CONE":
415 br = ob.pov.cone_base_radius
416 cr = ob.pov.cone_cap_radius
417 bz = ob.pov.cone_base_z
418 cz = ob.pov.cone_cap_z
419 tab_write(
420 file,
421 "#declare %s = cone { <0,0,%.4f>,%.4f,<0,0,%.4f>,%.4f\n"
422 % (povdataname, bz, br, cz, cr),
424 if ob.active_material:
425 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
426 try:
427 material = ob.active_material
428 write_object_material_interior(file, material, ob, tab_write)
429 except IndexError:
430 print(me)
431 # tab_write(file, "texture {%s}\n"%pov_mat_name)
432 write_object_modifiers(ob, file)
433 # tab_write(file, "rotate x*90\n")
434 tab_write(file, "}\n")
435 continue # Don't render proxy mesh, skip to next object
437 if ob.pov.object_as == "CYLINDER":
438 r = ob.pov.cylinder_radius
439 x2 = ob.pov.cylinder_location_cap[0]
440 y2 = ob.pov.cylinder_location_cap[1]
441 z2 = ob.pov.cylinder_location_cap[2]
442 tab_write(
443 file,
444 "#declare %s = cylinder { <0,0,0>,<%6f,%6f,%6f>,%6f\n"
445 % (povdataname, x2, y2, z2, r),
447 if ob.active_material:
448 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
449 try:
450 material = ob.active_material
451 write_object_material_interior(file, material, ob, tab_write)
452 except IndexError:
453 print(me)
454 # tab_write(file, "texture {%s}\n"%pov_mat_name)
455 # cylinders written at origin, translated below
456 write_object_modifiers(ob, file)
457 # tab_write(file, "rotate x*90\n")
458 tab_write(file, "}\n")
459 continue # Don't render proxy mesh, skip to next object
461 if ob.pov.object_as == "HEIGHT_FIELD":
462 data = ""
463 filename = ob.pov.hf_filename
464 data += '"%s"' % filename
465 gamma = " gamma %.4f" % ob.pov.hf_gamma
466 data += gamma
467 if ob.pov.hf_premultiplied:
468 data += " premultiplied on"
469 if ob.pov.hf_smooth:
470 data += " smooth"
471 if ob.pov.hf_water > 0:
472 data += " water_level %.4f" % ob.pov.hf_water
473 # hierarchy = obj.pov.hf_hierarchy
474 tab_write(file, "#declare %s = height_field { %s\n" % (povdataname, data))
475 if ob.active_material:
476 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
477 try:
478 material = ob.active_material
479 write_object_material_interior(file, material, ob, tab_write)
480 except IndexError:
481 print(me)
482 # tab_write(file, "texture {%s}\n"%pov_mat_name)
483 write_object_modifiers(ob, file)
484 tab_write(file, "rotate x*90\n")
485 tab_write(file, "translate <-0.5,0.5,0>\n")
486 tab_write(file, "scale <0,-1,0>\n")
487 tab_write(file, "}\n")
488 continue # Don't render proxy mesh, skip to next object
490 if ob.pov.object_as == "TORUS":
491 tab_write(
492 file,
493 "#declare %s = torus { %.4f,%.4f\n"
495 povdataname,
496 ob.pov.torus_major_radius,
497 ob.pov.torus_minor_radius,
500 if ob.active_material:
501 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
502 try:
503 material = ob.active_material
504 write_object_material_interior(file, material, ob, tab_write)
505 except IndexError:
506 print(me)
507 # tab_write(file, "texture {%s}\n"%pov_mat_name)
508 write_object_modifiers(ob, file)
509 tab_write(file, "rotate x*90\n")
510 tab_write(file, "}\n")
511 continue # Don't render proxy mesh, skip to next object
513 if ob.pov.object_as == "PARAMETRIC":
514 tab_write(file, "#declare %s = parametric {\n" % povdataname)
515 tab_write(file, "function { %s }\n" % ob.pov.x_eq)
516 tab_write(file, "function { %s }\n" % ob.pov.y_eq)
517 tab_write(file, "function { %s }\n" % ob.pov.z_eq)
518 tab_write(
519 file,
520 "<%.4f,%.4f>, <%.4f,%.4f>\n"
521 % (ob.pov.u_min, ob.pov.v_min, ob.pov.u_max, ob.pov.v_max),
523 # Previous to 3.8 default max_gradient 1.0 was too slow
524 tab_write(file, "max_gradient 0.001\n")
525 if ob.pov.contained_by == "sphere":
526 tab_write(file, "contained_by { sphere{0, 2} }\n")
527 else:
528 tab_write(file, "contained_by { box{-2, 2} }\n")
529 tab_write(file, "max_gradient %.6f\n" % ob.pov.max_gradient)
530 tab_write(file, "accuracy %.6f\n" % ob.pov.accuracy)
531 tab_write(file, "precompute 10 x,y,z\n")
532 tab_write(file, "}\n")
533 continue # Don't render proxy mesh, skip to next object
535 if ob.pov.object_as == "ISOSURFACE_NODE":
536 tab_write(file, "#declare %s = isosurface{ \n" % povdataname)
537 tab_write(file, "function{ \n")
538 text_name = ob.pov.iso_function_text
539 if text_name:
540 node_tree = bpy.context.scene.node_tree
541 for node in node_tree.nodes:
542 if node.bl_idname == "IsoPropsNode" and node.label == ob.name:
543 for inp in node.inputs:
544 if inp:
545 tab_write(
546 file,
547 "#declare %s = %.6g;\n" % (inp.name, inp.default_value),
550 text = bpy.data.texts[text_name]
551 for line in text.lines:
552 split = line.body.split()
553 if split[0] != "#declare":
554 tab_write(file, "%s\n" % line.body)
555 else:
556 tab_write(file, "abs(x) - 2 + y")
557 tab_write(file, "}\n")
558 tab_write(file, "threshold %.6g\n" % ob.pov.threshold)
559 tab_write(file, "max_gradient %.6g\n" % ob.pov.max_gradient)
560 tab_write(file, "accuracy %.6g\n" % ob.pov.accuracy)
561 tab_write(file, "contained_by { ")
562 if ob.pov.contained_by == "sphere":
563 tab_write(file, "sphere {0,%.6g}}\n" % ob.pov.container_scale)
564 else:
565 tab_write(
566 file,
567 "box {-%.6g,%.6g}}\n"
568 % (ob.pov.container_scale, ob.pov.container_scale),
570 if ob.pov.all_intersections:
571 tab_write(file, "all_intersections\n")
572 else:
573 if ob.pov.max_trace > 1:
574 tab_write(file, "max_trace %.6g\n" % ob.pov.max_trace)
575 if ob.active_material:
576 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
577 try:
578 material = ob.active_material
579 write_object_material_interior(file, material, ob, tab_write)
580 except IndexError:
581 print(me)
582 # tab_write(file, "texture {%s}\n"%pov_mat_name)
583 tab_write(file, "scale %.6g\n" % (1 / ob.pov.container_scale))
584 tab_write(file, "}\n")
585 continue # Don't render proxy mesh, skip to next object
587 if ob.pov.object_as == "ISOSURFACE_VIEW":
588 simple_isosurface_function = ob.pov.isosurface_eq
589 if simple_isosurface_function:
590 tab_write(file, "#declare %s = isosurface{ \n" % povdataname)
591 tab_write(file, "function{ \n")
592 tab_write(file, simple_isosurface_function)
593 tab_write(file, "}\n")
594 tab_write(file, "threshold %.6g\n" % ob.pov.threshold)
595 tab_write(file, "max_gradient %.6g\n" % ob.pov.max_gradient)
596 tab_write(file, "accuracy %.6g\n" % ob.pov.accuracy)
597 tab_write(file, "contained_by { ")
598 if ob.pov.contained_by == "sphere":
599 tab_write(file, "sphere {0,%.6g}}\n" % ob.pov.container_scale)
600 else:
601 tab_write(
602 file,
603 "box {-%.6g,%.6g}}\n"
604 % (ob.pov.container_scale, ob.pov.container_scale),
606 if ob.pov.all_intersections:
607 tab_write(file, "all_intersections\n")
608 else:
609 if ob.pov.max_trace > 1:
610 tab_write(file, "max_trace %.6g\n" % ob.pov.max_trace)
611 if ob.active_material:
612 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
613 try:
614 material = ob.active_material
615 write_object_material_interior(file, material, ob, tab_write)
616 except IndexError:
617 print(me)
618 # tab_write(file, "texture {%s}\n"%pov_mat_name)
619 tab_write(file, "scale %.6g\n" % (1 / ob.pov.container_scale))
620 tab_write(file, "}\n")
621 continue # Don't render proxy mesh, skip to next object
623 if ob.pov.object_as == "SUPERELLIPSOID":
624 tab_write(
625 file,
626 "#declare %s = superellipsoid{ <%.4f,%.4f>\n"
627 % (povdataname, ob.pov.se_n2, ob.pov.se_n1),
629 if ob.active_material:
630 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
631 try:
632 material = ob.active_material
633 write_object_material_interior(file, material, ob, tab_write)
634 except IndexError:
635 print(me)
636 # tab_write(file, "texture {%s}\n"%pov_mat_name)
637 write_object_modifiers(ob, file)
638 tab_write(file, "}\n")
639 continue # Don't render proxy mesh, skip to next object
641 if ob.pov.object_as == "SUPERTORUS":
642 rad_maj = ob.pov.st_major_radius
643 rad_min = ob.pov.st_minor_radius
644 ring = ob.pov.st_ring
645 cross = ob.pov.st_cross
646 accuracy = ob.pov.st_accuracy
647 gradient = ob.pov.st_max_gradient
648 # --- Inline Supertorus macro
649 file.write(
650 "#macro Supertorus(RMj, RMn, MajorControl, MinorControl, Accuracy, MaxGradient)\n"
652 file.write(" #local CP = 2/MinorControl;\n")
653 file.write(" #local RP = 2/MajorControl;\n")
654 file.write(" isosurface {\n")
655 file.write(
656 " function { pow( pow(abs(pow(pow(abs(x),RP) + pow(abs(z),RP), 1/RP) - RMj),CP) + pow(abs(y),CP) ,1/CP) - RMn }\n"
658 file.write(" threshold 0\n")
659 file.write(
660 " contained_by {box {<-RMj-RMn,-RMn,-RMj-RMn>, < RMj+RMn, RMn, RMj+RMn>}}\n"
662 file.write(" #if(MaxGradient >= 1)\n")
663 file.write(" max_gradient MaxGradient\n")
664 file.write(" #else\n")
665 file.write(" evaluate 1, 10, 0.1\n")
666 file.write(" #end\n")
667 file.write(" accuracy Accuracy\n")
668 file.write(" }\n")
669 file.write("#end\n")
670 # ---
671 tab_write(
672 file,
673 "#declare %s = object{ Supertorus( %.4g,%.4g,%.4g,%.4g,%.4g,%.4g)\n"
675 povdataname,
676 rad_maj,
677 rad_min,
678 ring,
679 cross,
680 accuracy,
681 gradient,
684 if ob.active_material:
685 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
686 try:
687 material = ob.active_material
688 write_object_material_interior(file, material, ob, tab_write)
689 except IndexError:
690 print(me)
691 # tab_write(file, "texture {%s}\n"%pov_mat_name)
692 write_object_modifiers(ob, file)
693 tab_write(file, "rotate x*90\n")
694 tab_write(file, "}\n")
695 continue # Don't render proxy mesh, skip to next object
697 if ob.pov.object_as == "POLYCIRCLE":
698 # TODO write below macro Once:
699 # if write_polytocircle_macro_once == 0:
700 file.write("/****************************\n")
701 file.write("This macro was written by 'And'.\n")
702 file.write("Link:(http://news.povray.org/povray.binaries.scene-files/)\n")
703 file.write("****************************/\n")
704 file.write("//from math.inc:\n")
705 file.write("#macro VPerp_Adjust(V, Axis)\n")
706 file.write(" vnormalize(vcross(vcross(Axis, V), Axis))\n")
707 file.write("#end\n")
708 file.write("//Then for the actual macro\n")
709 file.write("#macro Shape_Slice_Plane_2P_1V(point1, point2, clip_direct)\n")
710 file.write("#local p1 = point1 + <0,0,0>;\n")
711 file.write("#local p2 = point2 + <0,0,0>;\n")
712 file.write("#local clip_v = vnormalize(clip_direct + <0,0,0>);\n")
713 file.write("#local direct_v1 = vnormalize(p2 - p1);\n")
714 file.write("#if(vdot(direct_v1, clip_v) = 1)\n")
715 file.write(' #error "Shape_Slice_Plane_2P_1V error: Can\'t decide plane"\n')
716 file.write("#end\n\n")
717 file.write(
718 "#local norm = -vnormalize(clip_v - direct_v1*vdot(direct_v1,clip_v));\n"
720 file.write("#local d = vdot(norm, p1);\n")
721 file.write("plane{\n")
722 file.write("norm, d\n")
723 file.write("}\n")
724 file.write("#end\n\n")
725 file.write("//polygon to circle\n")
726 file.write(
727 "#macro Shape_Polygon_To_Circle_Blending("
728 "_polygon_n, _side_face, "
729 "_polygon_circumscribed_radius, "
730 "_circle_radius, "
731 "_height)\n"
733 file.write("#local n = int(_polygon_n);\n")
734 file.write("#if(n < 3)\n")
735 file.write(" #error\n")
736 file.write("#end\n\n")
737 file.write("#local front_v = VPerp_Adjust(_side_face, z);\n")
738 file.write("#if(vdot(front_v, x) >= 0)\n")
739 file.write(" #local face_ang = acos(vdot(-y, front_v));\n")
740 file.write("#else\n")
741 file.write(" #local face_ang = -acos(vdot(-y, front_v));\n")
742 file.write("#end\n")
743 file.write("#local polyg_ext_ang = 2*pi/n;\n")
744 file.write("#local polyg_outer_r = _polygon_circumscribed_radius;\n")
745 file.write("#local polyg_inner_r = polyg_outer_r*cos(polyg_ext_ang/2);\n")
746 file.write("#local cycle_r = _circle_radius;\n")
747 file.write("#local h = _height;\n")
748 file.write("#if(polyg_outer_r < 0 | cycle_r < 0 | h <= 0)\n")
749 file.write(' #error "error: each side length must be positive"\n')
750 file.write("#end\n\n")
751 file.write("#local multi = 1000;\n")
752 file.write("#local poly_obj =\n")
753 file.write("polynomial{\n")
754 file.write("4,\n")
755 file.write("xyz(0,2,2): multi*1,\n")
756 file.write("xyz(2,0,1): multi*2*h,\n")
757 file.write("xyz(1,0,2): multi*2*(polyg_inner_r-cycle_r),\n")
758 file.write("xyz(2,0,0): multi*(-h*h),\n")
759 file.write("xyz(0,0,2): multi*(-pow(cycle_r - polyg_inner_r, 2)),\n")
760 file.write("xyz(1,0,1): multi*2*h*(-2*polyg_inner_r + cycle_r),\n")
761 file.write("xyz(1,0,0): multi*2*h*h*polyg_inner_r,\n")
762 file.write("xyz(0,0,1): multi*2*h*polyg_inner_r*(polyg_inner_r - cycle_r),\n")
763 file.write("xyz(0,0,0): multi*(-pow(polyg_inner_r*h, 2))\n")
764 file.write("sturm\n")
765 file.write("}\n\n")
766 file.write("#local mockup1 =\n")
767 file.write("difference{\n")
768 file.write(" cylinder{\n")
769 file.write(" <0,0,0.0>,<0,0,h>, max(polyg_outer_r, cycle_r)\n")
770 file.write(" }\n\n")
771 file.write(" #for(i, 0, n-1)\n")
772 file.write(" object{\n")
773 file.write(" poly_obj\n")
774 file.write(" inverse\n")
775 file.write(" rotate <0, 0, -90 + degrees(polyg_ext_ang*i)>\n")
776 file.write(" }\n")
777 file.write(" object{\n")
778 file.write(
779 " Shape_Slice_Plane_2P_1V(<polyg_inner_r,0,0>,<cycle_r,0,h>,x)\n"
781 file.write(" rotate <0, 0, -90 + degrees(polyg_ext_ang*i)>\n")
782 file.write(" }\n")
783 file.write(" #end\n")
784 file.write("}\n\n")
785 file.write("object{\n")
786 file.write("mockup1\n")
787 file.write("rotate <0, 0, degrees(face_ang)>\n")
788 file.write("}\n")
789 file.write("#end\n")
790 # Use the macro
791 ngon = ob.pov.polytocircle_ngon
792 ngonR = ob.pov.polytocircle_ngonR
793 circleR = ob.pov.polytocircle_circleR
794 tab_write(
795 file,
796 "#declare %s = object { Shape_Polygon_To_Circle_Blending("
797 "%s, z, %.4f, %.4f, 2) rotate x*180 translate z*1\n"
798 % (povdataname, ngon, ngonR, circleR),
800 tab_write(file, "}\n")
801 continue # Don't render proxy mesh, skip to next object
802 if csg:
803 # fluid_found early return no longer runs this
804 # todo maybe make a function to run in that other branch
805 duplidata_ref = []
806 _dupnames_seen = {} # avoid duplicate output during introspection
807 for ob in sel:
808 # matrix = global_matrix @ obj.matrix_world
809 if ob.is_instancer:
810 tab_write(file, "\n//--DupliObjects in %s--\n\n" % ob.name)
811 # obj.dupli_list_create(scene) #deprecated in 2.8
812 dup = ""
813 if ob.is_modified(scene, "RENDER"):
814 # modified object always unique so using object name rather than data name
815 dup = "#declare OB%s = union{\n" % (
816 string_strip_hyphen(bpy.path.clean_name(ob.name))
818 else:
819 dup = "#declare DATA%s = union{\n" % (
820 string_strip_hyphen(bpy.path.clean_name(ob.name))
823 for eachduplicate in depsgraph.object_instances:
824 if (
825 eachduplicate.is_instance
826 ): # Real dupli instance filtered because original included in list since 2.8
827 _dupname = eachduplicate.object.name
828 _dupobj = bpy.data.objects[_dupname]
829 # BEGIN introspection for troubleshooting purposes
830 if "name" not in dir(_dupobj.data):
831 if _dupname not in _dupnames_seen:
832 print(
833 "WARNING: bpy.data.objects[%s].data (of type %s) has no 'name' attribute"
834 % (_dupname, type(_dupobj.data))
836 for _thing in dir(_dupobj):
837 print(
838 "|| %s.%s = %s"
839 % (_dupname, _thing, getattr(_dupobj, _thing))
841 _dupnames_seen[_dupname] = 1
842 print("''=> Unparseable objects so far: %s" % _dupnames_seen)
843 else:
844 _dupnames_seen[_dupname] += 1
845 continue # don't try to parse data objects with no name attribute
846 # END introspection for troubleshooting purposes
847 duplidataname = "OB" + string_strip_hyphen(
848 bpy.path.clean_name(_dupobj.data.name)
850 dupmatrix = (
851 eachduplicate.matrix_world.copy()
852 ) # has to be copied to not store instance since 2.8
853 dup += "\tobject {\n\t\tDATA%s\n\t\t%s\t}\n" % (
854 string_strip_hyphen(bpy.path.clean_name(_dupobj.data.name)),
855 matrix_as_pov_string(ob.matrix_world.inverted() @ dupmatrix),
857 # add object to a list so that it is not rendered for some instance_types
858 if (
859 ob.instance_type != "COLLECTION"
860 and duplidataname not in duplidata_ref
862 duplidata_ref.append(
863 duplidataname,
864 ) # older key [string_strip_hyphen(bpy.path.clean_name("OB"+obj.name))]
865 dup += "}\n"
866 # obj.dupli_list_clear()# just do not store any reference to instance since 2.8
867 tab_write(file, dup)
868 else:
869 continue
870 if _dupnames_seen:
871 print("WARNING: Unparseable objects in current .blend file:\n''--> %s" % _dupnames_seen)
872 if duplidata_ref:
873 print("duplidata_ref = %s" % duplidata_ref)
874 for data_name, inst in data_ref.items():
875 for ob_name, matrix_str in inst:
876 if ob_name not in duplidata_ref: # .items() for a dictionary
877 tab_write(file, "\n//----Blender Object Name: %s----\n" %
878 ob_name.removeprefix("OB"))
879 if ob.pov.object_as == "":
880 tab_write(file, "object { \n")
881 tab_write(file, "%s\n" % data_name)
882 tab_write(file, "%s\n" % matrix_str)
883 tab_write(file, "}\n")
884 else:
885 no_boolean = True
886 for mod in ob.modifiers:
887 if mod.type == "BOOLEAN":
888 operation = None
889 no_boolean = False
890 if mod.operation == "INTERSECT":
891 operation = "intersection"
892 else:
893 operation = mod.operation.lower()
894 mod_ob_name = string_strip_hyphen(
895 bpy.path.clean_name(mod.object.name)
897 mod_matrix = global_matrix @ mod.object.matrix_world
898 mod_ob_matrix = matrix_as_pov_string(mod_matrix)
899 tab_write(file, "%s { \n" % operation)
900 tab_write(file, "object { \n")
901 tab_write(file, "%s\n" % data_name)
902 tab_write(file, "%s\n" % matrix_str)
903 tab_write(file, "}\n")
904 tab_write(file, "object { \n")
905 tab_write(file, "%s\n" % ("DATA" + mod_ob_name))
906 tab_write(file, "%s\n" % mod_ob_matrix)
907 tab_write(file, "}\n")
908 tab_write(file, "}\n")
909 break
910 if no_boolean:
911 tab_write(file, "object { \n")
912 tab_write(file, "%s\n" % data_name)
913 tab_write(file, "%s\n" % matrix_str)
914 tab_write(file, "}\n")