mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-13 13:34:34 +00:00

Using object.update_tag() as pov specific object typing property set from python would not be visible from depsgraph before file save, causing mesh proxies to export rather than their pure untesselated pov shapes.
915 lines
42 KiB
Python
915 lines
42 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
# <pep8 compliant>
|
|
|
|
"""Translate to POV the control point compound geometries.
|
|
|
|
Such as polygon meshes or curve based shapes.
|
|
"""
|
|
|
|
# --------
|
|
# -- Faster mesh export ...one day
|
|
# import numpy as np
|
|
# --------
|
|
import bpy
|
|
from . import texturing # for how textures influence shaders
|
|
from . import model_poly_topology
|
|
# from .texturing import local_material_names
|
|
from .scenography import export_smoke
|
|
|
|
|
|
def matrix_as_pov_string(matrix):
|
|
"""Translate some transform matrix from Blender UI
|
|
to POV syntax and return that string"""
|
|
return (
|
|
"matrix <"
|
|
"%.6f, %.6f, %.6f, "
|
|
"%.6f, %.6f, %.6f, "
|
|
"%.6f, %.6f, %.6f, "
|
|
"%.6f, %.6f, %.6f"
|
|
">\n"
|
|
% (
|
|
matrix[0][0],
|
|
matrix[1][0],
|
|
matrix[2][0],
|
|
matrix[0][1],
|
|
matrix[1][1],
|
|
matrix[2][1],
|
|
matrix[0][2],
|
|
matrix[1][2],
|
|
matrix[2][2],
|
|
matrix[0][3],
|
|
matrix[1][3],
|
|
matrix[2][3],
|
|
)
|
|
)
|
|
|
|
# objectNames = {}
|
|
DEF_OBJ_NAME = "Default"
|
|
|
|
|
|
def objects_loop(
|
|
file,
|
|
scene,
|
|
sel,
|
|
csg,
|
|
material_names_dictionary,
|
|
unpacked_images,
|
|
tab_level,
|
|
tab_write,
|
|
info_callback,
|
|
):
|
|
# global preview_dir
|
|
# global smoke_path
|
|
# global global_matrix
|
|
# global comments
|
|
|
|
# global tab
|
|
"""write all meshes as POV mesh2{} syntax to exported file"""
|
|
# # some numpy functions to speed up mesh export NOT IN USE YET
|
|
# # Current 2.93 beta numpy linking has troubles so definitions commented off for now
|
|
|
|
# # TODO: also write a numpy function to read matrices at object level?
|
|
# # feed below with mesh object.data, but only after doing data.calc_loop_triangles()
|
|
# def read_verts_co(self, mesh):
|
|
# #'float64' would be a slower 64-bit floating-point number numpy datatype
|
|
# # using 'float32' vert coordinates for now until any issue is reported
|
|
# mverts_co = np.zeros((len(mesh.vertices) * 3), dtype=np.float32)
|
|
# mesh.vertices.foreach_get("co", mverts_co)
|
|
# return np.reshape(mverts_co, (len(mesh.vertices), 3))
|
|
|
|
# def read_verts_idx(self, mesh):
|
|
# mverts_idx = np.zeros((len(mesh.vertices)), dtype=np.int64)
|
|
# mesh.vertices.foreach_get("index", mverts_idx)
|
|
# return np.reshape(mverts_idx, (len(mesh.vertices), 1))
|
|
|
|
# def read_verts_norms(self, mesh):
|
|
# #'float64' would be a slower 64-bit floating-point number numpy datatype
|
|
# # using less accurate 'float16' normals for now until any issue is reported
|
|
# mverts_no = np.zeros((len(mesh.vertices) * 3), dtype=np.float16)
|
|
# mesh.vertices.foreach_get("normal", mverts_no)
|
|
# return np.reshape(mverts_no, (len(mesh.vertices), 3))
|
|
|
|
# def read_faces_idx(self, mesh):
|
|
# mfaces_idx = np.zeros((len(mesh.loop_triangles)), dtype=np.int64)
|
|
# mesh.loop_triangles.foreach_get("index", mfaces_idx)
|
|
# return np.reshape(mfaces_idx, (len(mesh.loop_triangles), 1))
|
|
|
|
# def read_faces_verts_indices(self, mesh):
|
|
# mfaces_verts_idx = np.zeros((len(mesh.loop_triangles) * 3), dtype=np.int64)
|
|
# mesh.loop_triangles.foreach_get("vertices", mfaces_verts_idx)
|
|
# return np.reshape(mfaces_verts_idx, (len(mesh.loop_triangles), 3))
|
|
|
|
# # Why is below different from vertex indices?
|
|
# def read_faces_verts_loops(self, mesh):
|
|
# mfaces_verts_loops = np.zeros((len(mesh.loop_triangles) * 3), dtype=np.int64)
|
|
# mesh.loop_triangles.foreach_get("loops", mfaces_verts_loops)
|
|
# return np.reshape(mfaces_verts_loops, (len(mesh.loop_triangles), 3))
|
|
|
|
# def read_faces_norms(self, mesh):
|
|
# #'float64' would be a slower 64-bit floating-point number numpy datatype
|
|
# # using less accurate 'float16' normals for now until any issue is reported
|
|
# mfaces_no = np.zeros((len(mesh.loop_triangles) * 3), dtype=np.float16)
|
|
# mesh.loop_triangles.foreach_get("normal", mfaces_no)
|
|
# return np.reshape(mfaces_no, (len(mesh.loop_triangles), 3))
|
|
|
|
# def read_faces_smooth(self, mesh):
|
|
# mfaces_smth = np.zeros((len(mesh.loop_triangles) * 1), dtype=np.bool)
|
|
# mesh.loop_triangles.foreach_get("use_smooth", mfaces_smth)
|
|
# return np.reshape(mfaces_smth, (len(mesh.loop_triangles), 1))
|
|
|
|
# def read_faces_material_indices(self, mesh):
|
|
# mfaces_mats_idx = np.zeros((len(mesh.loop_triangles)), dtype=np.int16)
|
|
# mesh.loop_triangles.foreach_get("material_index", mfaces_mats_idx)
|
|
# return np.reshape(mfaces_mats_idx, (len(mesh.loop_triangles), 1))
|
|
|
|
# obmatslist = []
|
|
# def hasUniqueMaterial():
|
|
# # Grab materials attached to object instances ...
|
|
# if hasattr(obj, 'material_slots'):
|
|
# for ms in obj.material_slots:
|
|
# if ms.material is not None and ms.link == 'OBJECT':
|
|
# if ms.material in obmatslist:
|
|
# return False
|
|
# else:
|
|
# obmatslist.append(ms.material)
|
|
# return True
|
|
# def hasObjectMaterial(obj):
|
|
# # Grab materials attached to object instances ...
|
|
# if hasattr(obj, 'material_slots'):
|
|
# for ms in obj.material_slots:
|
|
# if ms.material is not None and ms.link == 'OBJECT':
|
|
# # If there is at least one material slot linked to the object
|
|
# # and not the data (mesh), always create a new, "private" data instance.
|
|
# return True
|
|
# return False
|
|
# For objects using local material(s) only!
|
|
# This is a mapping between a tuple (dataname, material_names_dictionary, ...),
|
|
# and the POV dataname.
|
|
# As only objects using:
|
|
# * The same data.
|
|
# * EXACTLY the same materials, in EXACTLY the same sockets.
|
|
# ... can share a same instance in POV export.
|
|
from .render import (
|
|
string_strip_hyphen,
|
|
global_matrix,
|
|
tab,
|
|
comments,
|
|
)
|
|
from .render_core import (
|
|
preview_dir,
|
|
smoke_path,
|
|
)
|
|
from .model_primitives import write_object_modifiers
|
|
from .shading import write_object_material_interior
|
|
from .scenography import image_format, img_map, img_map_transforms
|
|
|
|
linebreaksinlists = scene.pov.list_lf_enable and not scene.pov.tempfiles_enable
|
|
obmats2data = {}
|
|
|
|
|
|
def check_object_materials(obj, obj_name, dataname):
|
|
"""Compare other objects exported material slots to avoid rewriting duplicates"""
|
|
if hasattr(obj, "material_slots"):
|
|
has_local_mats = False
|
|
key = [dataname]
|
|
for ms in obj.material_slots:
|
|
if ms.material is not None:
|
|
key.append(ms.material.name)
|
|
if ms.link == "OBJECT" and not has_local_mats:
|
|
has_local_mats = True
|
|
else:
|
|
# Even if the slot is empty, it is important to grab it...
|
|
key.append("")
|
|
if has_local_mats:
|
|
# If this object uses local material(s), lets find if another object
|
|
# using the same data and exactly the same list of materials
|
|
# (in the same slots) has already been processed...
|
|
# Note that here also, we use object name as new, unique dataname for Pov.
|
|
key = tuple(key) # Lists are not hashable...
|
|
if key not in obmats2data:
|
|
obmats2data[key] = obj_name
|
|
return obmats2data[key]
|
|
return None
|
|
|
|
data_ref = {}
|
|
|
|
def store(scene, ob, name, dataname, matrix):
|
|
# The Object needs to be written at least once but if its data is
|
|
# already in data_ref this has already been done.
|
|
# This func returns the "povray" name of the data, or None
|
|
# if no writing is needed.
|
|
if ob.is_modified(scene, "RENDER"):
|
|
# Data modified.
|
|
# Create unique entry in data_ref by using object name
|
|
# (always unique in Blender) as data name.
|
|
data_ref[name] = [(name, matrix_as_pov_string(matrix))]
|
|
return name
|
|
# Here, we replace dataname by the value returned by check_object_materials, only if
|
|
# it is not evaluated to False (i.e. only if the object uses some local material(s)).
|
|
dataname = check_object_materials(ob, name, dataname) or dataname
|
|
if dataname in data_ref:
|
|
# Data already known, just add the object instance.
|
|
data_ref[dataname].append((name, matrix_as_pov_string(matrix)))
|
|
# No need to write data
|
|
return None
|
|
# Else (no return yet): Data not yet processed, create a new entry in data_ref.
|
|
data_ref[dataname] = [(name, matrix_as_pov_string(matrix))]
|
|
return dataname
|
|
|
|
ob_num = 0
|
|
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
for ob in sel:
|
|
# Using depsgraph
|
|
ob = bpy.data.objects[ob.name].evaluated_get(depsgraph)
|
|
|
|
# subtract original from the count of their instances as were not counted before 2.8
|
|
if (ob.is_instancer and ob.original != ob):
|
|
continue
|
|
|
|
ob_num += 1
|
|
|
|
# XXX I moved all those checks here, as there is no need to compute names
|
|
# for object we won't export here!
|
|
if ob.type in {
|
|
"LIGHT",
|
|
"CAMERA", # 'EMPTY', #empties can bear dupligroups
|
|
"META",
|
|
"ARMATURE",
|
|
"LATTICE",
|
|
}:
|
|
continue
|
|
fluid_found = False
|
|
for mod in ob.modifiers:
|
|
if mod and hasattr(mod, "fluid_type"):
|
|
fluid_found = True
|
|
if mod.fluid_type == "DOMAIN":
|
|
if mod.domain_settings.domain_type == "GAS":
|
|
export_smoke(file, ob.name, smoke_path, comments, global_matrix)
|
|
break # don't render domain mesh, skip to next object.
|
|
if mod.fluid_type == "FLOW": # The domain contains all the smoke. so that's it.
|
|
if mod.flow_settings.flow_type == "SMOKE": # Check how liquids behave
|
|
break # don't render smoke flow emitter mesh either, skip to next object.
|
|
if fluid_found:
|
|
return
|
|
# No fluid found
|
|
if hasattr(ob, "particle_systems"):
|
|
# Importing function Export Hair
|
|
# here rather than at the top recommended for addons startup footprint
|
|
from .particles import export_hair
|
|
|
|
for p_sys in ob.particle_systems:
|
|
for particle_mod in [
|
|
m
|
|
for m in ob.modifiers
|
|
if (m is not None) and (m.type == "PARTICLE_SYSTEM")
|
|
]:
|
|
if (
|
|
(p_sys.settings.render_type == "PATH")
|
|
and particle_mod.show_render
|
|
and (p_sys.name == particle_mod.particle_system.name)
|
|
):
|
|
export_hair(file, ob, particle_mod, p_sys, global_matrix)
|
|
if not ob.show_instancer_for_render:
|
|
continue # don't render emitter mesh, skip to next object.
|
|
|
|
# ------------------------------------------------
|
|
# Generating a name for object just like materials to be able to use it
|
|
# (baking for now or anything else).
|
|
# XXX I don't understand that if we are here, sel if a non-empty iterable,
|
|
# so this condition is always True, IMO -- mont29
|
|
# EMPTY type objects treated a little further below -- MR
|
|
|
|
# modified elif to if below as non EMPTY objects can also be instancers
|
|
if ob.is_instancer:
|
|
if ob.instance_type == "COLLECTION":
|
|
name_orig = "OB" + ob.name
|
|
dataname_orig = "DATA" + ob.instance_collection.name
|
|
else:
|
|
# hoping only dupligroups have several source datablocks
|
|
# ob_dupli_list_create(scene) #deprecated in 2.8
|
|
for eachduplicate in depsgraph.object_instances:
|
|
# Real dupli instance filtered because
|
|
# original included in list since 2.8
|
|
if eachduplicate.is_instance:
|
|
dataname_orig = "DATA" + eachduplicate.object.name
|
|
# obj.dupli_list_clear() #just don't store any reference to instance since 2.8
|
|
elif ob.data: # not an EMPTY type object
|
|
name_orig = "OB" + ob.name
|
|
dataname_orig = "DATA" + ob.data.name
|
|
elif ob.type == "EMPTY":
|
|
name_orig = "OB" + ob.name
|
|
dataname_orig = "DATA" + ob.name
|
|
else:
|
|
name_orig = DEF_OBJ_NAME
|
|
dataname_orig = DEF_OBJ_NAME
|
|
name = string_strip_hyphen(bpy.path.clean_name(name_orig))
|
|
dataname = string_strip_hyphen(bpy.path.clean_name(dataname_orig))
|
|
# for slot in obj.material_slots:
|
|
# if slot.material is not None and slot.link == 'OBJECT':
|
|
# obmaterial = slot.material
|
|
|
|
# ------------------------------------------------
|
|
|
|
if info_callback:
|
|
info_callback("Object %2.d of %2.d (%s)" % (ob_num, len(sel), ob.name))
|
|
|
|
me = ob.data
|
|
|
|
matrix = global_matrix @ ob.matrix_world
|
|
povdataname = store(scene, ob, name, dataname, matrix)
|
|
if povdataname is None:
|
|
print("This is an instance of " + name)
|
|
continue
|
|
|
|
print("Writing Down First Occurrence of " + name)
|
|
|
|
# ------------ Mesh Primitives ------------ #
|
|
# special export_curves() function takes care of writing
|
|
# lathe, sphere_sweep, birail, and loft except with modifiers
|
|
# converted to mesh
|
|
if not ob.is_modified(scene, "RENDER"):
|
|
if ob.type == "CURVE" and (
|
|
ob.pov.curveshape in {"lathe", "sphere_sweep", "loft"}
|
|
):
|
|
continue # Don't render proxy mesh, skip to next object
|
|
# pov_mat_name = "Default_texture" # Not used...remove?
|
|
|
|
# Implicit else-if (as not skipped by previous "continue")
|
|
# which itself has no "continue" (to combine custom pov code)?, so Keep this last.
|
|
# For originals, but not their instances, attempt to export mesh:
|
|
if not ob.is_instancer:
|
|
# except duplis which should be instances groups for now but all duplis later
|
|
if ob.type == "EMPTY":
|
|
# XXX Should we only write this once and instantiate the same for every
|
|
# empty in the final matrix writing, or even no matrix and just a comment
|
|
# with empty object transforms ?
|
|
tab_write(file, "\n//dummy sphere to represent Empty location\n")
|
|
tab_write(
|
|
file,
|
|
"#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n"
|
|
% povdataname,
|
|
)
|
|
continue # Don't render empty object but this is later addition, watch it.
|
|
if ob.pov.object_as:
|
|
pass
|
|
else:
|
|
model_poly_topology.export_mesh(file, ob, povdataname,
|
|
material_names_dictionary,
|
|
unpacked_images,
|
|
tab_level, tab_write, linebreaksinlists)
|
|
|
|
# ------------ Povray Primitives ------------ #
|
|
# Also implicit elif (continue) clauses and sorted after mesh
|
|
# as less often used.
|
|
if ob.pov.object_as == "PLANE":
|
|
tab_write(file, "#declare %s = plane{ <0,0,1>,0\n" % povdataname)
|
|
if ob.active_material:
|
|
# pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
|
|
try:
|
|
material = ob.active_material
|
|
write_object_material_interior(file, material, ob, tab_write)
|
|
except IndexError:
|
|
print(me)
|
|
# tab_write(file, "texture {%s}\n"%pov_mat_name)
|
|
write_object_modifiers(ob, file)
|
|
# tab_write(file, "rotate x*90\n")
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "SPHERE":
|
|
|
|
tab_write(
|
|
file,
|
|
"#declare %s = sphere { 0,%6f\n" % (povdataname, ob.pov.sphere_radius),
|
|
)
|
|
if ob.active_material:
|
|
# pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
|
|
try:
|
|
material = ob.active_material
|
|
write_object_material_interior(file, material, ob, tab_write)
|
|
except IndexError:
|
|
print(me)
|
|
# tab_write(file, "texture {%s}\n"%pov_mat_name)
|
|
write_object_modifiers(ob, file)
|
|
# tab_write(file, "rotate x*90\n")
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "BOX":
|
|
tab_write(file, "#declare %s = box { -1,1\n" % povdataname)
|
|
if ob.active_material:
|
|
# pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
|
|
try:
|
|
material = ob.active_material
|
|
write_object_material_interior(file, material, ob, tab_write)
|
|
except IndexError:
|
|
print(me)
|
|
# tab_write(file, "texture {%s}\n"%pov_mat_name)
|
|
write_object_modifiers(ob, file)
|
|
# tab_write(file, "rotate x*90\n")
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "CONE":
|
|
br = ob.pov.cone_base_radius
|
|
cr = ob.pov.cone_cap_radius
|
|
bz = ob.pov.cone_base_z
|
|
cz = ob.pov.cone_cap_z
|
|
tab_write(
|
|
file,
|
|
"#declare %s = cone { <0,0,%.4f>,%.4f,<0,0,%.4f>,%.4f\n"
|
|
% (povdataname, bz, br, cz, cr),
|
|
)
|
|
if ob.active_material:
|
|
# pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
|
|
try:
|
|
material = ob.active_material
|
|
write_object_material_interior(file, material, ob, tab_write)
|
|
except IndexError:
|
|
print(me)
|
|
# tab_write(file, "texture {%s}\n"%pov_mat_name)
|
|
write_object_modifiers(ob, file)
|
|
# tab_write(file, "rotate x*90\n")
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "CYLINDER":
|
|
r = ob.pov.cylinder_radius
|
|
x2 = ob.pov.cylinder_location_cap[0]
|
|
y2 = ob.pov.cylinder_location_cap[1]
|
|
z2 = ob.pov.cylinder_location_cap[2]
|
|
tab_write(
|
|
file,
|
|
"#declare %s = cylinder { <0,0,0>,<%6f,%6f,%6f>,%6f\n"
|
|
% (povdataname, x2, y2, z2, r),
|
|
)
|
|
if ob.active_material:
|
|
# pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
|
|
try:
|
|
material = ob.active_material
|
|
write_object_material_interior(file, material, ob, tab_write)
|
|
except IndexError:
|
|
print(me)
|
|
# tab_write(file, "texture {%s}\n"%pov_mat_name)
|
|
# cylinders written at origin, translated below
|
|
write_object_modifiers(ob, file)
|
|
# tab_write(file, "rotate x*90\n")
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "HEIGHT_FIELD":
|
|
data = ""
|
|
filename = ob.pov.hf_filename
|
|
data += '"%s"' % filename
|
|
gamma = " gamma %.4f" % ob.pov.hf_gamma
|
|
data += gamma
|
|
if ob.pov.hf_premultiplied:
|
|
data += " premultiplied on"
|
|
if ob.pov.hf_smooth:
|
|
data += " smooth"
|
|
if ob.pov.hf_water > 0:
|
|
data += " water_level %.4f" % ob.pov.hf_water
|
|
# hierarchy = obj.pov.hf_hierarchy
|
|
tab_write(file, "#declare %s = height_field { %s\n" % (povdataname, data))
|
|
if ob.active_material:
|
|
# pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
|
|
try:
|
|
material = ob.active_material
|
|
write_object_material_interior(file, material, ob, tab_write)
|
|
except IndexError:
|
|
print(me)
|
|
# tab_write(file, "texture {%s}\n"%pov_mat_name)
|
|
write_object_modifiers(ob, file)
|
|
tab_write(file, "rotate x*90\n")
|
|
tab_write(file, "translate <-0.5,0.5,0>\n")
|
|
tab_write(file, "scale <0,-1,0>\n")
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "TORUS":
|
|
tab_write(
|
|
file,
|
|
"#declare %s = torus { %.4f,%.4f\n"
|
|
% (
|
|
povdataname,
|
|
ob.pov.torus_major_radius,
|
|
ob.pov.torus_minor_radius,
|
|
),
|
|
)
|
|
if ob.active_material:
|
|
# pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
|
|
try:
|
|
material = ob.active_material
|
|
write_object_material_interior(file, material, ob, tab_write)
|
|
except IndexError:
|
|
print(me)
|
|
# tab_write(file, "texture {%s}\n"%pov_mat_name)
|
|
write_object_modifiers(ob, file)
|
|
tab_write(file, "rotate x*90\n")
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "PARAMETRIC":
|
|
tab_write(file, "#declare %s = parametric {\n" % povdataname)
|
|
tab_write(file, "function { %s }\n" % ob.pov.x_eq)
|
|
tab_write(file, "function { %s }\n" % ob.pov.y_eq)
|
|
tab_write(file, "function { %s }\n" % ob.pov.z_eq)
|
|
tab_write(
|
|
file,
|
|
"<%.4f,%.4f>, <%.4f,%.4f>\n"
|
|
% (ob.pov.u_min, ob.pov.v_min, ob.pov.u_max, ob.pov.v_max),
|
|
)
|
|
# Previous to 3.8 default max_gradient 1.0 was too slow
|
|
tab_write(file, "max_gradient 0.001\n")
|
|
if ob.pov.contained_by == "sphere":
|
|
tab_write(file, "contained_by { sphere{0, 2} }\n")
|
|
else:
|
|
tab_write(file, "contained_by { box{-2, 2} }\n")
|
|
tab_write(file, "max_gradient %.6f\n" % ob.pov.max_gradient)
|
|
tab_write(file, "accuracy %.6f\n" % ob.pov.accuracy)
|
|
tab_write(file, "precompute 10 x,y,z\n")
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "ISOSURFACE_NODE":
|
|
tab_write(file, "#declare %s = isosurface{ \n" % povdataname)
|
|
tab_write(file, "function{ \n")
|
|
text_name = ob.pov.iso_function_text
|
|
if text_name:
|
|
node_tree = bpy.context.scene.node_tree
|
|
for node in node_tree.nodes:
|
|
if node.bl_idname == "IsoPropsNode" and node.label == ob.name:
|
|
for inp in node.inputs:
|
|
if inp:
|
|
tab_write(
|
|
file,
|
|
"#declare %s = %.6g;\n" % (inp.name, inp.default_value),
|
|
)
|
|
|
|
text = bpy.data.texts[text_name]
|
|
for line in text.lines:
|
|
split = line.body.split()
|
|
if split[0] != "#declare":
|
|
tab_write(file, "%s\n" % line.body)
|
|
else:
|
|
tab_write(file, "abs(x) - 2 + y")
|
|
tab_write(file, "}\n")
|
|
tab_write(file, "threshold %.6g\n" % ob.pov.threshold)
|
|
tab_write(file, "max_gradient %.6g\n" % ob.pov.max_gradient)
|
|
tab_write(file, "accuracy %.6g\n" % ob.pov.accuracy)
|
|
tab_write(file, "contained_by { ")
|
|
if ob.pov.contained_by == "sphere":
|
|
tab_write(file, "sphere {0,%.6g}}\n" % ob.pov.container_scale)
|
|
else:
|
|
tab_write(
|
|
file,
|
|
"box {-%.6g,%.6g}}\n"
|
|
% (ob.pov.container_scale, ob.pov.container_scale),
|
|
)
|
|
if ob.pov.all_intersections:
|
|
tab_write(file, "all_intersections\n")
|
|
else:
|
|
if ob.pov.max_trace > 1:
|
|
tab_write(file, "max_trace %.6g\n" % ob.pov.max_trace)
|
|
if ob.active_material:
|
|
# pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
|
|
try:
|
|
material = ob.active_material
|
|
write_object_material_interior(file, material, ob, tab_write)
|
|
except IndexError:
|
|
print(me)
|
|
# tab_write(file, "texture {%s}\n"%pov_mat_name)
|
|
tab_write(file, "scale %.6g\n" % (1 / ob.pov.container_scale))
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "ISOSURFACE_VIEW":
|
|
simple_isosurface_function = ob.pov.isosurface_eq
|
|
if simple_isosurface_function:
|
|
tab_write(file, "#declare %s = isosurface{ \n" % povdataname)
|
|
tab_write(file, "function{ \n")
|
|
tab_write(file, simple_isosurface_function)
|
|
tab_write(file, "}\n")
|
|
tab_write(file, "threshold %.6g\n" % ob.pov.threshold)
|
|
tab_write(file, "max_gradient %.6g\n" % ob.pov.max_gradient)
|
|
tab_write(file, "accuracy %.6g\n" % ob.pov.accuracy)
|
|
tab_write(file, "contained_by { ")
|
|
if ob.pov.contained_by == "sphere":
|
|
tab_write(file, "sphere {0,%.6g}}\n" % ob.pov.container_scale)
|
|
else:
|
|
tab_write(
|
|
file,
|
|
"box {-%.6g,%.6g}}\n"
|
|
% (ob.pov.container_scale, ob.pov.container_scale),
|
|
)
|
|
if ob.pov.all_intersections:
|
|
tab_write(file, "all_intersections\n")
|
|
else:
|
|
if ob.pov.max_trace > 1:
|
|
tab_write(file, "max_trace %.6g\n" % ob.pov.max_trace)
|
|
if ob.active_material:
|
|
# pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
|
|
try:
|
|
material = ob.active_material
|
|
write_object_material_interior(file, material, ob, tab_write)
|
|
except IndexError:
|
|
print(me)
|
|
# tab_write(file, "texture {%s}\n"%pov_mat_name)
|
|
tab_write(file, "scale %.6g\n" % (1 / ob.pov.container_scale))
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "SUPERELLIPSOID":
|
|
tab_write(
|
|
file,
|
|
"#declare %s = superellipsoid{ <%.4f,%.4f>\n"
|
|
% (povdataname, ob.pov.se_n2, ob.pov.se_n1),
|
|
)
|
|
if ob.active_material:
|
|
# pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
|
|
try:
|
|
material = ob.active_material
|
|
write_object_material_interior(file, material, ob, tab_write)
|
|
except IndexError:
|
|
print(me)
|
|
# tab_write(file, "texture {%s}\n"%pov_mat_name)
|
|
write_object_modifiers(ob, file)
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "SUPERTORUS":
|
|
rad_maj = ob.pov.st_major_radius
|
|
rad_min = ob.pov.st_minor_radius
|
|
ring = ob.pov.st_ring
|
|
cross = ob.pov.st_cross
|
|
accuracy = ob.pov.st_accuracy
|
|
gradient = ob.pov.st_max_gradient
|
|
# --- Inline Supertorus macro
|
|
file.write(
|
|
"#macro Supertorus(RMj, RMn, MajorControl, MinorControl, Accuracy, MaxGradient)\n"
|
|
)
|
|
file.write(" #local CP = 2/MinorControl;\n")
|
|
file.write(" #local RP = 2/MajorControl;\n")
|
|
file.write(" isosurface {\n")
|
|
file.write(
|
|
" 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"
|
|
)
|
|
file.write(" threshold 0\n")
|
|
file.write(
|
|
" contained_by {box {<-RMj-RMn,-RMn,-RMj-RMn>, < RMj+RMn, RMn, RMj+RMn>}}\n"
|
|
)
|
|
file.write(" #if(MaxGradient >= 1)\n")
|
|
file.write(" max_gradient MaxGradient\n")
|
|
file.write(" #else\n")
|
|
file.write(" evaluate 1, 10, 0.1\n")
|
|
file.write(" #end\n")
|
|
file.write(" accuracy Accuracy\n")
|
|
file.write(" }\n")
|
|
file.write("#end\n")
|
|
# ---
|
|
tab_write(
|
|
file,
|
|
"#declare %s = object{ Supertorus( %.4g,%.4g,%.4g,%.4g,%.4g,%.4g)\n"
|
|
% (
|
|
povdataname,
|
|
rad_maj,
|
|
rad_min,
|
|
ring,
|
|
cross,
|
|
accuracy,
|
|
gradient,
|
|
),
|
|
)
|
|
if ob.active_material:
|
|
# pov_mat_name = string_strip_hyphen(bpy.path.clean_name(obj.active_material.name))
|
|
try:
|
|
material = ob.active_material
|
|
write_object_material_interior(file, material, ob, tab_write)
|
|
except IndexError:
|
|
print(me)
|
|
# tab_write(file, "texture {%s}\n"%pov_mat_name)
|
|
write_object_modifiers(ob, file)
|
|
tab_write(file, "rotate x*90\n")
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
|
|
if ob.pov.object_as == "POLYCIRCLE":
|
|
# TODO write below macro Once:
|
|
# if write_polytocircle_macro_once == 0:
|
|
file.write("/****************************\n")
|
|
file.write("This macro was written by 'And'.\n")
|
|
file.write("Link:(http://news.povray.org/povray.binaries.scene-files/)\n")
|
|
file.write("****************************/\n")
|
|
file.write("//from math.inc:\n")
|
|
file.write("#macro VPerp_Adjust(V, Axis)\n")
|
|
file.write(" vnormalize(vcross(vcross(Axis, V), Axis))\n")
|
|
file.write("#end\n")
|
|
file.write("//Then for the actual macro\n")
|
|
file.write("#macro Shape_Slice_Plane_2P_1V(point1, point2, clip_direct)\n")
|
|
file.write("#local p1 = point1 + <0,0,0>;\n")
|
|
file.write("#local p2 = point2 + <0,0,0>;\n")
|
|
file.write("#local clip_v = vnormalize(clip_direct + <0,0,0>);\n")
|
|
file.write("#local direct_v1 = vnormalize(p2 - p1);\n")
|
|
file.write("#if(vdot(direct_v1, clip_v) = 1)\n")
|
|
file.write(' #error "Shape_Slice_Plane_2P_1V error: Can\'t decide plane"\n')
|
|
file.write("#end\n\n")
|
|
file.write(
|
|
"#local norm = -vnormalize(clip_v - direct_v1*vdot(direct_v1,clip_v));\n"
|
|
)
|
|
file.write("#local d = vdot(norm, p1);\n")
|
|
file.write("plane{\n")
|
|
file.write("norm, d\n")
|
|
file.write("}\n")
|
|
file.write("#end\n\n")
|
|
file.write("//polygon to circle\n")
|
|
file.write(
|
|
"#macro Shape_Polygon_To_Circle_Blending("
|
|
"_polygon_n, _side_face, "
|
|
"_polygon_circumscribed_radius, "
|
|
"_circle_radius, "
|
|
"_height)\n"
|
|
)
|
|
file.write("#local n = int(_polygon_n);\n")
|
|
file.write("#if(n < 3)\n")
|
|
file.write(" #error\n")
|
|
file.write("#end\n\n")
|
|
file.write("#local front_v = VPerp_Adjust(_side_face, z);\n")
|
|
file.write("#if(vdot(front_v, x) >= 0)\n")
|
|
file.write(" #local face_ang = acos(vdot(-y, front_v));\n")
|
|
file.write("#else\n")
|
|
file.write(" #local face_ang = -acos(vdot(-y, front_v));\n")
|
|
file.write("#end\n")
|
|
file.write("#local polyg_ext_ang = 2*pi/n;\n")
|
|
file.write("#local polyg_outer_r = _polygon_circumscribed_radius;\n")
|
|
file.write("#local polyg_inner_r = polyg_outer_r*cos(polyg_ext_ang/2);\n")
|
|
file.write("#local cycle_r = _circle_radius;\n")
|
|
file.write("#local h = _height;\n")
|
|
file.write("#if(polyg_outer_r < 0 | cycle_r < 0 | h <= 0)\n")
|
|
file.write(' #error "error: each side length must be positive"\n')
|
|
file.write("#end\n\n")
|
|
file.write("#local multi = 1000;\n")
|
|
file.write("#local poly_obj =\n")
|
|
file.write("polynomial{\n")
|
|
file.write("4,\n")
|
|
file.write("xyz(0,2,2): multi*1,\n")
|
|
file.write("xyz(2,0,1): multi*2*h,\n")
|
|
file.write("xyz(1,0,2): multi*2*(polyg_inner_r-cycle_r),\n")
|
|
file.write("xyz(2,0,0): multi*(-h*h),\n")
|
|
file.write("xyz(0,0,2): multi*(-pow(cycle_r - polyg_inner_r, 2)),\n")
|
|
file.write("xyz(1,0,1): multi*2*h*(-2*polyg_inner_r + cycle_r),\n")
|
|
file.write("xyz(1,0,0): multi*2*h*h*polyg_inner_r,\n")
|
|
file.write("xyz(0,0,1): multi*2*h*polyg_inner_r*(polyg_inner_r - cycle_r),\n")
|
|
file.write("xyz(0,0,0): multi*(-pow(polyg_inner_r*h, 2))\n")
|
|
file.write("sturm\n")
|
|
file.write("}\n\n")
|
|
file.write("#local mockup1 =\n")
|
|
file.write("difference{\n")
|
|
file.write(" cylinder{\n")
|
|
file.write(" <0,0,0.0>,<0,0,h>, max(polyg_outer_r, cycle_r)\n")
|
|
file.write(" }\n\n")
|
|
file.write(" #for(i, 0, n-1)\n")
|
|
file.write(" object{\n")
|
|
file.write(" poly_obj\n")
|
|
file.write(" inverse\n")
|
|
file.write(" rotate <0, 0, -90 + degrees(polyg_ext_ang*i)>\n")
|
|
file.write(" }\n")
|
|
file.write(" object{\n")
|
|
file.write(
|
|
" Shape_Slice_Plane_2P_1V(<polyg_inner_r,0,0>,<cycle_r,0,h>,x)\n"
|
|
)
|
|
file.write(" rotate <0, 0, -90 + degrees(polyg_ext_ang*i)>\n")
|
|
file.write(" }\n")
|
|
file.write(" #end\n")
|
|
file.write("}\n\n")
|
|
file.write("object{\n")
|
|
file.write("mockup1\n")
|
|
file.write("rotate <0, 0, degrees(face_ang)>\n")
|
|
file.write("}\n")
|
|
file.write("#end\n")
|
|
# Use the macro
|
|
ngon = ob.pov.polytocircle_ngon
|
|
ngonR = ob.pov.polytocircle_ngonR
|
|
circleR = ob.pov.polytocircle_circleR
|
|
tab_write(
|
|
file,
|
|
"#declare %s = object { Shape_Polygon_To_Circle_Blending("
|
|
"%s, z, %.4f, %.4f, 2) rotate x*180 translate z*1\n"
|
|
% (povdataname, ngon, ngonR, circleR),
|
|
)
|
|
tab_write(file, "}\n")
|
|
continue # Don't render proxy mesh, skip to next object
|
|
if csg:
|
|
# fluid_found early return no longer runs this
|
|
# todo maybe make a function to run in that other branch
|
|
duplidata_ref = []
|
|
_dupnames_seen = {} # avoid duplicate output during introspection
|
|
for ob in sel:
|
|
# matrix = global_matrix @ obj.matrix_world
|
|
if ob.is_instancer:
|
|
tab_write(file, "\n//--DupliObjects in %s--\n\n" % ob.name)
|
|
# obj.dupli_list_create(scene) #deprecated in 2.8
|
|
dup = ""
|
|
if ob.is_modified(scene, "RENDER"):
|
|
# modified object always unique so using object name rather than data name
|
|
dup = "#declare OB%s = union{\n" % (
|
|
string_strip_hyphen(bpy.path.clean_name(ob.name))
|
|
)
|
|
else:
|
|
dup = "#declare DATA%s = union{\n" % (
|
|
string_strip_hyphen(bpy.path.clean_name(ob.name))
|
|
)
|
|
|
|
for eachduplicate in depsgraph.object_instances:
|
|
if (
|
|
eachduplicate.is_instance
|
|
): # Real dupli instance filtered because original included in list since 2.8
|
|
_dupname = eachduplicate.object.name
|
|
_dupobj = bpy.data.objects[_dupname]
|
|
# BEGIN introspection for troubleshooting purposes
|
|
if "name" not in dir(_dupobj.data):
|
|
if _dupname not in _dupnames_seen:
|
|
print(
|
|
"WARNING: bpy.data.objects[%s].data (of type %s) has no 'name' attribute"
|
|
% (_dupname, type(_dupobj.data))
|
|
)
|
|
for _thing in dir(_dupobj):
|
|
print(
|
|
"|| %s.%s = %s"
|
|
% (_dupname, _thing, getattr(_dupobj, _thing))
|
|
)
|
|
_dupnames_seen[_dupname] = 1
|
|
print("''=> Unparseable objects so far: %s" % _dupnames_seen)
|
|
else:
|
|
_dupnames_seen[_dupname] += 1
|
|
continue # don't try to parse data objects with no name attribute
|
|
# END introspection for troubleshooting purposes
|
|
duplidataname = "OB" + string_strip_hyphen(
|
|
bpy.path.clean_name(_dupobj.data.name)
|
|
)
|
|
dupmatrix = (
|
|
eachduplicate.matrix_world.copy()
|
|
) # has to be copied to not store instance since 2.8
|
|
dup += "\tobject {\n\t\tDATA%s\n\t\t%s\t}\n" % (
|
|
string_strip_hyphen(bpy.path.clean_name(_dupobj.data.name)),
|
|
matrix_as_pov_string(ob.matrix_world.inverted() @ dupmatrix),
|
|
)
|
|
# add object to a list so that it is not rendered for some instance_types
|
|
if (
|
|
ob.instance_type != "COLLECTION"
|
|
and duplidataname not in duplidata_ref
|
|
):
|
|
duplidata_ref.append(
|
|
duplidataname,
|
|
) # older key [string_strip_hyphen(bpy.path.clean_name("OB"+obj.name))]
|
|
dup += "}\n"
|
|
# obj.dupli_list_clear()# just do not store any reference to instance since 2.8
|
|
tab_write(file, dup)
|
|
else:
|
|
continue
|
|
if _dupnames_seen:
|
|
print("WARNING: Unparseable objects in current .blend file:\n''--> %s" % _dupnames_seen)
|
|
if duplidata_ref:
|
|
print("duplidata_ref = %s" % duplidata_ref)
|
|
for data_name, inst in data_ref.items():
|
|
for ob_name, matrix_str in inst:
|
|
if ob_name not in duplidata_ref: # .items() for a dictionary
|
|
tab_write(file, "\n//----Blender Object Name: %s----\n" %
|
|
ob_name.removeprefix("OB"))
|
|
if ob.pov.object_as == "":
|
|
tab_write(file, "object { \n")
|
|
tab_write(file, "%s\n" % data_name)
|
|
tab_write(file, "%s\n" % matrix_str)
|
|
tab_write(file, "}\n")
|
|
else:
|
|
no_boolean = True
|
|
for mod in ob.modifiers:
|
|
if mod.type == "BOOLEAN":
|
|
operation = None
|
|
no_boolean = False
|
|
if mod.operation == "INTERSECT":
|
|
operation = "intersection"
|
|
else:
|
|
operation = mod.operation.lower()
|
|
mod_ob_name = string_strip_hyphen(
|
|
bpy.path.clean_name(mod.object.name)
|
|
)
|
|
mod_matrix = global_matrix @ mod.object.matrix_world
|
|
mod_ob_matrix = matrix_as_pov_string(mod_matrix)
|
|
tab_write(file, "%s { \n" % operation)
|
|
tab_write(file, "object { \n")
|
|
tab_write(file, "%s\n" % data_name)
|
|
tab_write(file, "%s\n" % matrix_str)
|
|
tab_write(file, "}\n")
|
|
tab_write(file, "object { \n")
|
|
tab_write(file, "%s\n" % ("DATA" + mod_ob_name))
|
|
tab_write(file, "%s\n" % mod_ob_matrix)
|
|
tab_write(file, "}\n")
|
|
tab_write(file, "}\n")
|
|
break
|
|
if no_boolean:
|
|
tab_write(file, "object { \n")
|
|
tab_write(file, "%s\n" % data_name)
|
|
tab_write(file, "%s\n" % matrix_str)
|
|
tab_write(file, "}\n")
|