1 # SPDX-License-Identifier: GPL-2.0-or-later
3 """Translate to POV the control point compound geometries.
5 Such as polygon meshes or curve based shapes.
9 # -- Faster mesh export ...one day
13 from . import texturing
# for how textures influence shaders
14 from . import model_poly_topology
15 # from .texturing import local_material_names
16 from .scenography
import export_smoke
19 def matrix_as_pov_string(matrix
):
20 """Translate some transform matrix from Blender UI
21 to POV syntax and return that string"""
46 DEF_OBJ_NAME
= "Default"
54 material_names_dictionary
,
62 # global global_matrix
66 """write all meshes as POV mesh2{} syntax to exported file"""
67 # # some numpy functions to speed up mesh export NOT IN USE YET
68 # # Current 2.93 beta numpy linking has troubles so definitions commented off for now
70 # # TODO: also write a numpy function to read matrices at object level?
71 # # feed below with mesh object.data, but only after doing data.calc_loop_triangles()
72 # def read_verts_co(self, mesh):
73 # #'float64' would be a slower 64-bit floating-point number numpy datatype
74 # # using 'float32' vert coordinates for now until any issue is reported
75 # mverts_co = np.zeros((len(mesh.vertices) * 3), dtype=np.float32)
76 # mesh.vertices.foreach_get("co", mverts_co)
77 # return np.reshape(mverts_co, (len(mesh.vertices), 3))
79 # def read_verts_idx(self, mesh):
80 # mverts_idx = np.zeros((len(mesh.vertices)), dtype=np.int64)
81 # mesh.vertices.foreach_get("index", mverts_idx)
82 # return np.reshape(mverts_idx, (len(mesh.vertices), 1))
84 # def read_verts_norms(self, mesh):
85 # #'float64' would be a slower 64-bit floating-point number numpy datatype
86 # # using less accurate 'float16' normals for now until any issue is reported
87 # mverts_no = np.zeros((len(mesh.vertices) * 3), dtype=np.float16)
88 # mesh.vertices.foreach_get("normal", mverts_no)
89 # return np.reshape(mverts_no, (len(mesh.vertices), 3))
91 # def read_faces_idx(self, mesh):
92 # mfaces_idx = np.zeros((len(mesh.loop_triangles)), dtype=np.int64)
93 # mesh.loop_triangles.foreach_get("index", mfaces_idx)
94 # return np.reshape(mfaces_idx, (len(mesh.loop_triangles), 1))
96 # def read_faces_verts_indices(self, mesh):
97 # mfaces_verts_idx = np.zeros((len(mesh.loop_triangles) * 3), dtype=np.int64)
98 # mesh.loop_triangles.foreach_get("vertices", mfaces_verts_idx)
99 # return np.reshape(mfaces_verts_idx, (len(mesh.loop_triangles), 3))
101 # # Why is below different from vertex indices?
102 # def read_faces_verts_loops(self, mesh):
103 # mfaces_verts_loops = np.zeros((len(mesh.loop_triangles) * 3), dtype=np.int64)
104 # mesh.loop_triangles.foreach_get("loops", mfaces_verts_loops)
105 # return np.reshape(mfaces_verts_loops, (len(mesh.loop_triangles), 3))
107 # def read_faces_norms(self, mesh):
108 # #'float64' would be a slower 64-bit floating-point number numpy datatype
109 # # using less accurate 'float16' normals for now until any issue is reported
110 # mfaces_no = np.zeros((len(mesh.loop_triangles) * 3), dtype=np.float16)
111 # mesh.loop_triangles.foreach_get("normal", mfaces_no)
112 # return np.reshape(mfaces_no, (len(mesh.loop_triangles), 3))
114 # def read_faces_smooth(self, mesh):
115 # mfaces_smth = np.zeros((len(mesh.loop_triangles) * 1), dtype=np.bool)
116 # mesh.loop_triangles.foreach_get("use_smooth", mfaces_smth)
117 # return np.reshape(mfaces_smth, (len(mesh.loop_triangles), 1))
119 # def read_faces_material_indices(self, mesh):
120 # mfaces_mats_idx = np.zeros((len(mesh.loop_triangles)), dtype=np.int16)
121 # mesh.loop_triangles.foreach_get("material_index", mfaces_mats_idx)
122 # return np.reshape(mfaces_mats_idx, (len(mesh.loop_triangles), 1))
125 # def hasUniqueMaterial():
126 # # Grab materials attached to object instances ...
127 # if hasattr(obj, 'material_slots'):
128 # for ms in obj.material_slots:
129 # if ms.material is not None and ms.link == 'OBJECT':
130 # if ms.material in obmatslist:
133 # obmatslist.append(ms.material)
135 # def hasObjectMaterial(obj):
136 # # Grab materials attached to object instances ...
137 # if hasattr(obj, 'material_slots'):
138 # for ms in obj.material_slots:
139 # if ms.material is not None and ms.link == 'OBJECT':
140 # # If there is at least one material slot linked to the object
141 # # and not the data (mesh), always create a new, "private" data instance.
144 # For objects using local material(s) only!
145 # This is a mapping between a tuple (dataname, material_names_dictionary, ...),
146 # and the POV dataname.
147 # As only objects using:
149 # * EXACTLY the same materials, in EXACTLY the same sockets.
150 # ... can share a same instance in POV export.
151 from .render
import (
157 from .render_core
import (
161 from .model_primitives
import write_object_modifiers
162 from .shading
import write_object_material_interior
163 from .scenography
import image_format
, img_map
, img_map_transforms
165 linebreaksinlists
= scene
.pov
.list_lf_enable
and not scene
.pov
.tempfiles_enable
169 def check_object_materials(obj
, obj_name
, dataname
):
170 """Compare other objects exported material slots to avoid rewriting duplicates"""
171 if hasattr(obj
, "material_slots"):
172 has_local_mats
= False
174 for ms
in obj
.material_slots
:
175 if ms
.material
is not None:
176 key
.append(ms
.material
.name
)
177 if ms
.link
== "OBJECT" and not has_local_mats
:
178 has_local_mats
= True
180 # Even if the slot is empty, it is important to grab it...
183 # If this object uses local material(s), lets find if another object
184 # using the same data and exactly the same list of materials
185 # (in the same slots) has already been processed...
186 # Note that here also, we use object name as new, unique dataname for Pov.
187 key
= tuple(key
) # Lists are not hashable...
188 if key
not in obmats2data
:
189 obmats2data
[key
] = obj_name
190 return obmats2data
[key
]
195 def store(scene
, ob
, name
, dataname
, matrix
):
196 # The Object needs to be written at least once but if its data is
197 # already in data_ref this has already been done.
198 # This func returns the "povray" name of the data, or None
199 # if no writing is needed.
200 if ob
.is_modified(scene
, "RENDER"):
202 # Create unique entry in data_ref by using object name
203 # (always unique in Blender) as data name.
204 data_ref
[name
] = [(name
, matrix_as_pov_string(matrix
))]
206 # Here, we replace dataname by the value returned by check_object_materials, only if
207 # it is not evaluated to False (i.e. only if the object uses some local material(s)).
208 dataname
= check_object_materials(ob
, name
, dataname
) or dataname
209 if dataname
in data_ref
:
210 # Data already known, just add the object instance.
211 data_ref
[dataname
].append((name
, matrix_as_pov_string(matrix
)))
212 # No need to write data
214 # Else (no return yet): Data not yet processed, create a new entry in data_ref.
215 data_ref
[dataname
] = [(name
, matrix_as_pov_string(matrix
))]
219 depsgraph
= bpy
.context
.evaluated_depsgraph_get()
222 ob
= bpy
.data
.objects
[ob
.name
].evaluated_get(depsgraph
)
224 # subtract original from the count of their instances as were not counted before 2.8
225 if (ob
.is_instancer
and ob
.original
!= ob
):
230 # XXX I moved all those checks here, as there is no need to compute names
231 # for object we won't export here!
234 "CAMERA", # 'EMPTY', #empties can bear dupligroups
241 for mod
in ob
.modifiers
:
242 if mod
and hasattr(mod
, "fluid_type"):
244 if mod
.fluid_type
== "DOMAIN":
245 if mod
.domain_settings
.domain_type
== "GAS":
246 export_smoke(file, ob
.name
, smoke_path
, comments
, global_matrix
)
247 break # don't render domain mesh, skip to next object.
248 if mod
.fluid_type
== "FLOW": # The domain contains all the smoke. so that's it.
249 if mod
.flow_settings
.flow_type
== "SMOKE": # Check how liquids behave
250 break # don't render smoke flow emitter mesh either, skip to next object.
254 if hasattr(ob
, "particle_systems"):
255 # Importing function Export Hair
256 # here rather than at the top recommended for addons startup footprint
257 from .particles
import export_hair
259 for p_sys
in ob
.particle_systems
:
260 for particle_mod
in [
262 for m
in ob
.modifiers
263 if (m
is not None) and (m
.type == "PARTICLE_SYSTEM")
266 (p_sys
.settings
.render_type
== "PATH")
267 and particle_mod
.show_render
268 and (p_sys
.name
== particle_mod
.particle_system
.name
)
270 export_hair(file, ob
, particle_mod
, p_sys
, global_matrix
)
271 if not ob
.show_instancer_for_render
:
272 continue # don't render emitter mesh, skip to next object.
274 # ------------------------------------------------
275 # Generating a name for object just like materials to be able to use it
276 # (baking for now or anything else).
277 # XXX I don't understand that if we are here, sel if a non-empty iterable,
278 # so this condition is always True, IMO -- mont29
279 # EMPTY type objects treated a little further below -- MR
281 # modified elif to if below as non EMPTY objects can also be instancers
283 if ob
.instance_type
== "COLLECTION":
284 name_orig
= "OB" + ob
.name
285 dataname_orig
= "DATA" + ob
.instance_collection
.name
287 # hoping only dupligroups have several source datablocks
288 # ob_dupli_list_create(scene) #deprecated in 2.8
289 for eachduplicate
in depsgraph
.object_instances
:
290 # Real dupli instance filtered because
291 # original included in list since 2.8
292 if eachduplicate
.is_instance
:
293 dataname_orig
= "DATA" + eachduplicate
.object.name
294 # obj.dupli_list_clear() #just don't store any reference to instance since 2.8
295 elif ob
.data
: # not an EMPTY type object
296 name_orig
= "OB" + ob
.name
297 dataname_orig
= "DATA" + ob
.data
.name
298 elif ob
.type == "EMPTY":
299 name_orig
= "OB" + ob
.name
300 dataname_orig
= "DATA" + ob
.name
302 name_orig
= DEF_OBJ_NAME
303 dataname_orig
= DEF_OBJ_NAME
304 name
= string_strip_hyphen(bpy
.path
.clean_name(name_orig
))
305 dataname
= string_strip_hyphen(bpy
.path
.clean_name(dataname_orig
))
306 # for slot in obj.material_slots:
307 # if slot.material is not None and slot.link == 'OBJECT':
308 # obmaterial = slot.material
310 # ------------------------------------------------
313 info_callback("Object %2.d of %2.d (%s)" % (ob_num
, len(sel
), ob
.name
))
317 matrix
= global_matrix
@ ob
.matrix_world
318 povdataname
= store(scene
, ob
, name
, dataname
, matrix
)
319 if povdataname
is None:
320 print("This is an instance of " + name
)
323 print("Writing Down First Occurrence of " + name
)
325 # ------------ Mesh Primitives ------------ #
326 # special export_curves() function takes care of writing
327 # lathe, sphere_sweep, birail, and loft except with modifiers
329 if not ob
.is_modified(scene
, "RENDER"):
330 if ob
.type == "CURVE" and (
331 ob
.pov
.curveshape
in {"lathe", "sphere_sweep", "loft"}
333 continue # Don't render proxy mesh, skip to next object
334 # pov_mat_name = "Default_texture" # Not used...remove?
336 # Implicit else-if (as not skipped by previous "continue")
337 # which itself has no "continue" (to combine custom pov code)?, so Keep this last.
338 # For originals, but not their instances, attempt to export mesh:
339 if not ob
.is_instancer
:
340 # except duplis which should be instances groups for now but all duplis later
341 if ob
.type == "EMPTY":
342 # XXX Should we only write this once and instantiate the same for every
343 # empty in the final matrix writing, or even no matrix and just a comment
344 # with empty object transforms ?
345 tab_write(file, "\n//dummy sphere to represent Empty location\n")
348 "#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n"
351 continue # Don't render empty object but this is later addition, watch it.
355 model_poly_topology
.export_mesh(file, ob
, povdataname
,
356 material_names_dictionary
,
358 tab_level
, tab_write
, linebreaksinlists
)
360 # ------------ Povray Primitives ------------ #
361 # Also implicit elif (continue) clauses and sorted after mesh
362 # as less often used.
363 if ob
.pov
.object_as
== "PLANE":
364 tab_write(file, "#declare %s = plane{ <0,0,1>,0\n" % povdataname
)
365 if ob
.active_material
:
366 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
368 material
= ob
.active_material
369 write_object_material_interior(file, material
, ob
, tab_write
)
372 # tab_write(file, "texture {%s}\n"%pov_mat_name)
373 write_object_modifiers(ob
, file)
374 # tab_write(file, "rotate x*90\n")
375 tab_write(file, "}\n")
376 continue # Don't render proxy mesh, skip to next object
378 if ob
.pov
.object_as
== "SPHERE":
382 "#declare %s = sphere { 0,%6f\n" % (povdataname
, ob
.pov
.sphere_radius
),
384 if ob
.active_material
:
385 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
387 material
= ob
.active_material
388 write_object_material_interior(file, material
, ob
, tab_write
)
391 # tab_write(file, "texture {%s}\n"%pov_mat_name)
392 write_object_modifiers(ob
, file)
393 # tab_write(file, "rotate x*90\n")
394 tab_write(file, "}\n")
395 continue # Don't render proxy mesh, skip to next object
397 if ob
.pov
.object_as
== "BOX":
398 tab_write(file, "#declare %s = box { -1,1\n" % povdataname
)
399 if ob
.active_material
:
400 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
402 material
= ob
.active_material
403 write_object_material_interior(file, material
, ob
, tab_write
)
406 # tab_write(file, "texture {%s}\n"%pov_mat_name)
407 write_object_modifiers(ob
, file)
408 # tab_write(file, "rotate x*90\n")
409 tab_write(file, "}\n")
410 continue # Don't render proxy mesh, skip to next object
412 if ob
.pov
.object_as
== "CONE":
413 br
= ob
.pov
.cone_base_radius
414 cr
= ob
.pov
.cone_cap_radius
415 bz
= ob
.pov
.cone_base_z
416 cz
= ob
.pov
.cone_cap_z
419 "#declare %s = cone { <0,0,%.4f>,%.4f,<0,0,%.4f>,%.4f\n"
420 % (povdataname
, bz
, br
, cz
, cr
),
422 if ob
.active_material
:
423 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
425 material
= ob
.active_material
426 write_object_material_interior(file, material
, ob
, tab_write
)
429 # tab_write(file, "texture {%s}\n"%pov_mat_name)
430 write_object_modifiers(ob
, file)
431 # tab_write(file, "rotate x*90\n")
432 tab_write(file, "}\n")
433 continue # Don't render proxy mesh, skip to next object
435 if ob
.pov
.object_as
== "CYLINDER":
436 r
= ob
.pov
.cylinder_radius
437 x2
= ob
.pov
.cylinder_location_cap
[0]
438 y2
= ob
.pov
.cylinder_location_cap
[1]
439 z2
= ob
.pov
.cylinder_location_cap
[2]
442 "#declare %s = cylinder { <0,0,0>,<%6f,%6f,%6f>,%6f\n"
443 % (povdataname
, x2
, y2
, z2
, r
),
445 if ob
.active_material
:
446 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
448 material
= ob
.active_material
449 write_object_material_interior(file, material
, ob
, tab_write
)
452 # tab_write(file, "texture {%s}\n"%pov_mat_name)
453 # cylinders written at origin, translated below
454 write_object_modifiers(ob
, file)
455 # tab_write(file, "rotate x*90\n")
456 tab_write(file, "}\n")
457 continue # Don't render proxy mesh, skip to next object
459 if ob
.pov
.object_as
== "HEIGHT_FIELD":
461 filename
= ob
.pov
.hf_filename
462 data
+= '"%s"' % filename
463 gamma
= " gamma %.4f" % ob
.pov
.hf_gamma
465 if ob
.pov
.hf_premultiplied
:
466 data
+= " premultiplied on"
469 if ob
.pov
.hf_water
> 0:
470 data
+= " water_level %.4f" % ob
.pov
.hf_water
471 # hierarchy = obj.pov.hf_hierarchy
472 tab_write(file, "#declare %s = height_field { %s\n" % (povdataname
, data
))
473 if ob
.active_material
:
474 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
476 material
= ob
.active_material
477 write_object_material_interior(file, material
, ob
, tab_write
)
480 # tab_write(file, "texture {%s}\n"%pov_mat_name)
481 write_object_modifiers(ob
, file)
482 tab_write(file, "rotate x*90\n")
483 tab_write(file, "translate <-0.5,0.5,0>\n")
484 tab_write(file, "scale <0,-1,0>\n")
485 tab_write(file, "}\n")
486 continue # Don't render proxy mesh, skip to next object
488 if ob
.pov
.object_as
== "TORUS":
491 "#declare %s = torus { %.4f,%.4f\n"
494 ob
.pov
.torus_major_radius
,
495 ob
.pov
.torus_minor_radius
,
498 if ob
.active_material
:
499 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
501 material
= ob
.active_material
502 write_object_material_interior(file, material
, ob
, tab_write
)
505 # tab_write(file, "texture {%s}\n"%pov_mat_name)
506 write_object_modifiers(ob
, file)
507 tab_write(file, "rotate x*90\n")
508 tab_write(file, "}\n")
509 continue # Don't render proxy mesh, skip to next object
511 if ob
.pov
.object_as
== "PARAMETRIC":
512 tab_write(file, "#declare %s = parametric {\n" % povdataname
)
513 tab_write(file, "function { %s }\n" % ob
.pov
.x_eq
)
514 tab_write(file, "function { %s }\n" % ob
.pov
.y_eq
)
515 tab_write(file, "function { %s }\n" % ob
.pov
.z_eq
)
518 "<%.4f,%.4f>, <%.4f,%.4f>\n"
519 % (ob
.pov
.u_min
, ob
.pov
.v_min
, ob
.pov
.u_max
, ob
.pov
.v_max
),
521 # Previous to 3.8 default max_gradient 1.0 was too slow
522 tab_write(file, "max_gradient 0.001\n")
523 if ob
.pov
.contained_by
== "sphere":
524 tab_write(file, "contained_by { sphere{0, 2} }\n")
526 tab_write(file, "contained_by { box{-2, 2} }\n")
527 tab_write(file, "max_gradient %.6f\n" % ob
.pov
.max_gradient
)
528 tab_write(file, "accuracy %.6f\n" % ob
.pov
.accuracy
)
529 tab_write(file, "precompute 10 x,y,z\n")
530 tab_write(file, "}\n")
531 continue # Don't render proxy mesh, skip to next object
533 if ob
.pov
.object_as
== "ISOSURFACE_NODE":
534 tab_write(file, "#declare %s = isosurface{ \n" % povdataname
)
535 tab_write(file, "function{ \n")
536 text_name
= ob
.pov
.iso_function_text
538 node_tree
= bpy
.context
.scene
.node_tree
539 for node
in node_tree
.nodes
:
540 if node
.bl_idname
== "IsoPropsNode" and node
.label
== ob
.name
:
541 for inp
in node
.inputs
:
545 "#declare %s = %.6g;\n" % (inp
.name
, inp
.default_value
),
548 text
= bpy
.data
.texts
[text_name
]
549 for line
in text
.lines
:
550 split
= line
.body
.split()
551 if split
[0] != "#declare":
552 tab_write(file, "%s\n" % line
.body
)
554 tab_write(file, "abs(x) - 2 + y")
555 tab_write(file, "}\n")
556 tab_write(file, "threshold %.6g\n" % ob
.pov
.threshold
)
557 tab_write(file, "max_gradient %.6g\n" % ob
.pov
.max_gradient
)
558 tab_write(file, "accuracy %.6g\n" % ob
.pov
.accuracy
)
559 tab_write(file, "contained_by { ")
560 if ob
.pov
.contained_by
== "sphere":
561 tab_write(file, "sphere {0,%.6g}}\n" % ob
.pov
.container_scale
)
565 "box {-%.6g,%.6g}}\n"
566 % (ob
.pov
.container_scale
, ob
.pov
.container_scale
),
568 if ob
.pov
.all_intersections
:
569 tab_write(file, "all_intersections\n")
571 if ob
.pov
.max_trace
> 1:
572 tab_write(file, "max_trace %.6g\n" % ob
.pov
.max_trace
)
573 if ob
.active_material
:
574 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
576 material
= ob
.active_material
577 write_object_material_interior(file, material
, ob
, tab_write
)
580 # tab_write(file, "texture {%s}\n"%pov_mat_name)
581 tab_write(file, "scale %.6g\n" % (1 / ob
.pov
.container_scale
))
582 tab_write(file, "}\n")
583 continue # Don't render proxy mesh, skip to next object
585 if ob
.pov
.object_as
== "ISOSURFACE_VIEW":
586 simple_isosurface_function
= ob
.pov
.isosurface_eq
587 if simple_isosurface_function
:
588 tab_write(file, "#declare %s = isosurface{ \n" % povdataname
)
589 tab_write(file, "function{ \n")
590 tab_write(file, simple_isosurface_function
)
591 tab_write(file, "}\n")
592 tab_write(file, "threshold %.6g\n" % ob
.pov
.threshold
)
593 tab_write(file, "max_gradient %.6g\n" % ob
.pov
.max_gradient
)
594 tab_write(file, "accuracy %.6g\n" % ob
.pov
.accuracy
)
595 tab_write(file, "contained_by { ")
596 if ob
.pov
.contained_by
== "sphere":
597 tab_write(file, "sphere {0,%.6g}}\n" % ob
.pov
.container_scale
)
601 "box {-%.6g,%.6g}}\n"
602 % (ob
.pov
.container_scale
, ob
.pov
.container_scale
),
604 if ob
.pov
.all_intersections
:
605 tab_write(file, "all_intersections\n")
607 if ob
.pov
.max_trace
> 1:
608 tab_write(file, "max_trace %.6g\n" % ob
.pov
.max_trace
)
609 if ob
.active_material
:
610 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
612 material
= ob
.active_material
613 write_object_material_interior(file, material
, ob
, tab_write
)
616 # tab_write(file, "texture {%s}\n"%pov_mat_name)
617 tab_write(file, "scale %.6g\n" % (1 / ob
.pov
.container_scale
))
618 tab_write(file, "}\n")
619 continue # Don't render proxy mesh, skip to next object
621 if ob
.pov
.object_as
== "SUPERELLIPSOID":
624 "#declare %s = superellipsoid{ <%.4f,%.4f>\n"
625 % (povdataname
, ob
.pov
.se_n2
, ob
.pov
.se_n1
),
627 if ob
.active_material
:
628 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
630 material
= ob
.active_material
631 write_object_material_interior(file, material
, ob
, tab_write
)
634 # tab_write(file, "texture {%s}\n"%pov_mat_name)
635 write_object_modifiers(ob
, file)
636 tab_write(file, "}\n")
637 continue # Don't render proxy mesh, skip to next object
639 if ob
.pov
.object_as
== "SUPERTORUS":
640 rad_maj
= ob
.pov
.st_major_radius
641 rad_min
= ob
.pov
.st_minor_radius
642 ring
= ob
.pov
.st_ring
643 cross
= ob
.pov
.st_cross
644 accuracy
= ob
.pov
.st_accuracy
645 gradient
= ob
.pov
.st_max_gradient
646 # --- Inline Supertorus macro
648 "#macro Supertorus(RMj, RMn, MajorControl, MinorControl, Accuracy, MaxGradient)\n"
650 file.write(" #local CP = 2/MinorControl;\n")
651 file.write(" #local RP = 2/MajorControl;\n")
652 file.write(" isosurface {\n")
654 " 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"
656 file.write(" threshold 0\n")
658 " contained_by {box {<-RMj-RMn,-RMn,-RMj-RMn>, < RMj+RMn, RMn, RMj+RMn>}}\n"
660 file.write(" #if(MaxGradient >= 1)\n")
661 file.write(" max_gradient MaxGradient\n")
662 file.write(" #else\n")
663 file.write(" evaluate 1, 10, 0.1\n")
664 file.write(" #end\n")
665 file.write(" accuracy Accuracy\n")
671 "#declare %s = object{ Supertorus( %.4g,%.4g,%.4g,%.4g,%.4g,%.4g)\n"
682 if ob
.active_material
:
683 # pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
685 material
= ob
.active_material
686 write_object_material_interior(file, material
, ob
, tab_write
)
689 # tab_write(file, "texture {%s}\n"%pov_mat_name)
690 write_object_modifiers(ob
, file)
691 tab_write(file, "rotate x*90\n")
692 tab_write(file, "}\n")
693 continue # Don't render proxy mesh, skip to next object
695 if ob
.pov
.object_as
== "POLYCIRCLE":
696 # TODO write below macro Once:
697 # if write_polytocircle_macro_once == 0:
698 file.write("/****************************\n")
699 file.write("This macro was written by 'And'.\n")
700 file.write("Link:(http://news.povray.org/povray.binaries.scene-files/)\n")
701 file.write("****************************/\n")
702 file.write("//from math.inc:\n")
703 file.write("#macro VPerp_Adjust(V, Axis)\n")
704 file.write(" vnormalize(vcross(vcross(Axis, V), Axis))\n")
706 file.write("//Then for the actual macro\n")
707 file.write("#macro Shape_Slice_Plane_2P_1V(point1, point2, clip_direct)\n")
708 file.write("#local p1 = point1 + <0,0,0>;\n")
709 file.write("#local p2 = point2 + <0,0,0>;\n")
710 file.write("#local clip_v = vnormalize(clip_direct + <0,0,0>);\n")
711 file.write("#local direct_v1 = vnormalize(p2 - p1);\n")
712 file.write("#if(vdot(direct_v1, clip_v) = 1)\n")
713 file.write(' #error "Shape_Slice_Plane_2P_1V error: Can\'t decide plane"\n')
714 file.write("#end\n\n")
716 "#local norm = -vnormalize(clip_v - direct_v1*vdot(direct_v1,clip_v));\n"
718 file.write("#local d = vdot(norm, p1);\n")
719 file.write("plane{\n")
720 file.write("norm, d\n")
722 file.write("#end\n\n")
723 file.write("//polygon to circle\n")
725 "#macro Shape_Polygon_To_Circle_Blending("
726 "_polygon_n, _side_face, "
727 "_polygon_circumscribed_radius, "
731 file.write("#local n = int(_polygon_n);\n")
732 file.write("#if(n < 3)\n")
733 file.write(" #error\n")
734 file.write("#end\n\n")
735 file.write("#local front_v = VPerp_Adjust(_side_face, z);\n")
736 file.write("#if(vdot(front_v, x) >= 0)\n")
737 file.write(" #local face_ang = acos(vdot(-y, front_v));\n")
738 file.write("#else\n")
739 file.write(" #local face_ang = -acos(vdot(-y, front_v));\n")
741 file.write("#local polyg_ext_ang = 2*pi/n;\n")
742 file.write("#local polyg_outer_r = _polygon_circumscribed_radius;\n")
743 file.write("#local polyg_inner_r = polyg_outer_r*cos(polyg_ext_ang/2);\n")
744 file.write("#local cycle_r = _circle_radius;\n")
745 file.write("#local h = _height;\n")
746 file.write("#if(polyg_outer_r < 0 | cycle_r < 0 | h <= 0)\n")
747 file.write(' #error "error: each side length must be positive"\n')
748 file.write("#end\n\n")
749 file.write("#local multi = 1000;\n")
750 file.write("#local poly_obj =\n")
751 file.write("polynomial{\n")
753 file.write("xyz(0,2,2): multi*1,\n")
754 file.write("xyz(2,0,1): multi*2*h,\n")
755 file.write("xyz(1,0,2): multi*2*(polyg_inner_r-cycle_r),\n")
756 file.write("xyz(2,0,0): multi*(-h*h),\n")
757 file.write("xyz(0,0,2): multi*(-pow(cycle_r - polyg_inner_r, 2)),\n")
758 file.write("xyz(1,0,1): multi*2*h*(-2*polyg_inner_r + cycle_r),\n")
759 file.write("xyz(1,0,0): multi*2*h*h*polyg_inner_r,\n")
760 file.write("xyz(0,0,1): multi*2*h*polyg_inner_r*(polyg_inner_r - cycle_r),\n")
761 file.write("xyz(0,0,0): multi*(-pow(polyg_inner_r*h, 2))\n")
762 file.write("sturm\n")
764 file.write("#local mockup1 =\n")
765 file.write("difference{\n")
766 file.write(" cylinder{\n")
767 file.write(" <0,0,0.0>,<0,0,h>, max(polyg_outer_r, cycle_r)\n")
769 file.write(" #for(i, 0, n-1)\n")
770 file.write(" object{\n")
771 file.write(" poly_obj\n")
772 file.write(" inverse\n")
773 file.write(" rotate <0, 0, -90 + degrees(polyg_ext_ang*i)>\n")
775 file.write(" object{\n")
777 " Shape_Slice_Plane_2P_1V(<polyg_inner_r,0,0>,<cycle_r,0,h>,x)\n"
779 file.write(" rotate <0, 0, -90 + degrees(polyg_ext_ang*i)>\n")
781 file.write(" #end\n")
783 file.write("object{\n")
784 file.write("mockup1\n")
785 file.write("rotate <0, 0, degrees(face_ang)>\n")
789 ngon
= ob
.pov
.polytocircle_ngon
790 ngonR
= ob
.pov
.polytocircle_ngonR
791 circleR
= ob
.pov
.polytocircle_circleR
794 "#declare %s = object { Shape_Polygon_To_Circle_Blending("
795 "%s, z, %.4f, %.4f, 2) rotate x*180 translate z*1\n"
796 % (povdataname
, ngon
, ngonR
, circleR
),
798 tab_write(file, "}\n")
799 continue # Don't render proxy mesh, skip to next object
801 # fluid_found early return no longer runs this
802 # todo maybe make a function to run in that other branch
804 _dupnames_seen
= {} # avoid duplicate output during introspection
806 # matrix = global_matrix @ obj.matrix_world
808 tab_write(file, "\n//--DupliObjects in %s--\n\n" % ob
.name
)
809 # obj.dupli_list_create(scene) #deprecated in 2.8
811 if ob
.is_modified(scene
, "RENDER"):
812 # modified object always unique so using object name rather than data name
813 dup
= "#declare OB%s = union{\n" % (
814 string_strip_hyphen(bpy
.path
.clean_name(ob
.name
))
817 dup
= "#declare DATA%s = union{\n" % (
818 string_strip_hyphen(bpy
.path
.clean_name(ob
.name
))
821 for eachduplicate
in depsgraph
.object_instances
:
823 eachduplicate
.is_instance
824 ): # Real dupli instance filtered because original included in list since 2.8
825 _dupname
= eachduplicate
.object.name
826 _dupobj
= bpy
.data
.objects
[_dupname
]
827 # BEGIN introspection for troubleshooting purposes
828 if "name" not in dir(_dupobj
.data
):
829 if _dupname
not in _dupnames_seen
:
831 "WARNING: bpy.data.objects[%s].data (of type %s) has no 'name' attribute"
832 % (_dupname
, type(_dupobj
.data
))
834 for _thing
in dir(_dupobj
):
837 % (_dupname
, _thing
, getattr(_dupobj
, _thing
))
839 _dupnames_seen
[_dupname
] = 1
840 print("''=> Unparseable objects so far: %s" % _dupnames_seen
)
842 _dupnames_seen
[_dupname
] += 1
843 continue # don't try to parse data objects with no name attribute
844 # END introspection for troubleshooting purposes
845 duplidataname
= "OB" + string_strip_hyphen(
846 bpy
.path
.clean_name(_dupobj
.data
.name
)
849 eachduplicate
.matrix_world
.copy()
850 ) # has to be copied to not store instance since 2.8
851 dup
+= "\tobject {\n\t\tDATA%s\n\t\t%s\t}\n" % (
852 string_strip_hyphen(bpy
.path
.clean_name(_dupobj
.data
.name
)),
853 matrix_as_pov_string(ob
.matrix_world
.inverted() @ dupmatrix
),
855 # add object to a list so that it is not rendered for some instance_types
857 ob
.instance_type
!= "COLLECTION"
858 and duplidataname
not in duplidata_ref
860 duplidata_ref
.append(
862 ) # older key [string_strip_hyphen(bpy.path.clean_name("OB"+obj.name))]
864 # obj.dupli_list_clear()# just do not store any reference to instance since 2.8
869 print("WARNING: Unparseable objects in current .blend file:\n''--> %s" % _dupnames_seen
)
871 print("duplidata_ref = %s" % duplidata_ref
)
872 for data_name
, inst
in data_ref
.items():
873 for ob_name
, matrix_str
in inst
:
874 if ob_name
not in duplidata_ref
: # .items() for a dictionary
875 tab_write(file, "\n//----Blender Object Name: %s----\n" %
876 ob_name
.removeprefix("OB"))
877 if ob
.pov
.object_as
== "":
878 tab_write(file, "object { \n")
879 tab_write(file, "%s\n" % data_name
)
880 tab_write(file, "%s\n" % matrix_str
)
881 tab_write(file, "}\n")
884 for mod
in ob
.modifiers
:
885 if mod
.type == "BOOLEAN":
888 if mod
.operation
== "INTERSECT":
889 operation
= "intersection"
891 operation
= mod
.operation
.lower()
892 mod_ob_name
= string_strip_hyphen(
893 bpy
.path
.clean_name(mod
.object.name
)
895 mod_matrix
= global_matrix
@ mod
.object.matrix_world
896 mod_ob_matrix
= matrix_as_pov_string(mod_matrix
)
897 tab_write(file, "%s { \n" % operation
)
898 tab_write(file, "object { \n")
899 tab_write(file, "%s\n" % data_name
)
900 tab_write(file, "%s\n" % matrix_str
)
901 tab_write(file, "}\n")
902 tab_write(file, "object { \n")
903 tab_write(file, "%s\n" % ("DATA" + mod_ob_name
))
904 tab_write(file, "%s\n" % mod_ob_matrix
)
905 tab_write(file, "}\n")
906 tab_write(file, "}\n")
909 tab_write(file, "object { \n")
910 tab_write(file, "%s\n" % data_name
)
911 tab_write(file, "%s\n" % matrix_str
)
912 tab_write(file, "}\n")