Skip to content
Snippets Groups Projects
  • Maurice Raybaud's avatar
    d1b824f3
    POV: fix some nested code and further files structure cleanup · d1b824f3
    Maurice Raybaud authored
    * FIX: wrongly nested pov braces made the default outpout file fail
    * FIX: use agnostic metallic property rather than create a duplicate
    * FIX: some 2.8 deprecated properties rewired in spec;diff; emit;ambient
    * FIX: clean up, hierarchize and redesign Global Settings ui panel
    * FIX: re-wire world background alpha to agnostic prop and redo its ui
    * FIX: wrong nested pov braces making the default outpout file fail
    * FIX: use agnostic metallic property rather than create a duplicate
    * FIX: reduced arguments numbers by imports and relocating variables
    * FIX: use more list comprehesions to reduce nested conditions levels
    * FIX: use more consistent class names but cleanup still not finished
    * FIX: use single quotes for enums preferably to distinguish strings
    * FIX: basic level of nodes based material (diffuse color) broken API
    * FIX: blurry reflection corner case caused output file to fail
    * FIX: added context managing ("with") syntaxes reducing crash cases
    ___________________________________________________________
    
    * ADD: model_all.py file to extract mostly object level loop and utils
    * ADD: model_meta_topology.py file to extract metaballs export
    * ADD: object_primitives_topology.py to extract pov compound primitives
    * ADD: nodes_fn.py file to extract main node exporting function
    * ADD: nodes_gui.py file to extract node operators and menus
    * ADD: nodes_properties.py file to extract nodes sub parameters
    * ADD: particles_properties.py to extract particles and fx parameters
    * ADD: render_core.py to extract main RenderEngine inheriting class(es)
    * ADD: shading_ray_properties.py to extract pathtraced shader parameters
    * ADD: texturing_procedural.py to extract algorithmic texture influences
    ___________________________________________________________
    
    * UPDATE: workspace tools icons and a couple of other icons choices
    * RENAME: pov.add.polygontocircle.dat macro workspace tool icon
    * RENAME: base_ui.py to ui_core.py
    * RENAME: shading_nodes.py to nodes.py
    * RENAME: df3_library.py to voxel_lib.py to make dot lookup inform more
    * RENAME: object_mesh_topology.py to model_poly_topology.py
    * RENAME: object_curve_topology.py to model_curve_topology.py
    * RENAME: object_gui.py to model_gui.py
    * RENAME: object_primitives.py to model_primitives.py
    * RENAME: object_properties.py to model_properties.py
    * RENAME: object_particles.py to particles.py
    d1b824f3
    History
    POV: fix some nested code and further files structure cleanup
    Maurice Raybaud authored
    * FIX: wrongly nested pov braces made the default outpout file fail
    * FIX: use agnostic metallic property rather than create a duplicate
    * FIX: some 2.8 deprecated properties rewired in spec;diff; emit;ambient
    * FIX: clean up, hierarchize and redesign Global Settings ui panel
    * FIX: re-wire world background alpha to agnostic prop and redo its ui
    * FIX: wrong nested pov braces making the default outpout file fail
    * FIX: use agnostic metallic property rather than create a duplicate
    * FIX: reduced arguments numbers by imports and relocating variables
    * FIX: use more list comprehesions to reduce nested conditions levels
    * FIX: use more consistent class names but cleanup still not finished
    * FIX: use single quotes for enums preferably to distinguish strings
    * FIX: basic level of nodes based material (diffuse color) broken API
    * FIX: blurry reflection corner case caused output file to fail
    * FIX: added context managing ("with") syntaxes reducing crash cases
    ___________________________________________________________
    
    * ADD: model_all.py file to extract mostly object level loop and utils
    * ADD: model_meta_topology.py file to extract metaballs export
    * ADD: object_primitives_topology.py to extract pov compound primitives
    * ADD: nodes_fn.py file to extract main node exporting function
    * ADD: nodes_gui.py file to extract node operators and menus
    * ADD: nodes_properties.py file to extract nodes sub parameters
    * ADD: particles_properties.py to extract particles and fx parameters
    * ADD: render_core.py to extract main RenderEngine inheriting class(es)
    * ADD: shading_ray_properties.py to extract pathtraced shader parameters
    * ADD: texturing_procedural.py to extract algorithmic texture influences
    ___________________________________________________________
    
    * UPDATE: workspace tools icons and a couple of other icons choices
    * RENAME: pov.add.polygontocircle.dat macro workspace tool icon
    * RENAME: base_ui.py to ui_core.py
    * RENAME: shading_nodes.py to nodes.py
    * RENAME: df3_library.py to voxel_lib.py to make dot lookup inform more
    * RENAME: object_mesh_topology.py to model_poly_topology.py
    * RENAME: object_curve_topology.py to model_curve_topology.py
    * RENAME: object_gui.py to model_gui.py
    * RENAME: object_primitives.py to model_primitives.py
    * RENAME: object_properties.py to model_properties.py
    * RENAME: object_particles.py to particles.py
render.py 31.60 KiB
# SPDX-License-Identifier: GPL-2.0-or-later

# <pep8 compliant>

"""Write the POV file using this file's functions and some from other modules then render it."""

import bpy
import subprocess
import os
from sys import platform
#import time
from math import (
    pi,
)  # maybe move to scenography.py and topology_*****_data.py respectively with smoke and matrix
import mathutils #import less than full
import tempfile  # generate temporary files with random names
from bpy.types import Operator
from bpy.utils import register_class, unregister_class

from . import (
    scripting,
)  # for writing, importing and rendering directly POV Scene Description Language items
from . import render_core
from . import scenography  # for atmosphere, environment, effects, lighting, camera
from . import shading  # for BI POV shaders emulation
from . import nodes_fn
from . import texturing_procedural # for Blender procedurals to POV patterns emulation
from . import model_all  # for mesh based geometry
from . import model_meta_topology  # for mesh based geometry
from . import model_curve_topology  # for curves based geometry

# from . import model_primitives  # for import and export of POV specific primitives


from .scenography import image_format, img_map, img_map_transforms, path_image

from .shading import write_object_material_interior
from .model_primitives import write_object_modifiers


tab_level = 0
tab=""
comments = False
using_uberpov = False
unpacked_images = []

from .render_core import (
    preview_dir,
    PovRender,
)

def string_strip_hyphen(name):

    """Remove hyphen characters from a string to avoid POV errors."""

    return name.replace("-", "")


def safety(name, ref_level_bound):
    """append suffix characters to names of various material declinations.

    Material declinations are necessary to POV syntax and used in shading.py
    by the pov_has_no_specular_maps function to create the finish map trick and
    the suffixes avoid name collisions.
    Keyword arguments:
    name -- the initial material name as a string
    ref_level_bound -- the enum number of the ref_level_bound being written:
        ref_level_bound=1 is for texture with No specular nor Mirror reflection
        ref_level_bound=2 is for texture with translation of spec and mir levels
        for when no map influences them
        ref_level_bound=3 is for texture with Maximum Spec and Mirror
    """
    # All the try except clause below seems useless as each time
    # prefix rewritten even after and outside of it what was the point?
    # It may not even be any longer possible to feed no arg from Blender UI
    # try:
    # if name: # if int(name) > 0: # could be zero if no argument provided
    # # and always triggered exception so is this similar ?
    # prefix = "shader"
    # except BaseException as e:
    # print(e.__doc__)
    # print('An exception occurred: {}'.format(e))
    # prefix = "" # rewritten below...
    prefix = "shader_"
    name = string_strip_hyphen(name)
    if ref_level_bound == 2:
        return prefix + name
    # implicit else-if (no return yet)
    if ref_level_bound == 1:
        return prefix + name + "0"  # used for 0 of specular map
    # implicit else-if (no return yet)
    if ref_level_bound == 3:
        return prefix + name + "1"  # used for 1 of specular map


# -------- end safety string name material


csg_list = []


def is_renderable(ob):
    """test for objects flagged as hidden or boolean operands not to render"""
    return not ob.hide_render and ob not in csg_list


def renderable_objects():
    """test for non hidden, non boolean operands objects to render"""
    return [ob for ob in bpy.data.objects if is_renderable(ob)]


def non_renderable_objects():
    """Boolean operands only. Not to render"""
    return list(csg_list)


def set_tab(tabtype, spaces):
    """Apply the configured indentation all along the exported POV file

    Arguments:
        tabtype -- Specifies user preference between tabs or spaces indentation
        spaces -- If using spaces, sets the number of space characters to use
    Returns:
        The beginning blank space for each line of the generated pov file
    """
    tab_str = ""
    match tabtype:
        case 'SPACE':
            tab_str = spaces * " "
        case 'NONE':
            tab_str = ""
        case 'TAB':
            tab_str = "\t"
    return tab_str



'''

# below properties not added to __init__ yet to avoid conflicts with material sss scale
# unless it would override then should be interfaced also in scene units property tab

# if scene.pov.sslt_enable:
    # file.write("    mm_per_unit %s\n"%scene.pov.mm_per_unit)
    # file.write("    subsurface {\n")
    # file.write("        samples %s, %s\n"%(scene.pov.sslt_samples_max,scene.pov.sslt_samples_min))
    # if scene.pov.sslt_radiosity:
        # file.write("        radiosity on\n")
    # file.write("}\n")

'''


# def write_object_modifiers(ob, File):
# """Translate some object level POV statements from Blender UI
# to POV syntax and write to exported file """

# # Maybe return that string to be added instead of directly written.

# '''XXX WIP
# import .model_all.write_object_csg_inside_vector
# write_object_csg_inside_vector(ob, file)
# '''

# if ob.pov.hollow:
# File.write("\thollow\n")
# if ob.pov.double_illuminate:
# File.write("\tdouble_illuminate\n")
# if ob.pov.sturm:
# File.write("\tsturm\n")
# if ob.pov.no_shadow:
# File.write("\tno_shadow\n")
# if ob.pov.no_image:
# File.write("\tno_image\n")
# if ob.pov.no_reflection:
# File.write("\tno_reflection\n")
# if ob.pov.no_radiosity:
# File.write("\tno_radiosity\n")
# if ob.pov.inverse:
# File.write("\tinverse\n")
# if ob.pov.hierarchy:
# File.write("\thierarchy\n")

# # XXX, Commented definitions
# '''
# if scene.pov.photon_enable:
# File.write("photons {\n")
# if ob.pov.target:
# File.write("target %.4g\n"%ob.pov.target_value)
# if ob.pov.refraction:
# File.write("refraction on\n")
# if ob.pov.reflection:
# File.write("reflection on\n")
# if ob.pov.pass_through:
# File.write("pass_through\n")
# File.write("}\n")
# if ob.pov.object_ior > 1:
# File.write("interior {\n")
# File.write("ior %.4g\n"%ob.pov.object_ior)
# if scene.pov.photon_enable and ob.pov.target and ob.pov.refraction and ob.pov.dispersion:
# File.write("ior %.4g\n"%ob.pov.dispersion_value)
# File.write("ior %s\n"%ob.pov.dispersion_samples)
# if scene.pov.photon_enable == False:
# File.write("caustics %.4g\n"%ob.pov.fake_caustics_power)
# '''

def tab_write(file, str_o, scene=None):
    """write directly to exported file if user checked autonamed temp files (faster).
    Otherwise, indent POV syntax from brackets levels and write to exported file"""

    if not scene:
        scene = bpy.data.scenes[0]
    global tab
    tab = set_tab(scene.pov.indentation_character, scene.pov.indentation_spaces)
    if scene.pov.tempfiles_enable:
        file.write(str_o)
    else:
        global tab_level
        brackets = str_o.count("{") - str_o.count("}") + str_o.count("[") - str_o.count("]")
        if brackets < 0:
            tab_level = tab_level + brackets
        if tab_level < 0:
            print("Indentation Warning: tab_level = %s" % tab_level)
            tab_level = 0
        if tab_level >= 1:
            file.write("%s" % tab * tab_level)
        file.write(str_o)
        if brackets > 0:
            tab_level = tab_level + brackets

def write_matrix(file, matrix):
    """Translate some transform matrix from Blender UI
    to POV syntax and write to exported file """
    tab_write(file,
        "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],
        )
    )
global_matrix = mathutils.Matrix.Rotation(-pi / 2.0, 4, 'X')

def write_pov(filename, scene=None, info_callback=None):
    """Main export process from Blender UI to POV syntax and write to exported file """

    with open(filename, "w") as file:
        # Only for testing
        if not scene:
            scene = bpy.data.scenes[0]

        render = scene.render
        world = scene.world
        global comments
        comments = scene.pov.comments_enable and not scene.pov.tempfiles_enable

        feature_set = bpy.context.preferences.addons[__package__].preferences.branch_feature_set_povray
        global using_uberpov
        using_uberpov = feature_set == 'uberpov'
        pov_binary = PovRender._locate_binary()

        if using_uberpov:
            print("Unofficial UberPOV feature set chosen in preferences")
        else:
            print("Official POV-Ray 3.7 feature set chosen in preferences")
        if 'uber' in pov_binary:
            print("The name of the binary suggests you are probably rendering with Uber POV engine")
        else:
            print("The name of the binary suggests you are probably rendering with standard POV engine")


        def unique_name(name, name_seq):
            """Increment any generated POV name that could get identical to avoid collisions"""

            if name not in name_seq:
                name = string_strip_hyphen(name)
                return name

            name_orig = name
            i = 1
            while name in name_seq:
                name = "%s_%.3d" % (name_orig, i)
                i += 1
            name = string_strip_hyphen(name)
            return name

        material_names_dictionary = {}
        DEF_MAT_NAME = ""  # or "Default"?

        # -----------------------------------------------------------------------------

        def export_global_settings(scene):
            """write all POV global settings to exported file """
            # Imperial units warning
            if scene.unit_settings.system == "IMPERIAL":
                print("Warning: Imperial units not supported")

            tab_write(file, "global_settings {\n")
            tab_write(file, "assumed_gamma 1.0\n")
            tab_write(file, "max_trace_level %d\n" % scene.pov.max_trace_level)

            if scene.pov.global_settings_advanced:
                if not scene.pov.radio_enable:
                    file.write("    adc_bailout %.6f\n" % scene.pov.adc_bailout)
                file.write("    ambient_light <%.6f,%.6f,%.6f>\n" % scene.pov.ambient_light[:])
                file.write("    irid_wavelength <%.6f,%.6f,%.6f>\n" % scene.pov.irid_wavelength[:])
                file.write("    number_of_waves %s\n" % scene.pov.number_of_waves)
                file.write("    noise_generator %s\n" % scene.pov.noise_generator)
            if scene.pov.radio_enable:
                tab_write(file, "radiosity {\n")
                tab_write(file, "adc_bailout %.4g\n" % scene.pov.radio_adc_bailout)
                tab_write(file, "brightness %.4g\n" % scene.pov.radio_brightness)
                tab_write(file, "count %d\n" % scene.pov.radio_count)
                tab_write(file, "error_bound %.4g\n" % scene.pov.radio_error_bound)
                tab_write(file, "gray_threshold %.4g\n" % scene.pov.radio_gray_threshold)
                tab_write(file, "low_error_factor %.4g\n" % scene.pov.radio_low_error_factor)
                tab_write(file, "maximum_reuse %.4g\n" % scene.pov.radio_maximum_reuse)
                tab_write(file, "minimum_reuse %.4g\n" % scene.pov.radio_minimum_reuse)
                tab_write(file, "nearest_count %d\n" % scene.pov.radio_nearest_count)
                tab_write(file, "pretrace_start %.3g\n" % scene.pov.radio_pretrace_start)
                tab_write(file, "pretrace_end %.3g\n" % scene.pov.radio_pretrace_end)
                tab_write(file, "recursion_limit %d\n" % scene.pov.radio_recursion_limit)
                tab_write(file, "always_sample %d\n" % scene.pov.radio_always_sample)
                tab_write(file, "normal %d\n" % scene.pov.radio_normal)
                tab_write(file, "media %d\n" % scene.pov.radio_media)
                tab_write(file, "subsurface %d\n" % scene.pov.radio_subsurface)
                tab_write(file, "}\n")
            once_sss = 1
            once_ambient = 1
            once_photons = 1
            for material in bpy.data.materials:
                if material.pov_subsurface_scattering.use and once_sss:
                    # In pov, the scale has reversed influence compared to blender. these number
                    # should correct that
                    tab_write(file,
                        "mm_per_unit %.6f\n" % (material.pov_subsurface_scattering.scale * 1000.0)
                    )
                    # 1000 rather than scale * (-100.0) + 15.0))

                    # In POV-Ray, the scale factor for all subsurface shaders needs to be the same

                    # formerly sslt_samples were multiplied by 100 instead of 10
                    sslt_samples = (11 - material.pov_subsurface_scattering.error_threshold) * 10

                    tab_write(file, "subsurface { samples %d, %d }\n" % (sslt_samples, sslt_samples / 10))
                    once_sss = 0

                if world and once_ambient:
                    tab_write(file, "ambient_light rgb<%.3g, %.3g, %.3g>\n" % world.pov.ambient_color[:])
                    once_ambient = 0

                if (
                    scene.pov.photon_enable
                    and once_photons
                    and (
                        material.pov.refraction_type == "2"
                        or material.pov.photons_reflection
                    )
                ):
                    tab_write(file, "photons {\n")
                    tab_write(file, "spacing %.6f\n" % scene.pov.photon_spacing)
                    tab_write(file, "max_trace_level %d\n" % scene.pov.photon_max_trace_level)
                    tab_write(file, "adc_bailout %.3g\n" % scene.pov.photon_adc_bailout)
                    tab_write(file,
                        "gather %d, %d\n"
                        % (scene.pov.photon_gather_min, scene.pov.photon_gather_max)
                    )
                    if scene.pov.photon_map_file_save_load in {'save'}:
                        ph_file_name = 'Photon_map_file.ph'
                        if scene.pov.photon_map_file != '':
                            ph_file_name = scene.pov.photon_map_file + '.ph'
                        ph_file_dir = tempfile.gettempdir()
                        path = bpy.path.abspath(scene.pov.photon_map_dir)
                        if os.path.exists(path):
                            ph_file_dir = path
                        full_file_name = os.path.join(ph_file_dir, ph_file_name)
                        tab_write(file, 'save_file "%s"\n' % full_file_name)
                        scene.pov.photon_map_file = full_file_name
                    if scene.pov.photon_map_file_save_load in {'load'}:
                        full_file_name = bpy.path.abspath(scene.pov.photon_map_file)
                        if os.path.exists(full_file_name):
                            tab_write(file, 'load_file "%s"\n' % full_file_name)
                    tab_write(file, "}\n")
                    once_photons = 0

            tab_write(file, "}\n")

        # sel = renderable_objects() #removed for booleans
        if comments:
            file.write(
                "//----------------------------------------------\n"
                "//--Exported with POV-Ray exporter for Blender--\n"
                "//----------------------------------------------\n\n"
            )
        file.write("#version 3.7;\n")  # Switch below as soon as 3.8 beta gets easy linked
        # file.write("#version 3.8;\n")
        file.write(
            "#declare Default_texture = texture{pigment {rgb 0.8} " "finish {brilliance 3.8} }\n\n"
        )
        if comments:
            file.write("\n//--Global settings--\n\n")

        export_global_settings(scene)

        if comments:
            file.write("\n//--Custom Code--\n\n")
        scripting.export_custom_code(file)

        if comments:
            file.write("\n//--Patterns Definitions--\n\n")
        local_pattern_names = []
        for texture in bpy.data.textures:  # ok?
            if texture.users > 0:
                current_pat_name = string_strip_hyphen(bpy.path.clean_name(texture.name))
                # string_strip_hyphen(patternNames[texture.name]) #maybe instead of the above
                local_pattern_names.append(current_pat_name)
                # use above list to prevent writing texture instances several times and assign in mats?
                if (
                    texture.type not in {'NONE', 'IMAGE'} and texture.pov.tex_pattern_type == 'emulator'
                ) or (texture.type in {'NONE', 'IMAGE'} and texture.pov.tex_pattern_type != 'emulator'):
                    file.write("\n#declare PAT_%s = \n" % current_pat_name)
                    file.write(texturing_procedural.export_pattern(texture))
                file.write("\n")
        if comments:
            file.write("\n//--Background--\n\n")

        scenography.export_world(file, scene.world, scene, global_matrix, tab_write)

        if comments:
            file.write("\n//--Cameras--\n\n")

        scenography.export_camera(file, scene, global_matrix, render, tab_write)

        if comments:
            file.write("\n//--Lamps--\n\n")

        for ob in bpy.data.objects:
            if ob.type == 'MESH':
                for mod in ob.modifiers:
                    if mod.type == 'BOOLEAN' and mod.object not in csg_list:
                        csg_list.append(mod.object)
        if csg_list:
            csg = False
            sel = non_renderable_objects()
            # export non rendered boolean objects operands
            model_all.objects_loop(
                file,
                scene,
                sel,
                csg,
                material_names_dictionary,
                unpacked_images,
                tab_level,
                tab_write,
                info_callback,
            )

        csg = True
        sel = renderable_objects()

        scenography.export_lights(
            [L for L in sel if (L.type == 'LIGHT' and L.pov.object_as != 'RAINBOW')],
            file,
            scene,
            global_matrix,
            tab_write,
        )

        if comments:
            file.write("\n//--Rainbows--\n\n")
        scenography.export_rainbows(
            [L for L in sel if (L.type == 'LIGHT' and L.pov.object_as == 'RAINBOW')],
            file,
            scene,
            global_matrix,
            tab_write,
        )

        if comments:
            file.write("\n//--Special Curves--\n\n")
        for c in sel:
            if c.is_modified(scene, 'RENDER'):
                continue  # don't export as pov curves objects with modifiers, but as mesh
            # Implicit else-if (as not skipped by previous "continue")
            if c.type == 'CURVE' and (c.pov.curveshape in {'lathe', 'sphere_sweep', 'loft', 'birail'}):
                model_curve_topology.export_curves(file, c, tab_write)

        if comments:
            file.write("\n//--Material Definitions--\n\n")
        # write a default pigment for objects with no material (comment out to show black)
        file.write("#default{ pigment{ color srgb 0.8 }}\n")
        # Convert all materials to strings we can access directly per vertex.
        # exportMaterials()
        shading.write_material(
            file,
            using_uberpov,
            DEF_MAT_NAME,
            tab_write,
            comments,
            unique_name,
            material_names_dictionary,
            None,
        )  # default material
        for material in bpy.data.materials:
            if material.users > 0:
                r, g, b, a = material.diffuse_color[:]
                pigment_color = "pigment {rgbt <%.4g,%.4g,%.4g,%.4g>}" % (r, g, b, 1 - a)
                if material.pov.material_use_nodes:
                    # Also make here other pigment_color fallback using BSDF node main color ?
                    ntree = material.node_tree
                    pov_mat_name = string_strip_hyphen(bpy.path.clean_name(material.name))
                    if len(ntree.nodes) == 0:
                        file.write('#declare %s = texture {%s}\n' % (pov_mat_name, pigment_color))
                    else:
                        nodes_fn.write_nodes(pov_mat_name, ntree, file)

                    for node in ntree.nodes:
                        if node:
                            if node.bl_idname == "PovrayOutputNode":
                                if node.inputs["Texture"].is_linked:
                                    for link in ntree.links:
                                        if link.to_node.bl_idname == "PovrayOutputNode":
                                            pov_mat_name = (
                                                string_strip_hyphen(
                                                    bpy.path.clean_name(link.from_node.name)
                                                )
                                                + "_%s" % pov_mat_name
                                            )
                                else:
                                    file.write(
                                        '#declare %s = texture {%s}\n' % (pov_mat_name, pigment_color)
                                    )
                else:
                    shading.write_material(
                        file,
                        using_uberpov,
                        DEF_MAT_NAME,
                        tab_write,
                        comments,
                        unique_name,
                        material_names_dictionary,
                        material,
                    )
                # attributes are all the variables needed by the other python file...
        if comments:
            file.write("\n")

        model_meta_topology.export_meta(file,
                                         [m for m in sel if m.type == 'META'],
                                         tab_write,
                                         DEF_MAT_NAME,)

        if comments:
            file.write("//--Mesh objects--\n")

        # tbefore = time.time()
        model_all.objects_loop(
            file,
            scene,
            sel,
            csg,
            material_names_dictionary,
            unpacked_images,
            tab_level,
            tab_write,
            info_callback,
        )
        # totime = time.time() - tbefore
        # print("objects_loop took" + str(totime))

        # What follow used to happen here:
        # export_camera()
        # scenography.export_world(file, scene.world, scene, global_matrix, tab_write)
        # export_global_settings(scene)
        # MR:..and the order was important for implementing pov 3.7 baking
        #      (mesh camera) comment for the record
        # CR: Baking should be a special case than. If "baking", than we could change the order.

    if not file.closed:
        file.close()

def write_pov_ini(filename_ini, filename_log, filename_pov, filename_image):
    """Write ini file."""
    feature_set = bpy.context.preferences.addons[__package__].preferences.branch_feature_set_povray
    global using_uberpov
    using_uberpov = feature_set == 'uberpov'
    # scene = bpy.data.scenes[0]
    scene = bpy.context.scene
    render = scene.render

    x = int(render.resolution_x * render.resolution_percentage * 0.01)
    y = int(render.resolution_y * render.resolution_percentage * 0.01)

    with open(filename_ini, "w") as file:
        file.write("Version=3.7\n")
        # write povray text stream to temporary file of same name with _log suffix
        # file.write("All_File='%s'\n" % filename_log)
        # DEBUG.OUT log if none specified:
        file.write("All_File=1\n")

        file.write("Input_File_Name='%s'\n" % filename_pov)
        file.write("Output_File_Name='%s'\n" % filename_image)

        file.write("Width=%d\n" % x)
        file.write("Height=%d\n" % y)

        # Border render.
        if render.use_border:
            file.write("Start_Column=%4g\n" % render.border_min_x)
            file.write("End_Column=%4g\n" % render.border_max_x)

            file.write("Start_Row=%4g\n" % (1.0 - render.border_max_y))
            file.write("End_Row=%4g\n" % (1.0 - render.border_min_y))

        file.write("Bounding_Method=2\n")  # The new automatic BSP is faster in most scenes

        # Activated (turn this back off when better live exchange is done between the two programs
        # (see next comment)
        file.write("Display=1\n")
        file.write("Pause_When_Done=0\n")
        # PNG, with POV-Ray 3.7, can show background color with alpha. In the long run using the
        # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
        file.write("Output_File_Type=N\n")
        # file.write("Output_File_Type=T\n") # TGA, best progressive loading
        file.write("Output_Alpha=1\n")

        if scene.pov.antialias_enable:
            # method 2 (recursive) with higher max subdiv forced because no mipmapping in POV-Ray
            # needs higher sampling.
            # aa_mapping = {"5": 2, "8": 3, "11": 4, "16": 5}
            if using_uberpov:
                method = {"0": 1, "1": 2, "2": 3}
            else:
                method = {"0": 1, "1": 2, "2": 2}
            file.write("Antialias=on\n")
            file.write("Antialias_Depth=%d\n" % scene.pov.antialias_depth)
            file.write("Antialias_Threshold=%.3g\n" % scene.pov.antialias_threshold)
            if using_uberpov and scene.pov.antialias_method == '2':
                file.write("Sampling_Method=%s\n" % method[scene.pov.antialias_method])
                file.write("Antialias_Confidence=%.3g\n" % scene.pov.antialias_confidence)
            else:
                file.write("Sampling_Method=%s\n" % method[scene.pov.antialias_method])
            file.write("Antialias_Gamma=%.3g\n" % scene.pov.antialias_gamma)
            if scene.pov.jitter_enable:
                file.write("Jitter=on\n")
                file.write("Jitter_Amount=%3g\n" % scene.pov.jitter_amount)
            else:
                file.write("Jitter=off\n")  # prevent animation flicker

        else:
            file.write("Antialias=off\n")
    if not file.closed:
        file.close()


# --------------------------------------------------------------------------------- #
# ----------------------------------- Operators ----------------------------------- #
# --------------------------------------------------------------------------------- #
class RenderPovTexturePreview(Operator):
    """Export only files necessary to texture preview and render image"""

    bl_idname = "tex.preview_update"
    bl_label = "Update preview"

    def execute(self, context):
        tex = bpy.context.object.active_material.active_texture  # context.texture
        tex_prev_name = string_strip_hyphen(bpy.path.clean_name(tex.name)) + "_prev"

        # Make sure Preview directory exists and is empty
        if not os.path.isdir(preview_dir):
            os.mkdir(preview_dir)

        ini_prev_file = os.path.join(preview_dir, "Preview.ini")
        input_prev_file = os.path.join(preview_dir, "Preview.pov")
        output_prev_file = os.path.join(preview_dir, tex_prev_name)
        # ---------------------------------- ini ---------------------------------- #
        with open(ini_prev_file, "w") as file_ini:
            file_ini.write('Version=3.8\n')
            file_ini.write('Input_File_Name="%s"\n' % input_prev_file)
            file_ini.write('Output_File_Name="%s.png"\n' % output_prev_file)
            file_ini.write('Library_Path="%s"\n' % preview_dir)
            file_ini.write('Width=256\n')
            file_ini.write('Height=256\n')
            file_ini.write('Pause_When_Done=0\n')
            file_ini.write('Output_File_Type=N\n')
            file_ini.write('Output_Alpha=1\n')
            file_ini.write('Antialias=on\n')
            file_ini.write('Sampling_Method=2\n')
            file_ini.write('Antialias_Depth=3\n')
            file_ini.write('-d\n')
        if not file_ini.closed:
            file_ini.close()
        # ---------------------------------- pov ---------------------------------- #
        with open(input_prev_file, "w") as file_pov:
            pat_name = "PAT_" + string_strip_hyphen(bpy.path.clean_name(tex.name))
            file_pov.write("#declare %s = \n" % pat_name)
            file_pov.write(texturing_procedural.export_pattern(tex))

            file_pov.write("#declare Plane =\n")
            file_pov.write("mesh {\n")
            file_pov.write(
                "    triangle {<-2.021,-1.744,2.021>,<-2.021,-1.744,-2.021>,<2.021,-1.744,2.021>}\n"
            )
            file_pov.write(
                "    triangle {<-2.021,-1.744,-2.021>,<2.021,-1.744,-2.021>,<2.021,-1.744,2.021>}\n"
            )
            file_pov.write("    texture{%s}\n" % pat_name)
            file_pov.write("}\n")
            file_pov.write("object {Plane}\n")
            file_pov.write("light_source {\n")
            file_pov.write("    <0,4.38,-1.92e-07>\n")
            file_pov.write("    color rgb<4, 4, 4>\n")
            file_pov.write("    parallel\n")
            file_pov.write("    point_at  <0, 0, -1>\n")
            file_pov.write("}\n")
            file_pov.write("camera {\n")
            file_pov.write("    location  <0, 0, 0>\n")
            file_pov.write("    look_at  <0, 0, -1>\n")
            file_pov.write("    right <-1.0, 0, 0>\n")
            file_pov.write("    up <0, 1, 0>\n")
            file_pov.write("    angle  96.805211\n")
            file_pov.write("    rotate  <-90.000003, -0.000000, 0.000000>\n")
            file_pov.write("    translate <0.000000, 0.000000, 0.000000>\n")
            file_pov.write("}\n")
        if not file_pov.closed:
            file_pov.close()
        # ------------------------------- end write ------------------------------- #

        pov_binary = PovRender._locate_binary()

        if platform.startswith('win'):
            with subprocess.Popen(
                ["%s" % pov_binary, "/EXIT", "%s" % ini_prev_file],
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
            ) as p1:
                p1.wait()
        else:
            with subprocess.Popen(
                ["%s" % pov_binary, "-d", "%s" % ini_prev_file],
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
            ) as p1:
                p1.wait()

        tex.use_nodes = True
        tree = tex.node_tree
        links = tree.links
        for n in tree.nodes:
            tree.nodes.remove(n)
        im = tree.nodes.new("TextureNodeImage")
        path_prev = "%s.png" % output_prev_file
        im.image = bpy.data.images.load(path_prev)
        name = path_prev
        name = name.split("/")
        name = name[len(name) - 1]
        im.name = name
        im.location = 200, 200
        previewer = tree.nodes.new('TextureNodeOutput')
        previewer.label = "Preview"
        previewer.location = 400, 400
        links.new(im.outputs[0], previewer.inputs[0])
        # tex.type="IMAGE" # makes clip extend possible
        # tex.extension="CLIP"
        return {'FINISHED'}


class RunPovTextRender(Operator):
    """Export files depending on text editor options and render image."""

    bl_idname = "text.run"
    bl_label = "Run"
    bl_context = "text"
    bl_description = "Run a render with this text only"

    def execute(self, context):
        scene = context.scene
        scene.pov.text_block = context.space_data.text.name

        bpy.ops.render.render()

        # empty text name property again
        scene.pov.text_block = ""
        return {'FINISHED'}


classes = (
    #PovRender,
    RenderPovTexturePreview,
    RunPovTextRender,
)


def register():
    for cls in classes:
        register_class(cls)
    scripting.register()


def unregister():
    scripting.unregister()
    for cls in reversed(classes):
        unregister_class(cls)